# Sandbox completo: PIX, custódia e cripto

Use a mesma base `https://api.luniumpay.com` com uma chave `lun_test_`. Toda operação financeira é fictícia. O QR de entrada contém `SANDBOX:NAO_PAGAVEL:`: não é um PIX bancário. Confirme o pagamento pela API de simulação. Nenhum saque é enviado à MEXC ou à blockchain.

## Testar agora

Abra [a integração guiada](https://luniumpay.com/integrar), escolha o fluxo e execute a demonstração. Pela conexão pública de IA, use `lunium_start_sandbox_demo` com `flow` igual a `custody`, `cashin`, `payout` ou `cashout`, além de um `request_id` aleatório e estável. Continue com `lunium_get_sandbox_demo`; ele pode criar o saque fictício já previsto na jornada após confirmar o crédito.

Para testar seu backend, [baixe o kit](https://docs.luniumpay.com/starter/lunium-starter.zip):

```bash
node sandbox.mjs all
# ou Python 3.9+, sem dependências:
python3 sandbox.py all
```

O kit cria uma chave em memória, confirma `GET /keys/me` antes de operar e recusa credenciais de produção. Também aceita `LUNIUM_API_KEY` contendo exclusivamente uma chave de teste. Não imprime credenciais. Selecione `custody`, `cashin`, `payout` ou `cashout` no lugar de `all` para uma jornada específica.

## Jornadas e conclusão

| Jornada | Criar | Confirmar e acompanhar | Sucesso |
|---|---|---|---|
| PIX → custódia BRL | `POST /cashin/charge`, `destino: "saldo"` | `POST /sandbox/cashin/{cashin_id}/pay`, depois `/cashin/{cashin_id}/status` e `/saldo` | `status=paid`, `settlement_status=sent`; disponível na subconta correta |
| PIX → BTC, USDC ou outra rota do catálogo de teste | `POST /cashin/charge`, `destino: "cripto"`, `asset`, `chain`, `payout_address` | Mesma confirmação de PIX e consulta de cash-in | `paid` e `sent`; hash começa com `sandbox:`, sem transação real |
| Custódia → cripto | `POST /saldo/sacar-cripto`, `amount_cents`, `tax_number`, `asset`, `chain`, `payout_address` | `/cashin/{cashin_id}/status` | `paid` e `sent`; débito inclui `fee_cents` |
| Custódia → PIX | `POST /payouts`, `amount_cents`, `pix_key` | `GET /payouts/{payout_id}` | `status=sent`; E2E fictício com ISPB `00000000` |
| Transferência interna | `POST /saldo/transferir`, `from_customer_ref`, `to_customer_ref`, `amount_cents` | `/saldo/extrato`, `/saldo/clientes`, `/saldo/consolidado` | Débito e crédito atômicos; total custodiado conservado |
| Cripto externa → PIX | `POST /cash-outs`, depois `POST /cash-outs/{cashout_id}/accept` | `GET /cash-outs/{cashout_id}` | `state=COMPLETED`; depósito também simulado |

Custódia não exige carteira fixa. O saldo é BRL. Escolha moeda, rede, endereço e memo apenas no saque. O kit percorre BTC nativo, USDC na Polygon e saque de USDC na Base; o catálogo de testes oferece outras rotas nominadas. No cash-in e saque use `chain`; no cash-out use `network`.

## Receber na custódia sem carteira

Crie a chave com `POST /keys/sandbox` e guarde `api_key` em `LUNIUM_API_KEY` somente no backend. Confirme `key.sandbox=true` em `GET /keys/me` antes das chamadas abaixo. O documento deste exemplo é sintético e serve apenas ao sandbox.

```bash
curl --fail-with-body https://api.luniumpay.com/cashin/charge \
  -H "X-API-Key: $LUNIUM_API_KEY" -H 'Content-Type: application/json' \
  -d '{"amount_cents":100000,"payer_tax_number":"12345678901","destino":"saldo","customer_ref":"cliente-teste","external_id":"sandbox-deposito-001"}'
```

Guarde `cashin_id`. Para simular o pagamento:

```bash
curl --fail-with-body -X POST "https://api.luniumpay.com/sandbox/cashin/$CASHIN_ID/pay" \
  -H "X-API-Key: $LUNIUM_API_KEY" -H 'Content-Type: application/json' -d '{}'
```

Consulte `/cashin/{cashin_id}/status` até `paid` e `sent`, geralmente cerca de 6 segundos. Depois consulte `/saldo?customer_ref=cliente-teste`. O crédito é líquido das taxas da chave; não compare o saldo com o valor bruto da cobrança. Repetir o pagamento não duplica crédito.

## Cenários determinísticos de cash-in, custódia e payout

Envie `sandbox_scenario` ao criar a operação. Consulte `GET /sandbox` para a matriz de cenários por fluxo.

| Cenário | Resultado |
|---|---|
| `success` | Sucesso; padrão quando omitido |
| `delayed` | Retenção/demora de aproximadamente 30 segundos antes da continuação |
| `held` | Somente depósito em custódia: crédito bloqueado por 30 segundos, depois disponível |
| `expired` | Cash-in expira ao tentar simular pagamento |
| `payer_mismatch` | Cash-in devolvido por divergência de pagador, sem crédito/entrega |
| `refunded` | Cash-in devolvido sem crédito; payout devolvido com estorno único |
| `failed` | Falha de pagamento; payout devolve o débito uma vez |
| `settlement_failed` | Pagamento pode estar confirmado, mas a entrega falha; saque estorna débito e taxas |
| `settlement_uncertain` | Entrega fica `incerto`; payout continua `processing`. Não estorna antes da conciliação |
| `route_unavailable` | Criação recusada, sem débito |
| `provider_unavailable` | HTTP 503 simulado, sem operação criada; enquanto o cenário continuar, a recusa continua |

Para resolver uma incerteza **somente em teste**, chame `POST /sandbox/operations/{operation_id}/resolve` com `{"outcome":"sent"}` ou `{"outcome":"failed"}`. Repetir a mesma resolução é idempotente. A rota exige uma operação incerta da própria chave. Não existe confirmação fictícia em produção.

Teste também payload inválido, mínimo por moeda/rede, saldo insuficiente, gasto concorrente, conta bloqueada, ID de outra chave e `external_id` reutilizado com parâmetros diferentes. Use uma intenção e um `external_id` estáveis para retry; parâmetros divergentes são recusados. Cobrança e saque cripto compartilham o espaço de referências. Após timeout, recupere pelo ID ou pelas listas filtradas por `external_id`.

## Cenários de cripto → PIX

O cash-out mantém os gatilhos nas duas casas decimais de `amount` (ou `brl_amount` na cotação reversa): `.00` sucesso, `.01` demora, `.02` falha, `.03` expiração, `.04` limite, `.05` lento, `.06` recusa do provedor, `.07` primeira tentativa de aceite retorna 503 e a segunda funciona, `.08` limite de pagador. Na prévia, nenhum registro é criado. Nunca envie cripto ao endereço devolvido pelo simulador.

## Webhooks reais sobre eventos fictícios

Configure uma URL HTTPS pública que você controla em `PATCH /keys/me` com `webhook_url` e guarde `webhook_secret` com segurança. Os eventos do sandbox usam a mesma fila de entrega e assinatura do produto; levam `sandbox:true`. Valide HMAC sobre o corpo bruto, timestamp, `event_id` e tolerância a duplicatas conforme o [manual](https://docs.luniumpay.com/manual#webhooks). Não confunda evento fictício entregue via HTTP real com liquidação financeira real.

Use `GET /sandbox/events` para inspecionar eventos gerados, mesmo sem callback configurado. `GET /webhooks/deliveries` mostra tentativas e `POST /webhooks/deliveries/{event_id}/retry` reenvia. Eventos anteriores à configuração do callback não são enviados retroativamente: crie uma nova operação ou use `/webhooks/test`. Cash-in publica `cashin.paid`, `cashin.settled` ou a falha correspondente. Retenção/liberação e transferência têm seus eventos específicos. Payout publica seu resultado final; cash-out publica progressão e conclusão.

## Persistência, preços e fronteiras do teste

Cash-in, custódia, payout e transferências persistem isolados do livro financeiro real, inclusive após reinício. Cash-out também é persistido, com retenção de duas horas. Há limite de 1.000 operações por chave para as novas jornadas e 1.000 cash-outs. `POST /sandbox/reset` com `{"confirm":true}` apaga apenas o estado fictício da sua chave; entregas de webhook já enfileiradas mantêm seu histórico.

Cotações e catálogos são snapshots sintéticos identificados na resposta. Os preços, mínimos, disponibilidade e análise de risco da produção podem mudar. O sandbox não chama MEXC, bancos ou carteiras. O custo de provedor do payout de teste é fixado em 100 centavos e informado em `/saldo`; não representa a tarifa real. A escada por CPF não consulta histórico de pessoas; `/cashin/limits` informa essa diferença. Testes de retenção têm tempo comprimido.

O simulador cobre ativos nominados de seu catálogo; não simula tokens DEX arbitrários por contrato, leitura bancária de QR dinâmico, a rede blockchain nem políticas privadas de cada provedor. Em produção, leia novamente `/keys/me`, `/saldo`, limites e catálogos com a chave real. Um piloto com PIX efetivamente pago e cripto enviada pela MEXC é uma operação real, com valor e destinatário autorizados. A conclusão do sandbox valida a integração de software, não certifica liquidez ou execução dos provedores.
