Voltar para o blog
ArquiteturaPagamentosBanco de dados

Carteira de saldo sem furo contábil

Equipe AltiviaCloud · 6 de setembro de 2026 · 4 min de leitura

Ler o saldo, calcular na aplicação e gravar o resultado é como o dinheiro some. O saldo é consequência do extrato, não a verdade.

Sistema de saldo parece simples: uma coluna com o valor, soma quando entra, subtrai quando sai. É assim que quase todo mundo começa, e é assim que aparece o primeiro furo — dinheiro que sumiu, ou saldo que apareceu do nada, e ninguém consegue explicar de onde veio.

O erro que gera furo

UPDATE carteiras SET saldo = 42.50 WHERE id = ...

O problema é ler o saldo, calcular na aplicação e gravar o resultado. Entre a leitura e a escrita, outra operação pode ter mexido no mesmo valor — e ela é sobrescrita.

Dois pedidos simultâneos de dez reais, saldo de cem: ambos leem cem, ambos calculam noventa, ambos gravam noventa. Saiu vinte, o saldo caiu dez.

A correção é deixar o banco fazer a conta:

UPDATE carteiras SET saldo = saldo - 10 WHERE id = ... AND saldo >= 10

Duas coisas mudam. A subtração acontece no banco, sobre o valor atual. E a condição de saldo suficiente faz parte da mesma operação — não é uma verificação anterior que pode ficar desatualizada.

Se essa operação afetar zero linhas, o saldo era insuficiente. É o retorno, não uma leitura posterior, que decide.

O saldo é derivado, não a verdade

O modelo que sobrevive a auditoria tem duas peças:

Um extrato imutável. Cada movimento é uma linha nova: tipo, valor, momento, e o que originou. Nada é atualizado nem apagado.

O saldo como consequência. Ou é a soma do extrato, ou é uma coluna mantida em sincronia — mas o extrato é quem manda.

Isso muda o que acontece quando alguém pergunta "por que meu saldo está diferente do que eu esperava". Com só a coluna, a resposta é um encolher de ombros. Com o extrato, você mostra linha por linha.

E permite a conferência que revela furo: somar o extrato e comparar com a coluna. Se divergiu, você tem um defeito — e descobre antes do cliente.

Todo movimento precisa de origem

Cada linha do extrato aponta para o que a causou: o pedido, a cobrança, o estorno. Sem essa ligação, você tem um débito de três reais sem saber de onde veio.

É também o que permite responder à pergunta que mais chega no suporte: "cobraram duas vezes". Com a origem registrada, você vê em segundos se são dois movimentos do mesmo pedido — que é defeito — ou dois pedidos distintos — que não é.

Crédito duplicado: o furo mais caro

O aviso de pagamento chega mais de uma vez. Todo provedor entrega ao menos uma vez, nenhum entrega exatamente uma vez.

A guarda que parece resolver e não resolve:

if (cobranca.status !== 'PAGA') { creditar(); marcarComoPaga(); }

Dois avisos que chegam ao mesmo tempo passam os dois pela verificação antes de qualquer um marcar. Crédito em dobro.

O que resolve é fazer a transição de status ser a primeira operação, condicional, dentro da mesma transação:

UPDATE cobrancas SET status = 'PAGA'
WHERE id = ... AND status <> 'PAGA'

Quem afetar uma linha ganhou a corrida e credita. Quem afetar zero desiste em silêncio. O banco decide, não a aplicação.

Estorno é lançamento, não subtração

A tentação é apagar o débito ou somar de volta na coluna. Nos dois casos o histórico deixa de contar o que aconteceu.

Estorno é uma linha nova, positiva, apontando para o movimento original. O extrato passa a mostrar o débito e a devolução — que é exatamente o que o cliente quer ver quando reclama.

Trabalhe com inteiros

Valor monetário em ponto flutuante acumula erro. 0.1 + 0.2 não dá 0.3, e isso vira centavo perdido em volume.

Duas saídas: tipo decimal do banco, ou guardar em centavos como inteiro. As duas funcionam; misturar as duas não.

O cuidado extra é na conversão para exibição, que é onde o arredondamento entra — e onde a soma da tela pode deixar de bater com a soma real.

Três conferências que valem a pena

Extrato contra coluna. Rodando periodicamente, alerta quando divergir.

Movimento sem origem. Deveria ser sempre zero.

Saldo negativo. Se a condição de saldo suficiente está correta, é impossível. Se aparecer, existe um caminho de débito que não passa por ela.

As três são consultas simples e pegam o problema antes do cliente.

O outro lado disso, do ponto de vista do meio de pagamento, está em Pix como meio de pagamento de API, e o mecanismo de defesa contra aviso repetido, em idempotência em webhook de pagamento.

Na AltiviaCloud o saldo segue essas regras: o débito é condição do próprio UPDATE, e o pedido que não recebe SMS é estornado sozinho. Como a cobrança funciona está em preços.