Depósito no nero_payments¶
Para: Dev — Conferido em 2026-10-18.
Como implementar POST /api/payments/deposits e GET /api/payments/deposits/{deposit_id} sobre o POST /v1/deposits do Dracma. A referência da API está em Depósitos.
O que é¶
A rota do portal recebe o pedido do jogador em reais, converte para a menor unidade, grava o intent e chama o Dracma. Na Nero há três particularidades: a conversão do valor vindo do front, as exigências por operação (que mudam com o PSP) e o campo accept_bonus, que fica de fora da chamada.
Quando usar¶
Ao implementar ou revisar a rota de depósito do nero_payments.
Como funciona¶
Passo a passo do POST /api/payments/deposits¶
- 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 a menor unidade por texto, nunca por multiplicação de ponto flutuante (Convenções HTTP):
Entrada (amount) |
Menor unidade (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 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.
4. Documento. O 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 dá 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 de 10 segundos. Trate a resposta pela tabela abaixo.
Corpo enviado ao Dracma¶
{
"external_id": "dep_0192a3b4-5c6d-7e8f-9a0b-1c2d3e4f5a6b",
"player": {
"external_ref": "0192a3b4-0000-7000-8000-000000000001",
"document": { "type": "CPF", "number": "52998224725", "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>) |
É o deposit_id do front e a 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 ou CNPJ em PIX |
player.name |
Nome do cadastro | Exigido quando a operação pede (até 120 caracteres) |
player.email |
E-mail cadastrado do jogador | Só quando a operação exige (ou para risco). No hml, só .test, .invalid ou .example |
player.ip |
IP do jogador que o portal recebeu do Caddy | Ajuda o risco; pode ser exigido por operação |
amount |
Menor unidade, da conversão acima | String de inteiro |
expires_in_seconds |
Opcional (300 a 86400; padrão 1800) | O prazo efetivo vem em instructions.pix.expires_at |
accept_bonus |
Não vai ao Dracma | Bônus é regra da bet (Arquitetura do conector) |
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).
Resposta do Dracma e 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 ou format |
rejected |
422 "complete seu cadastro" (e-mail) |
Outro 400 validation_failed |
rejected e alerta (bug do conector) |
500 genérico ao front |
400 currency_not_enabled ou method_not_enabled |
rejected e alerta |
503 "depósito indisponível" |
409 duplicate_external_id |
acknowledged com o objeto de existing |
Como 201 ou 202, conforme o estado de existing |
409 idempotency_key_reused |
needs_decision e alerta (bug) |
500 genérico |
422 document_invalid ou document_mismatch |
rejected |
422 "confira seu CPF" |
422 amount_below_minimum ou amount_above_maximum |
rejected |
422 com o limite (leia de GET /v1/rules) |
422 player_blocked ou velocity_exceeded |
rejected |
422 "não foi possível gerar o depósito agora" (genérico, sem motivo) |
429 rate_limited (limite de 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 ou 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 usa um novo external_id, portanto um novo deposit_id.
GET /api/payments/deposits/{deposit_id}¶
- Só o dono:
deposit_idde outro jogador devolve404, a mesma resposta de "não existe". - Lê o estado local e traduz pela tabela de Estados no front. Não é proxy do Dracma.
- Opcional: se o intent está parado em
creatingoupendinghá mais de 60 segundos, faça umGET /v1/deposits/{dracma_id}(no máximo a cada 10 segundos por depósito). Semdracma_id(resposta perdida), useGET /v1/deposits?external_id=, que está no balde de listagem (5 req/s por chave): nunca por polling do front.
Crédito¶
O crédito acontece só no caminho do webhook ou do job de GET /events (Webhook, eventos e reconciliação), 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, abre needs_decision e dispara alerta (nero_dracma_fetchback_mismatch_total).
Exemplos¶
Os comandos para criar um depósito de teste e vê-lo pago estão em Primeiros passos no sandbox.
Erros e casos de borda¶
- Jogador sem e-mail cadastrado numa operação que exige: devolva "complete seu cadastro" sem chamar o Dracma, para não gastar um
external_idrecusado. - Pagamento depois de expirar ou cancelar: o fetch-back mostra
paid_amountnum estado terminal. Não credite; abraneeds_decision(D8). - Conferência por
external_iddepois de resposta perdida:GET /v1/deposits?external_id=devolve exatamente um depósito ou nenhum.
Veja também¶
- Estados no front e Saque.
- Convenções HTTP: conversão de valor.
- Dados de teste.