Skip to Content
Comece agoraInício rápido

Início rápido

Objetivo: receber um pagamento real em modo de teste de ponta a ponta. No fim você vai ter uma sessão criada a partir do seu servidor, um checkout aberto no navegador do comprador e um webhook assinado entregue no seu localhost.

Você precisa de um par de chaves de teste (pk_test_… + sk_test_…) e de um segredo de assinatura de webhook (whsec_…). Gere as duas coisas no dashboard do lojista  em Developers → API keys e Developers → Webhooks.

1. Instale o SDK do navegador

npm install @lartech/infraio-checkout-js

2. Crie uma sessão de checkout (servidor)

As sessões são criadas a partir do seu servidor, assinadas com HMAC-SHA256 usando a sua chave secreta. Nunca coloque sk_ no navegador.

Monte a string canônica de assinatura

METHOD + "\n" + PATH + "\n" + TIMESTAMP + "\n" + BODY

Para uma requisição POST /b2b/v1/checkout-sessions/quick no unix-timestamp 1715990400 com body {...}, a entrada de assinatura é:

POST /b2b/v1/checkout-sessions/quick 1715990400 {"items":[...],"currency":"USD","success_url":"..."}

Assine com HMAC-SHA256(secret_key, signingString), com saída em hex.

Envie a requisição

server/create-session.ts
import { fetch } from "undici"; import { createHmac } from "node:crypto"; const PUBLIC_KEY = process.env.INFRAIO_PUBLIC_KEY!; // pk_test_… const SECRET_KEY = process.env.INFRAIO_SECRET_KEY!; // sk_test_… const BASE_URL = "https://api-dev.infraio.xyz"; const path = "/b2b/v1/checkout-sessions/quick"; const body = JSON.stringify({ items: [ { sku: "tee-large-blue", label: "Indigo Tee — L", unit_price: "49.00", quantity: 1, currency: "USD", image_url: "https://your-shop.test/img/tee.png", }, ], currency: "USD", customer_email: "[email protected]", success_url: "https://your-shop.test/return?status=success", cancel_url: "https://your-shop.test/return?status=cancel", external_ref: "ord_1234", // o ID do seu pedido; armazenado no order, não ecoado nos webhooks — busque via order_id expires_in: 1800, // segundos; padrão de 30 min idempotency_key: crypto.randomUUID(), // seguro para retry }); const timestamp = Math.floor(Date.now() / 1000).toString(); const signingInput = ["POST", path, timestamp, body].join("\n"); const signature = createHmac("sha256", SECRET_KEY) .update(signingInput) .digest("hex"); const res = await fetch(`${BASE_URL}${path}`, { method: "POST", headers: { "Content-Type": "application/json", "X-Client-ID": PUBLIC_KEY, "X-Timestamp": timestamp, "X-Signature": signature, }, body, }); // Toda resposta da API vem envelopada — o payload real fica em `data`. // Trate `code`/`message` no nível de cima como o marcador de resultado // (`201` / `"created"` para sucesso). const { code, message, data } = await res.json() as { code: number; message: string; data: { session_key: string; checkout_url: string; order_id: string; expires_at: string }; }; const { session_key, checkout_url, order_id, expires_at } = data; // Envie session_key + checkout_url para o navegador. order_id e expires_at // são para o seu controle interno.

Defasagem de relógio importa. O gateway rejeita requisições com um X-Timestamp mais de 5 minutos distante do horário do servidor. Sincronize seus servidores via NTP; não confie no relógio de parede de um cron rodando há tempos.

3. Abra o checkout (navegador)

checkout.ts
import { loadInfraIo } from "@lartech/infraio-checkout-js"; const sdk = await loadInfraIo(process.env.NEXT_PUBLIC_INFRAIO_PK!); sdk.checkout({ sessionId: session_key, checkoutUrl: checkout_url, mode: "popup", // padrão onReady: () => console.log("checkout interativo"), onSuccess: ({ sessionId }) => { // Só para UX — veja o aviso abaixo. O webhook é a fonte da verdade. location.assign("/thanks"); }, onCancel: () => console.log("comprador fechou o popup"), onError: (err) => console.error(err.code, err.message), });

4. Trate o webhook (servidor)

O webhook é o único sinal oficial de que o dinheiro se moveu. Callbacks no navegador (onSuccess) podem disparar sem um pagamento real sob certas condições de timing em testnet — nunca cumpra a venda a partir de um evento do navegador.

server/webhook.ts (Next.js App Router)
import { createHmac, timingSafeEqual } from "node:crypto"; const WEBHOOK_SECRET = process.env.INFRAIO_WEBHOOK_SECRET!; // whsec_… export async function POST(req: Request) { const raw = await req.text(); // bytes BRUTOS, não parseados const signature = req.headers.get("x-signature") ?? ""; const timestamp = req.headers.get("x-timestamp") ?? ""; // A assinatura é sobre `${timestamp}.${raw}` — NÃO só sobre o body. const expected = "sha256=" + createHmac("sha256", WEBHOOK_SECRET) .update(`${timestamp}.${raw}`) .digest("hex"); const a = Buffer.from(signature); const b = Buffer.from(expected); if (a.length !== b.length || !timingSafeEqual(a, b)) { return new Response("bad signature", { status: 400 }); } // O corpo HTTP É o objeto de dados do evento (sem envelope externo // estilo Stripe). O tipo do evento + ID da entrega + timestamp ficam // nos headers. const eventType = req.headers.get("x-event") ?? ""; // ex.: "payment.settled" const deliveryId = req.headers.get("idempotency-key") // espelha X-Delivery ?? req.headers.get("x-delivery") ?? ""; const payload = JSON.parse(raw); switch (eventType) { case "payment.settled": { // O payload traz order_id, payment_intent_id, tx_hash, // amount_received, confirmations, token de liquidação + rede, etc. // NÃO inclui o seu `external_ref` do order — busque do lado do // servidor via order_id se precisar fazer o mapeamento de volta. const { order_id, tx_hash, amount_received, confirmations } = payload; // Marque o pedido como pago de forma idempotente. Use deliveryId // (X-Delivery) como chave de dedup — é estável em todas as // retentativas da mesma entrega. A gente faz até 6 retentativas // (1m / 5m / 15m / 1h / 6h) em respostas que não forem 2xx. break; } case "checkout.expired": { /* TTL da sessão venceu — libere estoque */ break; } case "payment.refund.requested": { /* surface na fila de admin */ break; } default: // Compatibilidade futura: podemos adicionar eventos. Aceite e no-op. } return new Response("ok", { status: 200 }); }

Veja Webhooks → Verificação de assinatura para casos extremos (normalização do body, rotação de segredo, proteção contra replay).

5. Acione um pagamento de teste

Sessões em modo de teste liquidam contra testnets reais (Sepolia, Base Sepolia, BSC Testnet, etc.) — não existe rede fictícia. Pegue fundos de teste na torneira (faucet) que corresponde e envie para o endereço de depósito que a página de checkout mostra. O webhook dispara quando as confirmações forem atingidas (1 conf na maioria das testnets).

Próximos passos