Depósito no nero_payments¶
Implementação de POST /api/payments/deposits e GET /api/payments/deposits/{deposit_id} sobre o
POST /v1/deposits do Dracma. Referência completa da API: ../depositos.md.
1. Passo a passo do POST /api/payments/deposits¶
flowchart TD
A["Pedido do front + X-Nero-Identity"] --> B{"Corpo válido? amount em reais com até 2 casas, method = pix"}
B -->|não| B1["400 ao front"]
B -->|sim| C["Carrega o jogador pelo id da asserção: external_ref, CPF, nome, e-mail, IP"]
C --> D{"Exigências da operação (GET /v1/currencies em cache) atendidas?"}
D -->|não| D1["422 ao front: complete o cadastro (sem chamar o Dracma)"]
D -->|sim| E["Transação: grava intent dep_uuidv7 com os bytes do corpo"]
E --> F["POST /v1/deposits, Idempotency-Key = external_id, timeout 10 s"]
F -->|"201 pending"| G["Aplica o estado, devolve 201 com o pix"]
F -->|"202 created"| H["Devolve 202 status creating"]
F -->|"4xx"| I["Intent rejected, devolve o erro mapeado"]
F -->|"timeout, 5xx, 429"| J["Intent segue pending no outbox, devolve 202 creating"]
- Identidade. O jogador vem da asserção
X-Nero-Identitydo portal. Nada deplayer_id, e-mail ou CPF de outro jogador vindo do corpo. - Valor sem float. O front manda
amountem reais (50.00). Converta para centavos por texto, nunca porfloat * 100:
Entrada (amount) |
Centavos (amount.amount) |
|---|---|
50 |
"5000" |
50.5 |
"5050" |
50.00 |
"5000" |
0.01 |
"1" |
50.001, -1, 0, 1e3, "50,00" |
recuse com 400 |
centavos(numero_json_como_texto):
exigir casa com ^[0-9]{1,12}(\.[0-9]{1,2})?$ # sem sinal, sem expoente, até 2 casas
inteiro, frac = separar no "."; frac = (frac + "00")[0:2]
c = remover_zeros_a_esquerda(inteiro + frac); exigir c != "0"
devolver c # string de inteiro
Em Go, leia o corpo com json.Decoder.UseNumber(); em Node, não use JSON.parse direto no valor (leia o
número como texto ou use um parser que preserve o literal).
3. Exigências da operação. Antes de montar o corpo, confira method_details[].deposit_requirements do
GET /v1/currencies (em cache; recarregue no boot e a cada hora). Com o Mercado Pago como PSP de
depósito, o Dracma exige player.name e player.email; com a MaximaxPay, player.name. Se o jogador
não tem o campo, responda ao front pedindo para completar o cadastro, sem chamar o Dracma (economiza um
external_id recusado).
4. Documento. cpf do corpo só é aceito se for o CPF do cadastro do jogador (ou o primeiro cadastro dele).
O Dracma fixa o documento no primeiro uso: documento diferente depois → 422 document_mismatch.
5. Outbox. Na mesma transação: gere external_id = dep_<uuidv7>, serialize o corpo uma vez e grave os
bytes. A Idempotency-Key é o próprio external_id.
6. Chamada. POST /v1/deposits assinado, timeout 10 s. Trate a resposta pela tabela da seção 3.
2. Corpo enviado ao Dracma¶
{
"external_id": "dep_0192a3b4-5c6d-7e8f-9a0b-1c2d3e4f5a6b",
"player": {
"external_ref": "0192a3b4-0000-7000-8000-000000000001",
"document": { "type": "CPF", "number": "12345678909", "country": "BR" },
"name": "Jogador Teste",
"email": "jogador1@nero.test",
"ip": "203.0.113.10"
},
"amount": { "amount": "5000", "currency": "BRL" },
"method": "pix",
"expires_in_seconds": 1800
}
| Campo | Origem na Nero | Nota |
|---|---|---|
external_id |
gerado pelo nero_payments (dep_<uuidv7>) |
= deposit_id do front = Idempotency-Key |
player.external_ref |
id interno imutável do jogador (IAM da Nero) | nunca e-mail, telefone ou CPF |
player.document |
CPF do cadastro, só dígitos | só CPF/CNPJ em PIX |
player.name |
nome do cadastro | exigido nas operações reais (Mercado Pago e MaximaxPay); ≤ 120 caracteres |
player.email |
e-mail cadastrado do jogador | só quando a operação exige (ou para risco); em hml, só .test/.invalid/.example |
player.ip |
IP do jogador que o portal recebeu do Caddy | ajuda o risco; pode ser exigido por operação |
amount |
centavos da seção 1 | string de inteiro |
expires_in_seconds |
opcional (300–86400; default 1800) | o prazo efetivo vem em instructions.pix.expires_at |
accept_bonus |
não vai ao Dracma | bônus é regra da bet |
Nunca envie operation_id, status, version, paid_amount, method_options ou fx_quote_id: o Dracma
recusa campo desconhecido com 400 (exceto operation_id na raiz, que ele descarta).
3. Resposta do Dracma → resposta ao front¶
| Dracma | Outbox | Front |
|---|---|---|
201, status: pending |
acknowledged; grava dracma_id, version, instructions |
201 {deposit_id, status: "pending", amount, currency: "BRL", method: "pix", pix: {copy_paste, qr_code_png_base64?, expires_at}, expires_at} — use o expires_at do Dracma |
201, status: failed |
acknowledged (terminal) |
201 {status: "failed"} |
202, status: created |
acknowledged |
202 {deposit_id, status: "creating"}; o PIX chega por deposit.pending |
400 validation_failed com required_by_operation / reserved_domain_required / format |
rejected |
422 "complete seu cadastro" (e-mail) |
outro 400 validation_failed |
rejected + alerta (é bug do conector) |
500 genérico ao front |
400 currency_not_enabled / method_not_enabled |
rejected + alerta |
503 "depósito indisponível" |
409 duplicate_external_id |
acknowledged com o objeto de existing |
como 201/202 conforme o estado de existing |
409 idempotency_key_reused |
needs_decision + alerta (bug) |
500 genérico |
422 document_invalid / document_mismatch |
rejected |
422 "confira seu CPF" |
422 amount_below_minimum / amount_above_maximum |
rejected |
422 com o limite (leia de GET /v1/rules) |
422 player_blocked / velocity_exceeded |
rejected |
422 "não foi possível gerar o depósito agora" (genérico, sem motivo) |
429 rate_limited (10 depósitos por jogador por hora) |
pending (repete depois do Retry-After) ou rejected se a Nero preferir desistir |
429 "muitas tentativas, aguarde" |
503 / 5xx / timeout |
pending, backoff, mesma chave |
202 {status: "creating"} |
4xx não cria objeto no Dracma e não se repete. Tentar de novo depois de corrigir o cadastro = novo
external_id (novo deposit_id).
4. GET /api/payments/deposits/{deposit_id}¶
- Só o dono:
deposit_idde outro jogador →404(mesma resposta de "não existe"). - Lê o estado local e traduz pela tabela de estados-no-front.md.
- Não é proxy do Dracma. Opcional: se o intent está parado em
creating/pendinghá mais de 60 s, faça umGET /v1/deposits/{dracma_id}(no máximo a cada 10 s por depósito). Semdracma_id(resposta perdida), useGET /v1/deposits?external_id=<deposit_id>— que está no balde de listagem (5 req/s por chave): nunca por polling do front.
5. Crédito¶
O crédito acontece só no caminho do webhook//events (webhook-e-jobs.md), depois do
fetch-back, e é idempotente pelo external_id:
-- no nero_ledger, na mesma transação que marca o evento como visto e o intent como paid
INSERT INTO lancamento (origem, external_id, jogador_id, valor_minor, tipo)
VALUES ('dracma', $1, $2, $3, 'credito_deposito')
ON CONFLICT (origem, external_id, tipo) DO NOTHING; -- restrição única: 1 crédito por depósito
O valor creditado é o amount_minor do intent (o que a Nero pediu), depois de conferir que o
amount.amount do fetch-back é idêntico. Divergência → não credita, needs_decision, alerta
(nero_dracma_fetchback_mismatch_total).