3-D Secure
Trate cobranças requires_action: carregue o SDK, rode o desafio e confirme.
Uma cobrança de cartão pode nascer requires_action pedindo autenticação 3-D Secure. O SDK hospedado roda o desafio no browser e você conclui em POST /charges/{id}/confirm-3ds, resolvendo a charge canônica final.
Quando o 3DS acontece
Quando uma regra de 3DS exige autenticação E a adquirente resolvida suporta 3DS, a charge nasce requires_action e inclui next_action em vez de autorizar na hora. Sem 3DS, next_action fica ausente e o fluxo segue normal.
O next_action da charge
next_action só aparece enquanto a charge está requires_action. sdk_payload é opaco, específico da adquirente e ligado ao browser — repasse ao SDK sem inspecionar.
{ "id": "ch_xxx", "object": "charge", "status": "requires_action", "amount_cents": 10990, "currency": "BRL", "payment_method": "credit_card", "next_action": { "type": "three_ds_challenge", "three_ds": { "authentication_id": "tds_xxx", "acquirer": "cielo", "sdk_payload": {} } }, "request_id": "req_xxx"}Inclua o SDK hospedado
Carregue o script hospedado e crie um cliente com create() — ele instala o global Bunne. Aponte baseUrl para a sua API (ou o seu proxy).
<script src="https://bunne.com.br/sdk/v1/bunne-3ds.js"></script><script> // Instala window.Bunne. Sem API key no browser: só emitimos chaves secretas // (sk_), que nunca podem ser expostas. O browser só roda o desafio; o // confirm-3ds é uma chamada do seu backend. const client = Bunne.create({ baseUrl: "https://bunne.com.br" });</script>Recomendado: desafio no browser, confirm no backend
No browser, rode initThreeDS(next_action): ele roda o desafio SEM chave e resolve { authentication_id, acquirer, result }. Seu backend faz o POST em /charges/{id}/confirm-3ds com a chave sk_.
// 1. No browser, rode só o desafio (agnóstico de adquirente). Sem rede ainda.// Resolve { authentication_id, acquirer, result }.const resolved = await client.initThreeDS(charge.next_action);// 2. Envie `resolved` ({ authentication_id, result }) para o SEU backend, que// confirma com a chave sk_ (exemplo abaixo). Nenhuma chave passa pelo browser.Confirm no seu backend
POST /charges/{id}/confirm-3ds com { authentication_id, result } retorna a charge canônica. É idempotente — um confirm duplicado é replay e nunca autoriza duas vezes.
curl -X POST https://bunne.com.br/api/v1/charges/ch_xxx/confirm-3ds \ -H "Authorization: Bearer sk_test_xxx" \ -H "Idempotency-Key: order_123_confirm_3ds" \ -H "Content-Type: application/json" \ -d '{ "authentication_id": "tds_xxx", "result": { "authenticated": true } }'O atalho de uma chamada
threeDSecure(charge) faz tudo: roda o desafio e chama o confirm-3ds. Como o confirm exige a chave sk_, use só onde a chave fica segura (backend ou render no servidor). Hoje, sem chave publishable, prefira o fluxo em duas etapas acima.
// charge é o JSON retornado por POST /charges (criado no seu backend).// Pode vir "requires_action" com next_action, ou já final.const finalCharge = await client.threeDSecure(charge);// finalCharge.status agora é final: "paid" / "authorized" / "refused".// Sem 3DS? threeDSecure resolve a mesma charge, sem chamada de rede.Nunca exponha sk_ no browser