Pular para o conteúdo

Começar

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

{
  "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.

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.

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:

curl -X POST "$SANDBOX_CONFIRM_URL"
# -> 200  { "status": "confirmed" }

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

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.

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.

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.

Como saber que pagou

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

sandboxproduçã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:

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

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.