Autenticação 3D Secure
O SDK da FastPay fornece uma implementação completa e simplificada de autenticação 3D Secure (3DS) para transações com cartão de crédito, garantindo máxima segurança e redução de fraudes.
Instalação
Via NPM
npm install @fastpaybr/security-sdk
Via CDN
<script src="https://js.fastpaybrasil.com/security-sdk.min.js"></script>
Inicialização
Configuração Básica
import FastPayClient from '@fastpaybr/security-sdk'
const client = new FastPayClient({
credentials: {
publicKey: 'sua_api_key_publica'
},
debug: false
})
await client.initialize()
if (client.isThreeDSAvailable()) {
console.log('3DS disponível e pronto para uso')
}
Verificação de Status
// Verificar se o SDK foi inicializado
if (client.isReady()) {
console.log('SDK inicializado com sucesso')
}
// Verificar se 3DS está disponível
if (client.isThreeDSAvailable()) {
console.log('3DS pronto para uso')
}
Fluxo de Autenticação 3DS
Visão Geral do Fluxo
Processo Completo (Recomendado)
O método process() gerencia todo o fluxo de autenticação automaticamente:
try {
const result = await client.threeds.process(
{
// Dados da transação
amount: 100.00, // Valor na moeda especificada
currency: 'BRL',
installments: 1, // Número de parcelas (1-12)
// Dados do cartão
card: {
number: '4111111111111111',
holderName: 'João Silva',
expirationMonth: '12',
expirationYear: '2025'
},
// Dados do cliente (necessários para 3DS)
customer: {
name: 'João Silva',
email: 'joao@email.com',
phone: '5511987654321'
},
// Endereço do cliente
address: {
street: 'Rua das Flores',
number: '123',
complement: 'Apto 45',
zipCode: '12345678',
neighborhood: 'Centro',
city: 'São Paulo',
state: 'SP',
country: 'BRA'
}
},
{
// Opções (todas opcionais)
fingerprintingTimeout: 30000, // Timeout para fingerprinting (recomendado: 30s)
challengeTimeout: 180000, // Timeout para challenge (recomendado: 3min)
modal: {
width: '600px', // Largura do modal
height: '600px', // Altura do modal
showCloseButton: true // Mostrar botão de fechar
}
}
)
// Resultado da autenticação
console.log('Autenticação concluída:', result)
// Usar dados na criação da cobrança
const charge = await createCharge({
// ... outros dados da cobrança
threeDS: result
})
} catch (error) {
console.error('Erro na autenticação 3DS:', error)
}
Estrutura do Resultado
interface ChallengeResultResponse {
type: 'full' | 'partial' | 'none'
state: 'success' | 'failure' | 'unenrolled' | 'error' | 'unsupported_brand' | 'disabled'
// Dados da autenticação (quando state === 'success')
cryptogram?: string // CAVV/AAV
directoryServerTransactionId?: string // DS Transaction ID
threeDsServerTransactionId?: string // 3DS Server Transaction ID
acsTransactionId?: string // ACS Transaction ID
eci?: string // Electronic Commerce Indicator
version?: string // Versão do protocolo 3DS
}
Estados de Autenticação
| Estado | Descrição | Ação Recomendada |
|---|---|---|
success | ✅ Autenticação bem-sucedida | Prosseguir com a cobrança |
failure | ❌ Cliente falhou na autenticação | Rejeitar transação ou solicitar outro método |
unenrolled | ⚠️ Cartão não inscrito no 3DS | Avaliar política de risco |
error | 🔴 Erro no processo de autenticação | Tentar novamente ou usar outro método |
unsupported_brand | 🚫 Bandeira não suporta 3DS | Usar outro método de pagamento |
disabled | 🔒 3DS desabilitado para este cartão | Avaliar política de risco |
Customização do Modal
Container Personalizado
Você pode fornecer seu próprio container para o iframe do challenge:
// Criar container personalizado
const customContainer = document.getElementById('my-3ds-container')
const result = await client.threeds.process({
// ... dados da transação
challengeContainer: customContainer
})
<div id="my-3ds-container" style="width: 500px; height: 500px;"></div>
Desabilitar Modal Automático
const result = await client.threeds.process(
{
// ... dados
challengeContainer: document.getElementById('my-container')
},
{
modal: false // Desabilita modal automático
}
)
Customizar Aparência do Modal
const result = await client.threeds.process(
{
/* ... dados */
},
{
modal: {
width: '800px',
height: '700px',
closeOnOverlayClick: false, // Impede fechar ao clicar fora
showCloseButton: false // Oculta botão de fechar
}
}
)
Exemplos de Implementação
React
import { useState } from 'react'
import FastPayClient from '@fastpaybr/security-sdk'
function CheckoutForm() {
const [loading, setLoading] = useState(false)
const [result, setResult] = useState(null)
const handleSubmit = async (formData) => {
setLoading(true)
try {
const client = new FastPayClient({
credentials: { publicKey: process.env.REACT_APP_FASTPAY_KEY }
})
await client.initialize()
if (!client.isThreeDSAvailable()) {
throw new Error('3DS não disponível')
}
const threeDSResult = await client.threeds.process({
amount: formData.amount,
currency: 'BRL',
card: {
number: formData.cardNumber,
holderName: formData.cardHolder,
expirationMonth: formData.expMonth,
expirationYear: formData.expYear
},
customer: {
name: formData.customerName,
email: formData.customerEmail,
phone: formData.customerPhone
},
address: formData.address
})
if (threeDSResult.state === 'success') {
// Criar cobrança com dados 3DS
const charge = await createCharge({
...formData,
threeDS: threeDSResult
})
setResult(charge)
} else {
throw new Error(`Autenticação falhou: ${threeDSResult.state}`)
}
} catch (error) {
console.error('Erro:', error)
alert(error.message)
} finally {
setLoading(false)
}
}
return (
<form onSubmit={handleSubmit}>
{/* Campos do formulário */}
<button type='submit' disabled={loading}>
{loading ? 'Processando...' : 'Finalizar Pagamento'}
</button>
</form>
)
}
Vanilla JavaScript
<!DOCTYPE html>
<html>
<head>
<title>Checkout com 3DS</title>
</head>
<body>
<form id="checkout-form">
<!-- Campos do formulário -->
<button type="submit">Pagar</button>
</form>
<script src="https://js.fastpaybrasil.com/security-sdk.min.js"></script>
<script>
let client = null
async function initializeSDK() {
client = new FastPay.FastPayClient({
credentials: {
publicKey: 'sua_chave_publica'
}
})
await client.initialize()
if (!client.isThreeDSAvailable()) {
alert('3DS não disponível no momento')
}
}
document.getElementById('checkout-form').addEventListener('submit', async (e) => {
e.preventDefault()
try {
const result = await client.threeds.process({
amount: 100.00,
currency: 'BRL',
card: {
number: document.getElementById('card-number').value,
holderName: document.getElementById('card-holder').value,
expirationMonth: document.getElementById('exp-month').value,
expirationYear: document.getElementById('exp-year').value
},
customer: {
name: document.getElementById('name').value,
email: document.getElementById('email').value,
phone: document.getElementById('phone').value
},
address: {
// ... dados do endereço
}
})
if (result.state === 'success') {
// Criar cobrança
console.log('3DS completo:', result)
}
} catch (error) {
console.error('Erro:', error)
}
})
// Inicializar ao carregar a página
initializeSDK()
</script>
</body>
</html>
Vue.js
<template>
<form @submit.prevent="handleSubmit">
<!-- Campos do formulário -->
<button type="submit" :disabled="loading">
{{ loading ? 'Processando...' : 'Pagar' }}
</button>
</form>
</template>
<script>
import FastPayClient from '@fastpaybr/security-sdk'
export default {
data() {
return {
loading: false,
client: null
}
},
async mounted() {
this.client = new FastPayClient({
credentials: {
publicKey: process.env.VUE_APP_FASTPAY_KEY
}
})
await this.client.initialize()
},
methods: {
async handleSubmit() {
if (!this.client.isThreeDSAvailable()) {
alert('3DS não disponível')
return
}
this.loading = true
try {
const result = await this.client.threeds.process({
// ... dados da transação
})
if (result.state === 'success') {
// Criar cobrança
await this.createCharge(result)
}
} catch (error) {
console.error('Erro:', error)
} finally {
this.loading = false
}
}
}
}
</script>
Métodos Disponíveis
client.initialize()
Inicializa o SDK e carrega os provedores de 3DS disponíveis.
await client.initialize()
Retorno: Promise<void>
client.isReady()
Verifica se o SDK foi inicializado com sucesso.
const ready = client.isReady()
Retorno: boolean
client.isThreeDSAvailable()
Verifica se o 3DS está disponível e pronto para uso.
const available = client.isThreeDSAvailable()
Retorno: boolean
client.threeds.process(request, options)
Executa o fluxo completo de autenticação 3DS.
Parâmetros:
interface ThreeDSProcessRequest {
amount: number // Valor na moeda especificada
currency: string // Código da moeda (ex: BRL, USD)
installments?: number // Número de parcelas (1-12)
card: {
number: string // Número do cartão
holderName?: string // Nome do titular
expirationMonth: string // Mês de expiração (1-12)
expirationYear: string // Ano de expiração (4 dígitos)
}
customer: {
name: string // Nome completo
email: string // Email do cliente
phone: string // Telefone com código do país
}
address: {
street: string // Logradouro
number: string // Número
complement?: string // Complemento (opcional)
zipCode: string // CEP
neighborhood: string // Bairro
city: string // Cidade
state: string // Estado
country: string // País (código ISO 3166-1 alpha-3)
}
challengeContainer?: HTMLElement // Container customizado para o challenge
}
interface ThreeDSProcessOptions {
fingerprintingTimeout?: number // Timeout para fingerprinting em ms (padrão: 30000)
challengeTimeout?: number // Timeout para challenge em ms (padrão: 180000)
modal?:
| {
width?: string // Largura do modal (padrão: '500px')
height?: string // Altura do modal (padrão: '600px')
closeOnOverlayClick?: boolean // Fechar ao clicar fora (padrão: true)
showCloseButton?: boolean // Mostrar botão X (padrão: true)
}
| false // false para desabilitar modal automático
}
Retorno: Promise<ChallengeResultResponse>
Integração com API de Cobranças
Após obter o resultado do 3DS, use-o na criação da cobrança:
// 1. Executar autenticação 3DS
const threeDSResult = await client.threeds.process({
amount: 100.00,
currency: 'BRL',
card: {
/* ... */
},
customer: {
/* ... */
},
address: {
/* ... */
}
})
// 2. Criar cobrança incluindo dados 3DS
const charge = await fetch('https://api-global.fastpaybrasil.com/v1/charges', {
method: 'POST',
headers: {
'Authorization': `Basic ${credentials}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
amount: 100.00,
currency: 'BRL',
customer: {
name: 'João Silva',
email: 'joao@email.com',
document: {
type: 'cpf',
id: '12345678900'
},
phone: '5511987654321'
},
paymentMethod: {
type: 'credit_card',
cardNumber: '4111111111111111',
cardholderName: 'João Silva',
expirationMonth: '12',
expirationYear: '2025',
cvv: '123',
installments: 1,
// Incluir dados 3DS
threeDS: threeDSResult
},
items: [
{
title: 'Produto XYZ',
type: 'digital',
description: 'Descrição',
unit_price: 10000,
quantity: 1
}
]
})
})
const result = await charge.json()
console.log('Cobrança criada:', result)
Cenários de Validação
| Situação | Comportamento |
|---|---|
| 3DS não fornecido | ❌ Cobrança recusada com erro 3ds_required |
state !== 'success' | ❌ Cobrança recusada com erro específico do estado |
| Dados incompletos | ❌ Cobrança recusada com erro 3ds_incomplete |
| Tudo OK | ✅ Cobrança prossegue normalmente |
Exemplos de Erros
{
"id": "charge_id",
"status": "refused",
"reason": "3ds_required: 3D Secure authentication is required for this merchant. Please complete 3DS authentication before creating a charge."
}
{
"id": "charge_id",
"status": "refused",
"reason": "3ds_failed: The cardholder failed to authenticate. Please try again or use a different card."
}
Tratamento de Erros
Erros Comuns
try {
const result = await client.threeds.process({
/* ... */
})
} catch (error) {
if (error.message.includes('not available')) {
// 3DS provider não está disponível
console.error('3DS não disponível no momento')
} else if (error.message.includes('timeout')) {
// Timeout no processo
console.error('Timeout na autenticação')
} else if (error.message.includes('container')) {
// Container não fornecido quando necessário
console.error('Container para challenge não fornecido')
} else {
// Outros erros
console.error('Erro desconhecido:', error)
}
}
Estados de Erro no Resultado
const result = await client.threeds.process({
/* ... */
})
switch (result.state) {
case 'success':
// Autenticação bem-sucedida
break
case 'failure':
// Cliente falhou na autenticação
alert('Autenticação falhou. Tente novamente ou use outro cartão.')
break
case 'unenrolled':
// Cartão não inscrito no 3DS
// Decisão de negócio: aceitar ou recusar
break
case 'error':
// Erro no processo
alert('Erro na autenticação. Tente novamente.')
break
case 'unsupported_brand':
// Bandeira não suporta 3DS
alert('Este cartão não suporta 3D Secure.')
break
case 'disabled':
// 3DS desabilitado
alert('3D Secure está desabilitado para este cartão.')
break
}
Boas Práticas
1. Sempre Inicializar Antes de Usar
// ❌ Errado
const result = await client.threeds.process({
/* ... */
})
// ✅ Correto
await client.initialize()
if (client.isThreeDSAvailable()) {
const result = await client.threeds.process({
/* ... */
})
}
2. Tratar Todos os Estados
const result = await client.threeds.process({
/* ... */
})
// ✅ Sempre trate todos os estados possíveis
if (result.state === 'success') {
// Prosseguir com cobrança
} else {
// Lidar com falha de acordo com sua política de negócio
handleAuthenticationFailure(result.state)
}
3. Usar Variáveis de Ambiente
// ❌ Errado - chave exposta
const client = new FastPayClient({
credentials: { publicKey: 'pk_abc123' }
})
// ✅ Correto - usar variáveis de ambiente
const client = new FastPayClient({
credentials: { publicKey: process.env.FASTPAY_PUBLIC_KEY }
})
4. Implementar Loading States
// ✅ Mostrar feedback visual ao usuário
setLoading(true)
try {
const result = await client.threeds.process({
/* ... */
})
// Processar resultado
} finally {
setLoading(false)
}
5. Log de Debug em Desenvolvimento
const client = new FastPayClient({
credentials: { publicKey: 'your_key' },
debug: process.env.NODE_ENV === 'development' // Apenas em dev
})
6. Configuração de Timeouts
Os valores padrão são otimizados para produção. Ajuste apenas se necessário:
const result = await client.threeds.process(
{
/* ... */
},
{
fingerprintingTimeout: 30000, // Recomendado: 30s (não aumente além de 45s)
challengeTimeout: 180000 // Recomendado: 3min (não aumente além de 5min)
}
)
⚠️ Importante:
- Fingerprinting: 30s é suficiente. Valores maiores podem indicar problemas de rede
- Challenge: 3min é o ideal. Timeouts muito longos prejudicam a conversão
- Em caso de timeout frequente, investigue a causa raiz ao invés de aumentar o valor
Troubleshooting
Modal não aparece
Possíveis causas:
- O fluxo foi frictionless (sem challenge necessário)
- O modal foi bloqueado por pop-up blocker
- Container customizado com display:none
Solução:
// Verificar se challenge foi necessário
console.log('Resultado:', result)
// Garantir que container está visível
const container = document.getElementById('container')
container.style.display = 'block'
Timeout no fingerprinting
Possíveis causas:
- Conexão de internet lenta do cliente
- Problemas no provedor 3DS
- Bloqueio de scripts no navegador
Solução:
// Apenas se necessário, aumente moderadamente (máx. 45s)
const result = await client.threeds.process(
{
/* ... */
},
{ fingerprintingTimeout: 45000 } // 45s (não recomendado acima disso)
)
⚠️ Importante: Timeouts frequentes indicam problemas que devem ser investigados, não apenas contornados
Erro "3DS provider not available"
Possíveis causas:
- SDK não foi inicializado
- Nenhum provider 3DS configurado no backend
- Erro na inicialização do provider
Solução:
await client.initialize()
if (!client.isThreeDSAvailable()) {
console.error('3DS não disponível. Verifique a configuração no backend.')
// Implementar fallback ou notificar usuário
}
Erro "container is required"
Causa: Modal desabilitado sem fornecer container customizado
Solução:
// Opção 1: Usar modal automático (padrão)
const result = await client.threeds.process({
/* ... */
})
// Opção 2: Fornecer container se modal desabilitado
const result = await client.threeds.process(
{
/* ... */
challengeContainer: document.getElementById('my-container')
},
{ modal: false }
)
Dados do cartão inválidos
Solução:
// Validar dados antes de chamar o SDK
if (!isValidCardNumber(cardNumber)) {
alert('Número do cartão inválido')
return
}
if (!isValidExpiration(month, year)) {
alert('Data de validade inválida')
return
}