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.

  1. POST /v1/checkout-sessions — cria sessão (público ou com API key)
  2. POST /v1/checkout-sessions/{id}/confirm com paymentMethod: PIX
  3. 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.

  1. POST /v1/checkout-sessions — cria sessão
  2. Tokenização do cartão via PCI (PAN/CVV nunca passam pela sua API)
  3. POST .../threeds/setup — retorna deviceDataCollectionUrl e accessToken
  4. POST do accessToken (campo JWT) para deviceDataCollectionUrl em iframe oculto (~2s) — coleta de device fingerprint (DDC)
  5. POST .../threeds/authenticate — envia deviceInformation + billing + deviceChannel: BROWSER
  6. Se retornar stepUpUrl, POST do accessToken (campo JWT) nesse URL no iframe do desafio — não navegar para returnUrl — e depois POST .../threeds/challenge-result
  7. POST .../confirm com cardToken e objeto threeDs (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.

Shell
npm install @rebornpay/checkout-3ds

Para 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.