Skip to content
EntryTarget Blog
Go back
CASO-DE-USO-01 · PT

Wallets digitais: por que cada usuário merece o seu próprio subledger

Subcontas por usuário, float, settlements e yield. Uma arquitetura de ledger certa transforma cada uma dessas operações de problema em commodity.

Uma wallet digital parece simples por fora — o usuário tem um saldo, ele aumenta, diminui, ele transfere. Por dentro, é um problema de contabilidade de alta cardinalidade rodando em tempo real. Cada usuário é uma conta. Cada interação é uma transação com pelo menos dois lançamentos. E o operador precisa, a qualquer momento, ser capaz de responder três perguntas ao regulador: onde está o dinheiro, de quem é, e como ele chegou aqui.

O problema

A tentação é começar com uma tabela users que tem uma coluna balance. Funciona em protótipo, falha em produção. No momento em que duas operações mutam o saldo em paralelo, o histórico deixa de reconciliar e você descobre, de madrugada, que o saldo agregado da plataforma não bate com o saldo da conta-pool no banco parceiro.

O saldo não é a fonte da verdade. A sequência de lançamentos é. O saldo é um snapshot derivado — e se você não consegue reconstruí-lo a partir dos lançamentos, você não tem um ledger, você tem um contador com bugs.

Modelo de contas

Um bom plano de contas para wallet tem três camadas: contas de usuário (uma por wallet), contas operacionais (pool, receita, taxas) e contas de contrapartida externa (provedor PIX, parceiro de cartão). Os usuários vivem em um domínio próprio — muitas vezes milhões de contas — mas compartilham a mesma árvore de tipos.

ContaTipoNormalExemplo
user:<id>:cashPassivoCredorSaldo disponível do usuário
user:<id>:pendingPassivoCredorTransações em confirmação
ops:pool:pixAtivoDevedorSaldo na conta-pool BACEN
ops:revenue:feesReceitaCredorTaxas cobradas
ops:float:yieldReceitaCredorRendimento do float
ext:provider:payinsContrapartidaDevedorRecebíveis do provedor

Uma transação, quatro perspectivas

Considere um cash-in via PIX de R$ 100,00, com taxa de R$ 2,00 retida pela plataforma. Na visão do produto, “o usuário depositou cem reais”. Na visão do ledger, três lançamentos precisam acontecer atomicamente:

CASH-IN PIX · R$ 100,00 · TAXA R$ 2,00
Account Debit Credit
ops:pool:pix 100.00
user:u_42:cash 98.00
ops:revenue:fees 2.00
Totals 100 100

Somando: débitos R$ 100,00, créditos R$ 100,00. Fecha em zero. A plataforma ganhou R$ 2,00 de receita. O usuário ficou com R$ 98,00 disponíveis. O pool subiu R$ 100,00. Tudo isto em uma única transação atômica, com um único transaction_id que o compliance consegue puxar.

API da transação

POST /v1/transactions
{
"idempotency_key": "pix_in_01JCK7...",
"metadata": {
  "source": "pix.inbound",
  "end_to_end_id": "E18236120202604...",
  "user_id": "u_42"
},
"entries": [
  { "account": "ops:pool:pix",     "direction": "debit",  "amount": "100.00" },
  { "account": "user:u_42:cash",   "direction": "credit", "amount": "98.00"  },
  { "account": "ops:revenue:fees", "direction": "credit", "amount": "2.00"   }
]
}

Arquitetura no seu AWS

O runtime do EntryTarget roda em uma subnet privada dentro da sua VPC. Sua aplicação fala com ele por gRPC interno — o tráfego nunca sai da sua conta. O Postgres transacional é seu, provisionado por IaC, com backup e criptografia usando chaves do seu KMS. O provedor PIX (ou outro banco parceiro) conversa com a sua aplicação, e só com ela. O ledger nunca expõe endpoints públicos.

Invariantes que não podem falhar

InvariantePor quêOnde é checada
Σ débitos = Σ créditos por transaçãoDouble-entry; impossível de relaxarTransaction commit
Saldo do usuário ≥ 0 (salvo linhas de crédito)Não permitir saldo negativo inadvertidoPré-commit constraint
Σ saldos de usuários ≤ saldo do poolReconciliação com a conta real no banco parceiroReconciliação diária
Toda transação tem idempotency_key únicoRetries seguros em rede instávelAPI gateway + DB
Histórico é append-onlyAuditoria e complianceDB + KMS

Reconciliação

O maior buraco operacional em wallets que crescem rápido é a reconciliação entre o ledger interno e os extratos dos parceiros — BACEN, banco custodiante, bandeiras. Quando cada transação tem um external_id e um transaction_id internos amarrados, reconciliar vira uma operação de JOIN. Quando não tem, vira uma planilha.

A reconciliação diária roda uma query simples: para cada entrada no extrato externo, existe exatamente um lançamento no ledger com o mesmo end_to_end_id e o mesmo valor. Divergências entram em uma fila de exceção. Em regime, uma wallet de milhões de usuários fecha o dia com zero ou poucas dezenas de exceções — não com centenas de milhares.

Float e yield

Quando o saldo agregado dos usuários fica parado no pool overnight, ele rende. Essa receita — o float — é proprietária da plataforma, não do usuário (salvo quando você explicitamente remunera o saldo). Um ledger direito contabiliza o rendimento diariamente em ops:float:yield sem tocar no saldo dos usuários. Nada de cálculo manual em planilha no fim do mês.

Se o seu saldo agregado não reconcilia com o extrato do banco parceiro, você não tem um problema de wallet. Você tem um problema de solvência.

— Regra universal

FAQ

Preciso de double-entry mesmo se tenho uma única moeda?

Sim. O benefício não vem da multi-moeda — vem da garantia matemática de que toda transação fecha. Single-entry em saldo escalar falha silenciosamente; double-entry não tem como falhar silenciosamente.

Onde armazeno o saldo do usuário?

Você não armazena. Você deriva — SELECT SUM(amount) sobre a conta do usuário, ou uma view materializada mantida pelo ledger. Saldo é uma projeção; fonte de verdade é o lançamento.

Isso não é lento para milhões de usuários?

Um ledger moderno indexa por conta + timestamp, e materializa saldos de forma incremental. Lê em milissegundos, escreve em milissegundos. A complexidade é de operação — não de performance.

Como lidar com reversões e chargebacks?

Com uma nova transação de sentido oposto, referenciando a original via reverses_transaction_id. Nunca editando lançamentos antigos. O histórico permanece append-only e auditável.

Quando você precisa disso

  1. 1
    Sua aplicação mantém saldo em nome do usuário
    Você custodia dinheiro alheio. Regulador e usuário esperam contas claras.
  2. 2
    Você reconcilia com um ou mais parceiros externos
    Cada parceiro traz um arquivo, um extrato, um webhook. O ledger é o ponto comum.
  3. 3
    Você cobra taxas variáveis por operação
    Sem ledger, receita se perde ou se conta em dobro.
  4. 4
    Você precisa fechar o mês em dia
    Sem ledger, fechamento é reconstrução manual. Com ledger, é um relatório.
DEPLOY NO SEU AWS

Um ledger que é realmente seu.

entrytarget.com →


Previous Post
Marketplaces: split de pagamentos, escrow e payouts sem planilha