Checkout
Pix, cartão com 3DS2. Para integração browser completa, veja também @rebornpay/elements. Sellers internacionais: outro guia.
Este guia é o checkout Brasil (PIX + cartão 3DS2). Se vocês têm UI própria e cobram USD/EUR, use Clientes internacionais.
POST /v1/checkout-sessions— cria sessão (público ou com API key)POST /v1/checkout-sessions/{id}/confirmcompaymentMethod: PIX- Exibir QR code / copia-e-cola e consultar status da transação
Pagamentos com cartão exigem autenticação 3DS2 no browser do comprador. O fluxo não pode ser feito 100% server-side: parte dele roda no frontend de quem integra.
POST /v1/checkout-sessions— cria sessão- Tokenização do cartão via PCI (PAN/CVV nunca passam pela sua API)
POST .../threeds/setup— retornadeviceDataCollectionUrleaccessToken- POST do
accessToken(campoJWT) paradeviceDataCollectionUrlem iframe oculto (~2s) — coleta de device fingerprint (DDC) POST .../threeds/authenticate— enviadeviceInformation+ billing +deviceChannel: BROWSER- Se retornar
stepUpUrl, POST doaccessToken(campoJWT) nesse URL no iframe do desafio — não navegar parareturnUrl— e depoisPOST .../threeds/challenge-result POST .../confirmcomcardTokene objetothreeDs(CAVV, XID, etc.)
IP do comprador: nunca confie em deviceInformation.ipAddress vindo do JavaScript. Proxie threeds/authenticate pelo seu BFF e sobrescreva o IP com o valor de x-forwarded-for / x-real-ip.
No checkout hospedado Reborn, o antifraude roda no browser e segue no POST .../confirm antes da cobrança. Se você monta o checkout na sua página, inicialize o helper no mount, colete a sessão antes do confirm e trate CHK-010 (bloqueado) e CHK-011 (revisão manual) na UI.
Instale @rebornpay/checkout-3ds para reutilizar coleta de device, iframes e orquestração 3DS. O exemplo completo está na aba TypeScript em Snippets.
npm install @rebornpay/checkout-3dsPara hosted checkout, Payment Element e headless com pk_ + clientSecret, veja o guia Reborn Elements.
Fluxo completo no browser (JavaScript / TypeScript) e exemplos server-side (Shell, Python, Rust) para as etapas de API. Compartilhe esta seção com #snippet.
const API_BASE = "https://sandbox-api.rebornpay.io";
function collectDeviceInformation() {
const javaEnabled =
typeof navigator.javaEnabled === "function" ? navigator.javaEnabled() : false;
return {
httpBrowserLanguage: navigator.language || "pt-BR",
httpBrowserJavaEnabled: String(javaEnabled),
httpBrowserColorDepth: String(screen.colorDepth),
httpBrowserScreenHeight: String(screen.height),
httpBrowserScreenWidth: String(screen.width),
httpBrowserTimeDifference: String(new Date().getTimezoneOffset()),
userAgentBrowserValue: navigator.userAgent,
httpAcceptContent: "*/*",
ipAddress: "0.0.0.0", // seu BFF deve sobrescrever com o IP real
};
}
function waitForDeviceCollection(url, accessToken) {
return new Promise((resolve, reject) => {
const iframeName = `reborn-3ds-ddc-${crypto.randomUUID()}`;
const iframe = document.createElement("iframe");
iframe.name = iframeName;
iframe.style.cssText = "display:none;width:1px;height:1px;border:0";
const form = document.createElement("form");
form.method = "POST";
form.action = url;
form.target = iframeName;
form.style.display = "none";
const jwt = document.createElement("input");
jwt.type = "hidden";
jwt.name = "JWT";
jwt.value = accessToken;
form.appendChild(jwt);
let settled = false;
// Espera o coletor avisar "profile.completed". Remover o iframe antes cancela o 3DS Method do emissor.
const onMessage = (event) => {
if (event.source !== iframe.contentWindow) return;
let data = event.data;
try { data = typeof data === "string" ? JSON.parse(data) : data; } catch { return; }
if (data && data.MessageType === "profile.completed") finish(resolve);
};
const timer = setTimeout(() => finish(resolve), 10000);
const finish = (done) => {
if (settled) return;
settled = true;
clearTimeout(timer);
window.removeEventListener("message", onMessage);
form.remove();
iframe.remove();
done();
};
window.addEventListener("message", onMessage);
iframe.onerror = () => finish(() => reject(new Error("3DS device collection failed")));
document.body.appendChild(iframe);
document.body.appendChild(form);
form.submit();
});
}
function runChallengeStepUp(stepUpUrl, accessToken, returnUrl) {
return new Promise((resolve) => {
const iframeName = `reborn-3ds-stepup-${crypto.randomUUID()}`;
const iframe = document.createElement("iframe");
iframe.name = iframeName;
iframe.title = "Autenticação 3DS";
iframe.setAttribute("sandbox", "allow-forms allow-scripts allow-same-origin allow-pointer-lock allow-popups");
iframe.style.cssText =
"position:fixed;inset:0;width:100%;height:100%;z-index:9999;border:0;background:#fff";
const form = document.createElement("form");
form.method = "POST";
form.action = stepUpUrl;
form.target = iframeName;
form.style.display = "none";
const jwt = document.createElement("input");
jwt.type = "hidden";
jwt.name = "JWT";
jwt.value = accessToken;
form.appendChild(jwt);
const waitFor = returnUrl || window.location.href;
const target = new URL(waitFor, window.location.href);
const finish = () => { clearInterval(poll); clearTimeout(timer); form.remove(); iframe.remove(); resolve(); };
const check = () => {
try {
const current = new URL(iframe.contentWindow.location.href);
if (current.origin === target.origin && current.pathname === target.pathname) finish();
} catch { /* ainda no domínio do ACS */ }
};
const poll = setInterval(check, 400);
const timer = setTimeout(finish, 5 * 60 * 1000);
iframe.onload = check;
document.body.appendChild(iframe);
document.body.appendChild(form);
form.submit();
});
}
async function api(path, { method = "GET", body, apiKey } = {}) {
const res = await fetch(`${API_BASE}${path}`, {
method,
headers: {
"Content-Type": "application/json",
...(apiKey ? { "X-Api-Key": apiKey } : {}),
},
body: body ? JSON.stringify(body) : undefined,
});
if (!res.ok) throw new Error(await res.text());
return res.json();
}
async function payWithCard({ paymentLinkId, cardToken, buyer, returnUrl, apiKey }) {
const session = await api("/v1/checkout-sessions", {
method: "POST",
apiKey,
body: {
paymentLinkId,
customer: {
name: buyer.name,
email: buyer.email,
document: buyer.document.replace(/\D/g, ""),
},
},
});
const sessionId = session.sessionId;
const setup = await api(`/v1/checkout-sessions/${sessionId}/threeds/setup`, {
method: "POST",
apiKey,
body: { cardToken },
});
await waitForDeviceCollection(setup.deviceDataCollectionUrl, setup.accessToken);
const auth = await api(
`/v1/checkout-sessions/${sessionId}/threeds/authenticate?paymentMethod=CREDIT_CARD`,
{
method: "POST",
apiKey,
body: {
cardToken,
returnUrl,
deviceChannel: "BROWSER",
deviceInformation: collectDeviceInformation(),
billing: buyer.billing,
},
},
);
if (auth.code === "AUTHENTICATION_FAILED") throw new Error("3DS authentication failed");
// xid (Visa), directoryServerTransactionId (Mastercard) and threeDsServerTransactionId (Amex)
// are brand-specific and never come together — only cavv is guaranteed on success.
let threeDs;
if (auth.code === "AUTHENTICATION_SUCCESSFUL" && auth.cavv) {
threeDs = {
xid: auth.xid,
cavv: auth.cavv,
cavvResultCode: "2",
secureVersion: auth.specificationVersion ?? "2.1.0",
directoryServerTransactionId: auth.directoryServerTransactionId,
threeDsServerTransactionId: auth.authenticationTransactionId,
};
} else {
if (auth.stepUpUrl) await runChallengeStepUp(auth.stepUpUrl, auth.accessToken, returnUrl);
const challenge = await api(
`/v1/checkout-sessions/${sessionId}/threeds/challenge-result`,
{
method: "POST",
apiKey,
body: { authenticationTransactionId: auth.authenticationTransactionId },
},
);
threeDs = {
xid: challenge.xid,
cavv: challenge.cavv,
cavvResultCode: "2",
secureVersion: challenge.secureVersion,
directoryServerTransactionId: challenge.directoryServerTransactionId,
threeDsServerTransactionId: challenge.threeDsServerTransactionId,
};
}
return api(`/v1/checkout-sessions/${sessionId}/confirm`, {
method: "POST",
apiKey,
body: {
paymentMethod: "CREDIT_CARD",
installments: 1,
cardToken,
threeDs,
},
});
}Schemas e parâmetros em API Reference → Checkout Sessions → 3DS.
