> ## Documentation Index
> Fetch the complete documentation index at: https://docs.beinfi.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Testar no sandbox

> O que o sandbox é de verdade: Asaas em homologação, Pix real, e onde o loop fecha.

Sandbox **não é mock**. Suas cobranças saem pelo **Infi**, um provedor nosso, que
por baixo fala com uma conta de **homologação do Asaas** — o mesmo adapter, os
mesmos formatos de webhook, os mesmos modos de falha que produção. O que muda é a
conta, não o código.

Na prática: o campo `provider` volta `"infi"`, e tudo abaixo descreve o
comportamento do Asaas, porque é ele que está por baixo.

Isso é ótimo pra fidelidade, e a parte chata dele acabou: **você fecha o loop
sozinho**, sem a chave de ninguém. O QR que a gente devolve em sandbox é uma
página nossa de confirmação — escaneia com o celular, aperta um botão, a fatura
vira paga.

## O que vem na cobrança pix

```json theme={null}
{
  "provider": "infi",
  "method": "pix",
  "status": "pending",
  "pixPayload": "https://api-sandbox.beinfi.com/pay/{slug}/sandbox/{paymentId}",
  "pixQrImage": "iVBORw0KGgoAAAANSUhEUg…",
  "sandboxConfirmUrl": "https://api-sandbox.beinfi.com/pay/{slug}/sandbox/{paymentId}",
  "providerPixPayload": "00020101021226820014br.gov.bcb.pix2560pix-h.asaas.com/qr/…",
  "pixExpiresAt": "2026-08-20T16:30:00Z"
}
```

* `pixPayload` — **renderize como QR, igual em produção.** Em sandbox ele é a URL
  da nossa página de confirmação; em produção é o copia-e-cola EMV. Seu código de
  renderização é o mesmo nos dois: é um QR.
* `pixQrImage` — PNG em base64 já pronto, gerado do mesmo `pixPayload`. Se vier,
  use em vez de gerar o QR você mesmo.
* `sandboxConfirmUrl` — **só existe em sandbox**, e é o marcador explícito. Se
  você quer um botão "confirmar como se eu tivesse pagado" na sua tela de teste,
  cheque **este campo**. Nunca farejando se o `pixPayload` parece uma URL: em
  produção ele é EMV, e esse tipo de checagem é como uma coisa de teste vaza pro
  checkout de verdade.
* `providerPixPayload` — o EMV do Asaas, pra você ver o que produção devolveria.
  Não é pagável em app de banco: é conta de homologação.
* Se `pixPayload` vier **vazio**, a cobrança existe mas não tem como ser paga.
  Não invente um QR a partir de `id` ou `providerId`: o banco do pagador recusa,
  e a tela mente pra ele. Mostre erro e ofereça outro método.

<Warning>
  **Pix exige CPF/CNPJ.** O Asaas recusa criar o pagador sem CPF/CNPJ — sem ele a cobrança para em
  `422 customer_tax_id_required`. Colete e passe o `taxId` junto do cliente.
</Warning>

## Fechar o loop: confirmar o pagamento

Duas formas, e nenhuma precisa de chave de provedor.

**Escaneando**, que é o teste que vale — é o fluxo do seu comprador de verdade:
renderize o `pixPayload` como QR, aponte a câmera do celular, e abra. Cai numa
página nossa com o valor e um botão.

**Ou por HTTP**, pro seu CI:

```bash theme={null}
curl -X POST "$SANDBOX_CONFIRM_URL"
# -> 200  { "status": "confirmed" }
```

`$SANDBOX_CONFIRM_URL` é o `sandboxConfirmUrl` que a cobrança devolveu. No SDK:

```ts theme={null}
const pay = await infi.pay.charge({ slug, invoiceId, method: "pix" });
if (pay.sandboxConfirmUrl) await fetch(pay.sandboxConfirmUrl, { method: "POST" });
await infi.pay.waitForPaid({ slug, invoiceId });
```

Confirmar duas vezes é seguro: a segunda responde `200` e não faz nada — é uma
página que as pessoas recarregam.

Medido de ponta a ponta: confirmação → `paid` em \~3s, com o grant de download
emitido e a entrega disparada.

<Info>
  **Por baixo é o Asaas confirmando, não a gente inventando.** A nossa rota não escreve "pago" no banco. Ela chama o `confirm` de sandbox do
  provedor que criou a cobrança, e o webhook real dele volta pelo mesmo caminho que
  produção usa. É por isso que você vê `pixTransaction`, taxa descontada e data de
  crédito — a cobrança é real, só a confirmação é sua.
</Info>

<Warning>
  **Isso não existe em produção.** A rota de confirmação **não é montada** num deployment live — não é protegida, é
  ausente. E o `sandboxConfirmUrl` não vem na resposta. Se o seu código chama ela,
  ele não tem o que chamar em produção; se ele checa o campo, se comporta certo nos
  dois. É por isso que o marcador é um campo e não o formato do `pixPayload`.
</Warning>

## Como saber que pagou

Duas formas, e em sandbox só uma funciona hoje:

|                                 | sandbox              | produção   |
| ------------------------------- | -------------------- | ---------- |
| **Polling** da fatura           | ✅ funciona           | ✅ funciona |
| **Webhook** `payment.confirmed` | ❌ `503` ao registrar | ✅          |

Registrar webhook em sandbox responde
`503 secret_store_unavailable` — o cofre de segredos não está disponível pra
tenant de sandbox. Então, em sandbox, **polling é o caminho**:

```ts theme={null}
const pago = await infi.pay.waitForPaid({ slug, invoiceId });
// ou, do lado servidor:
const inv = await infi.invoices.get(invoiceId);   // status: "open" | "paid"
```

Os eventos, a verificação de assinatura e o que fazer em produção estão em
<a href="/webhooks">webhooks</a>.

## O que NÃO existe em sandbox

Coisas que respondem erro e não são bug seu:

* **`providers.*` → `404`.** Conectar PSP (BYOP) é superfície só de produção. Em
  sandbox quem processa é o **Infi** — um provedor nosso, e é o que aparece em
  `provider` nas suas cobranças. Ele roda sobre o Asaas por baixo, o que explica os
  formatos e os erros que você vê, mas a conta é nossa e não há o que conectar.
* **`webhooks.create` → `503`**, como acima.
* **Cartão pode vir desabilitado.** A resposta pública do link traz
  `cardEnabled`; se for `false`, só Pix e boleto estão disponíveis naquele tenant.
