Go-live do operador — checklist de produção

Você só recebe key_live_ depois de cumprir esta lista na sua operação de sandbox. Cada item tem uma prova: algo que você mostra (saída de teste, print de painel, log, dashboard), não uma promessa. Um item sem prova conta como não feito.

Copie a lista para o seu tracker, preencha a coluna de prova e anexe ao registro de go-live (onboarding, passo 9 — README.md).

1. Conformidade

  • [ ] Suíte de conformidade verde contra a sua operação de sandbox, rodada com a URL de webhook real do seu conector (sandbox.md). Prova: saída do go test … -v com todos os casos PASS e a data.
  • [ ] Seus clientes reproduzem os vetores de assinatura byte a byte (as três chamadas, os dois webhooks válidos e a recusa do negativo) no seu CI. Prova: teste no seu repositório, como os de exemplos/.
  • [ ] GET /v1/ping com a credencial de sandbox devolve environment: "sandbox" e o operation_id esperado.

2. Chamadas

  • [ ] Nonce por CSPRNG, 32 hex minúsculos, novo a cada requisição (inclusive em retry).
  • [ ] Timestamp em segundos; host com NTP e alerta de desvio > 5 s comparando com server_time do GET /v1/ping.
  • [ ] Timeout de cliente de 10 s.
  • [ ] Todo POST com Idempotency-Key; em depósito/saque, Idempotency-Key = external_id, gravados no seu banco antes da chamada (outbox).
  • [ ] Regra de ouro implementada: 4xx não repete; 5xx/429/rede repete com a mesma chave; code desconhecido tratado pela classe HTTP (erros-e-idempotencia.md). Prova: caso de teste com POST /sandbox/outage {"down": true} → 503 → retomada com a mesma chave sem duplicar.
  • [ ] trace_id/Dracma-Trace-Id registrado em todo log de chamada ao Dracma.

3. Webhooks

  • [ ] Verificação de assinatura sobre o corpo cru, janela de 300 s, Dracma-Timestamp = t=, aceita qualquer v1 com qualquer segredo ativo (lista para rotação), comparação em tempo constante.
  • [ ] Dracma-Event-Id = id do corpo, senão 400.
  • [ ] Dedup por Dracma-Event-Id com índice único, retenção ≥ 7 dias; duplicata → 200 sem efeito. Prova: POST /sandbox/webhooks/redeliver com um evento já processado → 200, saldo inalterado.
  • [ ] Fetch-back obrigatório antes de mover dinheiro: o crédito sai do GET /deposits/{id}, nunca do corpo do webhook. Prova: um webhook deposit.paid forjado com assinatura válida para um depósito que está pending na API não credita.
  • [ ] No fetch-back, confere external_id (é seu), amount/currency (idênticos ao registrado) e player_ref (idêntico). Divergência → não credita, marca para revisão, alerta.
  • [ ] Crédito/débito idempotente por external_id (um depósito credita uma vez, mesmo com dois eventos diferentes para o mesmo objeto).
  • [ ] Ordem não garantida: version ≤ já aplicado é ignorado (com 200).
  • [ ] Leitor tolerante: tipo de evento, status, method ou campo desconhecido → 200, grava, não move dinheiro, confere depois.
  • [ ] Responde em ≤ 10 s; trabalho pesado vai para fila local.
  • [ ] Rota de webhook sem autenticação de usuário, mas com a allowlist de IP de saída do Dracma aplicada no proxy como defesa extra (não substitui a assinatura).

4. Depósitos

  • [ ] Só paid credita, pelo valor registrado na criação (depositos.md).
  • [ ] under_review, pending, created e qualquer estado desconhecido não creditam.
  • [ ] refunded/chargeback depois de paid: política definida e escrita (recomendação: não mover automaticamente; needs_decision + alerta + fila para uma pessoa decidir).
  • [ ] Pagamento após expirar: não credita; revisão.
  • [ ] player.external_ref opaco e imutável (nunca e-mail/telefone); documento do jogador consistente entre depósito e saque.
  • [ ] Exigências por operação lidas de GET /v1/currencies (method_details[].deposit_requirements) no boot e periodicamente; os campos exigidos (ex.: player.name e player.email com Mercado Pago) vão preenchidos a partir do cadastro do jogador. Prova: depósito sem o campo exigido → 400 com reason: required_by_operation tratado como recusa (sem retry, external_id novo depois da correção).
  • [ ] Fora de produção, e-mails de jogador só de domínio reservado (.test/.invalid/.example); o sandbox recusa outros com reserved_domain_required.
  • [ ] Mostra ao jogador instructions.pix.expires_at devolvido (o PSP pode ajustar o prazo).

5. Saques

  • [ ] Reserva (débito) do saldo do jogador ANTES de POST /withdrawals, na mesma transação que grava o external_id (saques.md).
  • [ ] Devolve a reserva só em rejected, failed, cancelled (confirmados por fetch-back) ou em 4xx conclusivo na criação. Nunca em timeout/5xx sem resposta conclusiva.
  • [ ] paid finaliza e guarda psp_reference (E2E id do PIX).
  • [ ] player.ip = IP do jogador em todo saque (sem ele o saque nunca é automático: OWNERSHIP_UNVERIFIED). Prova: no sandbox, saque automático dentro de X× com player.ip → approved.
  • [ ] returned depois de paid: política definida (recomendação: não mover; needs_decision + alerta).
  • [ ] review_requested: true quando a sua pré-checagem desconfiar (você só pode tornar a decisão mais restritiva).
  • [ ] decision.reasons exibido para o seu time de suporte/risco; nada de motivo de bloqueio exibido ao jogador.
  • [ ] Prova: no sandbox, um saque acima de X× vai para under_review com AUTO_MULTIPLIER_EXCEEDED, e reject/fail devolvem a reserva exatamente uma vez.

6. Recuperação e reconciliação

  • [ ] Job de GET /events?after=<último evt> a cada 5 min, com cursor persistido, passando pelo mesmo dedup + fetch-back do webhook (webhooks.md).
  • [ ] Reconciliação diária por moeda (GET /reconciliation) com verificação do digest e da assinatura (reconciliacao.md).
  • [ ] Alerta de divergência: todo paid do Dracma tem crédito/débito seu e vice-versa; qualquer diferença abre incidente. Prova: reconciliação de um dia de sandbox comparada nos dois lados, com zero divergência (onboarding, passo 7).
  • [ ] Alerta para: webhook com assinatura inválida (pode ser ataque), 401/403 na sua saída (credencial ou IP errados), fila needs_decision parada > 4 h, job de /events atrasado, desvio de relógio.

7. Segredos e rede

  • [ ] Segredo de chamada, segredo de webhook e chave privada do mTLS no cofre; entregues ao host pela sua ferramenta de deploy; arquivo 0400 do usuário do serviço; nunca em repositório, imagem, log, variável de front (NEXT_PUBLIC_*, VITE_*) ou ticket.
  • [ ] O Dracma é chamado só do servidor. Nenhum app/navegador de jogador fala com ele.
  • [ ] Logs não registram segredos, headers Dracma-Signature completos, documento completo do jogador nem chave PIX em claro.
  • [ ] Allowlist de IP de produção cadastrada com todos os IPs de saída (cluster, NAT redundante, DR) antes do corte. Lista vazia = sem acesso.
  • [ ] Certificado mTLS de produção instalado; renovação agendada aos 60 dias (validade ≤ 90).
  • [ ] Rotação de chave (≤ 90 dias) e de segredo de webhook ensaiada no sandbox sem downtime, com o verificador aceitando dois segredos ao mesmo tempo.
  • [ ] Procedimento de revogação de emergência escrito: quem clica "Revogar agora", como a chave nova chega ao conector, quanto tempo leva.

8. Ambientes

  • [ ] Homologação aponta para o sandbox com key_test_; produção para produção com key_live_. Prova: chamar produção com a chave de sandbox dá 401 unknown_key.
  • [ ] Fixtures, seeds e testes usam só domínios reservados (.test, .invalid, .example) e documentos de teste; nenhum dado de jogador real fora de produção.

9. Dia do corte

  • [ ] Limites conservadores configurados na operação de produção.
  • [ ] Primeira transação real de valor mínimo (depósito e saque) acompanhada pelos dois lados, do POST ao crédito/débito e à linha da reconciliação.
  • [ ] Caixa inicial conferido em GET /balances.
  • [ ] Plano de volta: como desligar o método no seu front sem derrubar o resto do seu sistema se o Dracma ficar indisponível (o seu serviço sobe e funciona sem o Dracma; pagamentos ficam em fila/indisponíveis).
  • [ ] Registro de go-live assinado pelos responsáveis dos dois lados.