3-D Secure
Trate 3DS e Risk Session: carregue o SDK, rode o desafio e envie contexto de risco na cobrança.
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.
Uma chave com a capacidade charges:bypass_3ds nunca pausa: a cobrança autoriza na hora, sem next_action, mesmo com a regra exigindo 3DS. É uma capacidade sensível, concedida caso a caso pela plataforma — e sem 3DS não há liability shift, então o risco de fraude fica com a operação.
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. Crie o cliente com a pk_ na próxima etapa.</script>Inicie a Risk Session
Assim que a página carregar, crie a instância com uma pk_ seller-scoped. Bunne inicia a sessão imediatamente; antes de criar a cobrança, riskSession() devolve o id.
// Bunne.create inicia e coleta a Risk Session agora.// baseUrl pode ser omitido quando a SDK e a API estão na mesma origem.const client = Bunne.create({ baseUrl: "https://bunne.com.br", apiKey: "pk_test_xxx",});// Antes de criar a cobrança, aguarde e pegue o id dessa mesma sessão.const riskSession = await client.riskSession();// Envie só riskSession.id ao SEU backend junto com o pedido de checkout.// Não envie a pk_, client_secret ou dados de browser para o seu servidor.Envie a sessão na cobrança
Envie riskSession.id ao seu backend e inclua-o como risk_session_id em POST /charges usando a sk_.
curl -X POST https://bunne.com.br/api/v1/charges \ -H "Authorization: Bearer sk_test_xxx" \ -H "Idempotency-Key: order_123" \ -H "Content-Type: application/json" \ -d '{ "amount": 10990, "payment_method": "credit_card", "card_token": "tok_xxx", "installments": 1, "risk_session_id": "rks_xxx", "buyer": { "...": "campos normais da cobrança" } }'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). A pk_ de Risk Session não autoriza esse confirm; no browser, 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