Exiba o desafio 3DS
Abra o desafio em um iframe quando a cobrança voltar RequiresAction, receba as mensagens do navegador e confirme o resultado pelo servidor.
Quando o emissor pede que o comprador prove ser o dono do cartão (com senha do app do banco ou código por SMS), a criação da cobrança retorna status: "RequiresAction" e um nextAction com a URL do desafio. O seu checkout abre essa URL num iframe; o resto da conversa com o banco acontece lá dentro.
{
"id": "e092c6cc-8d6f-47d3-8616-df99670286dd",
"object": "payment_intent",
"amount": 10000,
"currency": "BRL",
"status": "RequiresAction",
"statusDetail": "PENDING_CHALLENGE",
"nextAction": {
"type": "IFRAME_CHALLENGE",
"url": "URL_DE_DESAFIO_RETORNADA_PELA_API",
"authenticationId": "6c868afc-d6bc-45c4-9886-ed97d20a2729",
"expiresAt": "2026-09-08T15:10:00Z"
}
}Use nextAction.url inteira, sem montar a URL a partir do authenticationId.
1. Abra o iframe
Registre o listener de mensagens antes de carregar o iframe, para não perder uma resposta rápida.
const frame = document.createElement('iframe');
frame.title = 'Verificação do pagamento';
frame.style.width = '100%';
frame.style.maxWidth = '420px';
frame.height = '600';
const hooksOrigin = new URL(DM_HOOKS_BASE).origin;
function onChallengeMessage(event) {
// Aceite só mensagens da Hooks, vindas deste iframe.
if (event.origin !== hooksOrigin) return;
if (event.source !== frame.contentWindow) return;
let msg;
try {
msg = JSON.parse(event.data); // a mensagem é uma string JSON
} catch {
return;
}
if (!msg || typeof msg !== 'object') return;
if (msg.type === 'card_3ds.processing') {
showPending(); // não feche o iframe: o resultado final ainda pode chegar
return;
}
if (msg.type === 'card_3ds.succeeded' || msg.type === 'card_3ds.failed') {
window.removeEventListener('message', onChallengeMessage);
frame.remove();
refreshOrderFromYourBackend(paymentIntentId);
}
}
window.addEventListener('message', onChallengeMessage);
frame.src = nextAction.url;
document.getElementById('challenge-container').appendChild(frame);showPending e refreshOrderFromYourBackend são funções do seu checkout. A segunda consulta o seu backend, que por sua vez consulta o PaymentIntent na API. O navegador nunca libera o pedido sozinho.
O desafio funciona somente em iframe. Aberto como pop-up ou por redirecionamento da página inteira, o comprador consegue se autenticar, mas o seu checkout não recebe nenhuma mensagem.
2. Interprete as mensagens
Cada mensagem é uma string JSON com três campos: type, authentication_id e detail.
type | detail | Significado | O checkout deve |
|---|---|---|---|
card_3ds.succeeded | null | A autenticação terminou. | Fechar o iframe e consultar o resultado da cobrança no servidor. |
card_3ds.failed | null | A autenticação falhou, foi recusada ou expirou. | Fechar o iframe e consultar o resultado no servidor. |
card_3ds.failed | { "reason": "cancelled" } | O comprador clicou em Cancelar. | Fechar o iframe e oferecer outro meio de pagamento. |
card_3ds.processing | { "reason": "awaiting_outcome" } | O banco respondeu e o resultado está sendo apurado. Costuma resolver em segundos. | Mostrar "processando" sem remover o iframe. |
card_3ds.processing | { "reason": "ui_timeout" } | Dez minutos se passaram sem retorno do banco. O comprador pode ainda estar digitando. | Mostrar "processando" sem remover o iframe. |
card_3ds.processing | { "reason": "tail_timeout" } | O resultado demorou mais de 90 segundos para ser apurado. | Mostrar "processando" e acompanhar a cobrança pelo servidor. |
Regras de entrega:
- Cada desafio envia no máximo uma mensagem final (
succeededoufailed) e no máximo umaprocessing. processingnão encerra o fluxo: uma mensagem final pode chegar depois dela.- O
authentication_idpode mudar dentro do mesmo pagamento, se o banco pedir um segundo desafio. Não use esse campo para filtrar mensagens.
Autenticação não é pagamento
card_3ds.succeeded diz que o comprador se autenticou, não que o pagamento foi aprovado. O emissor ainda pode recusar a cobrança depois do desafio. O resultado financeiro é sempre o status do PaymentIntent.
3. Confirme pelo servidor
Depois de qualquer mensagem final, o seu backend consulta GET /api/v1/paymentintents/{id} e decide:
status | Ação |
|---|---|
Succeeded | Concluir o pedido, de forma idempotente. |
Failed | Ler lastPaymentError e orientar o comprador. |
Processing | Manter o pedido em processamento e aguardar webhook ou nova consulta. |
RequiresAction | O desafio ainda está aberto. Se nextAction.expiresAt não passou, reabra o iframe com a mesma URL. |
O webhook payment_intent.succeeded também confirma a aprovação. Não há webhook específico para o desafio 3DS.
Comprador que sai e volta
Fechar o iframe ou recarregar a página não cancela a cobrança. Quando o comprador voltar ao checkout, consulte o PaymentIntent: se ele continuar RequiresAction com nextAction.expiresAt no futuro, reabra o desafio com a mesma nextAction.url. Se já tiver um desfecho, siga por ele.
Opções e dados do cartão
Campos do cartão, parcelamento, 3DS, débito, moeda, descritor de fatura e categoria de atividade (MCC).
Capture o cartão com o SDK de campos
Colete os dados do cartão em campos seguros da Accelerazione dentro do seu checkout e cobre com um token, sem que número e código de segurança passem pela sua página ou pelo seu servidor.