<!-- Source: https://docs.infraio.xyz/pt-BR/get-started/quickstart -->
<!-- Last updated: 2026-10-04 -->

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

> **Note:**
>
> 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](https://app.infraio.xyz) em **Developers →
> API keys** e **Developers → Webhooks**.

## 1. Instale o SDK do navegador

**npm**

```bash
npm install @lartech/infraio-checkout-js
```

**pnpm**

```bash
pnpm add @lartech/infraio-checkout-js
```

**yarn**

```bash
yarn add @lartech/infraio-checkout-js
```

**bun**

```bash
bun add @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

```ts filename="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: "buyer@example.com",
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.
```

> **Warning:**
>
> Defasagem de relógio importa. A API 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)

**Popup**

```ts filename="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),
});
```

**Redirect**

```ts filename="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: "redirect",
});
// O navegador vai para checkout.infraio.xyz; o comprador volta para a
// sua `success_url` / `cancel_url` do payload de criação da sessão.
```

**Embed**

```ts filename="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: "embed",
  container: "#checkout-container",   // o seu alvo no DOM
  onReady: () => /* iframe carregou */ {},
});
```

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

```ts filename="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](https://docs.infraio.xyz/pt-BR/webhooks/signature-verification)
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).

Em TRON Nile, Solana Devnet e TON Testnet (em breve) não há endereço de depósito: o comprador paga de uma carteira pela página de checkout. Veja [Redes com pagamento direto na carteira](https://docs.infraio.xyz/pt-BR/concepts/chains#redes-com-pagamento-direto-na-carteira).

## Próximos passos

- [Conceitos → Sessões](https://docs.infraio.xyz/pt-BR/concepts/sessions) — o modelo de dados em
  três níveis (CheckoutSession + Order + PaymentIntent).
- [Conceitos → Redes e ativos](https://docs.infraio.xyz/pt-BR/concepts/chains) — quais redes e
  tokens são suportados em live vs. teste.
- [Webhooks → Visão geral](https://docs.infraio.xyz/pt-BR/webhooks/overview) — cada tipo de
  evento e o que dispara.
- [Segurança → Chaves de API](https://docs.infraio.xyz/pt-BR/security/api-keys) — escopos,
  rotação, rate limits.
