Fluxo ponta a ponta¶
Esta página mostra quem fala com quem, em que ordem, e onde cada garantia (idempotência, fetch-back, reserva) acontece. Os detalhes de implementação estão em deposito.md, saque.md e webhook-e-jobs.md.
1. Componentes e fronteiras¶
flowchart LR
J["Jogador: navegador ou app"] -->|"sessão da bet, cookie HttpOnly"| F["nero_client (front)"]
F -->|"HTTPS, sem segredo no bundle"| P["nero_portal :8080"]
P -->|"X-Nero-Identity (asserção assinada)"| C["nero_payments (conector + outbox dracma_intents)"]
C <-->|"reserva, crédito, devolução"| L["nero_ledger (carteira)"]
C -->|"mTLS + HMAC + Idempotency-Key"| D["Dracma API /v1"]
D -->|"credencial de PSP, nunca sai do Dracma"| PSP["PSP PIX: MaximaxPay, Mercado Pago"]
PSP -->|"callback, termina no Dracma"| D
D -->|"webhook assinado"| CD["Caddy da bet: allowlist de IP do Dracma"]
CD -->|"POST /api/payments/dracma/webhook"| C
C -->|"fetch-back GET /v1/deposits/id, /v1/withdrawals/id"| D
| Fronteira | O que cruza | O que nunca cruza |
|---|---|---|
| Jogador → portal | sessão, valor, método, chave PIX de saque, CPF | nada do Dracma |
Portal → nero_payments |
X-Nero-Identity (quem é o jogador), corpo do pedido |
identidade vinda do corpo |
nero_payments → Dracma |
external_id, player.external_ref (opaco), documento, nome, e-mail/IP/device (risco e exigências), valor em menor unidade, destino do saque, review_requested |
senha, sessão, token da bet, dados de jogo |
Dracma → nero_payments |
objetos Deposito/Saque, BR Code, eventos assinados, reconciliação |
credencial de PSP, dados de outra operação |
2. Depósito¶
sequenceDiagram
autonumber
participant J as Jogador (front)
participant P as nero_portal
participant C as nero_payments
participant O as outbox dracma_intents
participant D as Dracma
participant S as PSP PIX
J->>P: POST /api/payments/deposits {amount: 50.00, method: pix}
P->>C: repassa + X-Nero-Identity
C->>O: grava intent (external_id = dep_uuidv7, valor em centavos, player_ref) na MESMA transação
C->>D: POST /v1/deposits (Idempotency-Key = external_id, timeout 10 s)
D->>S: cria a cobrança PIX
D-->>C: 201 pending + instructions.pix (ou 202 created)
C-->>J: 201 {deposit_id, status: pending, pix.copy_paste, expires_at}
J->>S: paga o PIX no app do banco
S-->>D: confirmação do pagamento
D->>D: confere valor e pagador (P1 a P4) e vai a paid
D->>C: webhook deposit.paid (assinado)
C->>C: verifica assinatura, janela, dedup por Dracma-Event-Id
C->>D: GET /v1/deposits/dep_... (fetch-back)
D-->>C: status paid, amount, external_id, player_ref
C->>C: confere com o intent e credita UMA vez (idempotente por external_id)
C-->>D: 200
J->>P: GET /api/payments/deposits/dep_... (polling)
P->>C: repassa
C-->>J: status paid
Pontos que o diagrama esconde:
- Se o
POST /v1/depositsder timeout,5xxou429, o intent continua no outbox e um worker repete com a mesma chave. O front recebe202 {status: creating}e faz polling. - Se o webhook se perder, o job de
/events(a cada 5 min) encontra odeposit.paide passa pelo mesmo caminho (dedup → fetch-back → crédito). - O polling do front lê o estado local do
nero_payments; não é um proxy para o Dracma.
3. Saque¶
sequenceDiagram
autonumber
participant J as Jogador (front)
participant P as nero_portal
participant C as nero_payments
participant L as nero_ledger
participant D as Dracma
participant S as PSP PIX
J->>P: POST /api/wallet/withdraw/request {amount, type: pix, pix_key, pix_type}
P->>C: repassa + X-Nero-Identity + IP do jogador
C->>L: RESERVA o valor (débito) na mesma transação do intent wdr_uuidv7
C->>D: POST /v1/withdrawals (Idempotency-Key = external_id, player.ip)
D->>D: motor de regras: automático, revisão ou bloqueio
alt bloqueio (422)
D-->>C: 422 code
C->>L: devolve a reserva
C-->>J: 200 {status: false, message} (mensagem genérica)
else automático ou revisão
D-->>C: 201 approved ou under_review
C-->>J: 200 {status: true, withdrawal_id, state: processing ou under_review}
end
D->>S: envia o PIX (automático, ou depois da aprovação no painel)
S-->>D: liquidado (E2E id)
D->>C: webhook withdrawal.paid
C->>D: GET /v1/withdrawals/wdr_... (fetch-back)
D-->>C: status paid, psp_reference
C->>L: finaliza a reserva (saída definitiva)
C-->>D: 200
- A reserva vem antes do
POSTe só volta emrejected,failed,cancelled(confirmados por fetch-back) ou em4xxconclusivo. Timeout e5xxnão devolvem reserva. under_reviewé decidido por revisores da Nero no painel do Dracma (1 ou 2 pessoas). O resultado chega por webhook comoapproved→processing→paid, ourejected.
4. O outbox dracma_intents¶
Toda chamada de dinheiro ao Dracma nasce como uma linha do outbox, gravada antes da chamada:
| Coluna sugerida | Conteúdo |
|---|---|
external_id |
dep_<uuidv7> ou wdr_<uuidv7>; também é a Idempotency-Key |
kind |
deposit ou withdrawal |
player_ref, amount_minor, currency |
o que foi pedido (é contra isso que o fetch-back confere) |
body |
os bytes exatos do corpo enviado (repetição manda os mesmos bytes) |
state |
pending → sent → acknowledged (resposta conclusiva) / rejected (4xx) / unknown (tentativas esgotadas) |
attempts, next_attempt_at, last_trace_id |
controle de retry e diagnóstico (Dracma-Trace-Id) |
dracma_id, dracma_status, dracma_version |
último estado aplicado (dep_…/wdr_…, versão monotônica) |
stateDiagram-v2
[*] --> pending: gravado na transação do pedido (e da reserva, no saque)
pending --> acknowledged: 2xx (ou Idempotent-Replayed)
pending --> rejected: 4xx conclusivo
pending --> pending: timeout, 5xx, 429, 409 idempotency_in_progress (mesma chave, backoff)
pending --> unknown: tentativas esgotadas sem resposta conclusiva
unknown --> acknowledged: GET ?external_id= achou o objeto
unknown --> needs_decision: GET ?external_id= inconclusivo
rejected --> [*]
acknowledged --> [*]
- Backoff exponencial com jitter (1 s, 2 s, 4 s … até 60 s), respeitando
Retry-After. - Cada tentativa é uma requisição nova: timestamp, nonce e assinatura novos; mesma chave e mesmos bytes.
- Um worker que reinicia retoma do outbox com a mesma chave: nunca duplica.
- Métrica
nero_dracma_outbox_pendinge alerta com intent pendente há mais de 10 min (OPERACAO do Dracma).
5. O que o Dracma garante e o que é da Nero¶
| Garantia | Quem |
|---|---|
| Não criar duas cobranças/saques para a mesma chave | Dracma (idempotência 30 dias) — desde que a Nero repita com a mesma chave |
| Não pagar para chave PIX de terceiro, decidir automático × revisão | Dracma (motor de regras, DICT) |
Creditar uma vez, só em paid |
Nero (fetch-back + idempotência por external_id) |
| Reservar antes, devolver só em estado conclusivo | Nero |
| Não perder evento | os dois: Dracma reentrega por 72 h e mantém /events; a Nero roda o job de 5 min |
| Provar que os dois livros batem | os dois: reconciliação diária com digest assinado |