Armazene dinheiro em centavos inteiros para evitar bugs de cobrança em protótipos
Armazene valores em centavos inteiros para evitar erros de arredondamento, defina regras de moeda e evite disputas de cobrança quando seu protótipo virar produto.

Por que protótipos erram ao lidar com dinheiro
Um protótipo pode parecer perfeito na tela e ainda assim cobrar o valor errado. O rótulo mostra $19.99, o carrinho exibe $39.98, mas a confirmação do pagamento volta como $39.97 ou $39.99. Ninguém percebe isso nos testes, e então usuários reais percebem.
Isso geralmente acontece porque o protótipo trata dinheiro como qualquer outro número. Ele soma, divide e aplica porcentagens usando decimais que nem sempre são representados exatamente. A interface arredonda de um jeito, o backend arredonda de outro, e o provedor de pagamento pode arredondar de novo de forma diferente. Essas pequenas diferenças viram mensagens de “Por que fui cobrado a mais?”.
Um centavo parece inofensivo, mas se acumula:
- Chamados de suporte e reembolsos consomem tempo
- Chargebacks trazem taxas e risco para a conta
- Contabilidade fica bagunçada quando totais não batem com relatórios
- Confiança cai quando recibos e telas discordam
O objetivo é previsibilidade chata: as mesmas entradas devem produzir os mesmos totais toda vez, no carrinho, no checkout, no recibo, nas faturas e nos reembolsos.
Você não precisa de um sistema financeiro complexo para isso. Precisa de algumas escolhas práticas: armazenar valores com segurança (o padrão mais simples é centavos como inteiros), decidir onde o arredondamento é permitido e concordar com as regras de moeda antes de lançar.
O problema oculto dos decimais e da aritmética de ponto flutuante
Os preços parecem simples na tela: $9.99, $19.00, $0.50. O problema é que muitas linguagens de programação armazenam esses valores como números de ponto flutuante. Ponto flutuante foi feito para velocidade, não para matemática exata com dinheiro. Alguns valores decimais não podem ser representados perfeitamente em binário, então o valor armazenado acaba ligeiramente errado.
É assim que $9.99 pode virar silenciosamente algo como 9.9899999997 ou 9.9900000003 dentro do seu app. Normalmente você não nota quando imprime um único número porque a formatação arredonda para exibição. Mas o erro pequeno continua lá.
Esses erros aparecem em operações comuns de cobrança:
- Somar muitas linhas
- Aplicar imposto (multiplicar, depois arredondar)
- Aplicar descontos percentuais
- Dividir ou ratear cobranças
Um cenário comum: um carrinho totaliza $49.95 (cinco itens a $9.99). A UI mostra $49.95. Depois você adiciona 8.25% de imposto. Se os valores subjacentes estiverem ligeiramente errados, o imposto pode arredondar de forma diferente dependendo de como você calcula (por item vs no total). O cliente vê um total, mas o processador de pagamento recebe outro, às vezes com diferença de $0.01.
Essa discrepância é onde começam as disputas. Usuários não se importam que a diferença seja “só arredondamento”. Eles se importam que a tela do checkout, o recibo e a cobrança no cartão não batam.
Decida a moeda e as regras de arredondamento antes de escrever código
Muitos bugs de cobrança não vêm de “má matemática”. Acontecem porque ninguém concordou com as regras.
Comece escolhendo uma moeda de precificação. Para testes iniciais, uma moeda geralmente basta. Você ainda pode mostrar um símbolo na UI, mas o produto deve ter uma moeda oficial para preços armazenados e cobranças.
Depois decida quando o arredondamento é permitido. Arredondar cada item pode produzir um resultado diferente de arredondar apenas no total final, especialmente com descontos e impostos. Qualquer abordagem pode funcionar, mas você precisa de uma só abordagem em todo lugar.
Um pequeno conjunto de regras evita a maioria das disputas:
- Uma moeda de precificação para preços armazenados
- Um momento único de arredondamento (por item ou no total final)
- Uma ordem fixa de operações (por exemplo: descontos, depois impostos, depois taxas)
- Uma promessa clara sobre exibição vs cobrança (o que o usuário vê deve bater com o que você envia ao processador)
- Uma fonte única de verdade quando os números divergirem (seus totais salvos ou o total do processador)
Se depois você migrar para armazenar em centavos inteiros, essas regras dizem o que armazenar, o que calcular e com o que comparar os recibos do processador.
O modelo mais seguro: inteiros para valores, moeda como código
Se você quer que os preços se comportem igual em todos os ambientes, a escolha mais segura é simples: armazene o dinheiro como unidades menores inteiras (centavos para USD) e guarde a moeda como um código separado.
Quando você salva um valor decimal como 9.99, muitos sistemas não conseguem representá-lo exatamente. Mesmo que a UI imprima “$9.99”, o valor armazenado pode ficar ligeiramente acima ou abaixo. Essa diferença pode mudar totais depois de impostos, descontos ou cálculos repetidos. Se você armazenar 999 em vez disso, a matemática permanece exata.
Um modelo limpo de armazenamento tem duas partes:
- Amount em unidades menores (inteiro)
- Código da moeda (como “USD” ou “EUR”)
Use nomes de campo que dificultem erros:
amount_cents(inteiro)currency(string)description(opcional, para recibos e logs)created_at(para auditoria)metadata(opcional, mantido separado dos campos de dinheiro)
Mantenha strings formatadas fora do armazenamento. “$9.99” é escolha de exibição, não um valor. Formate na borda da UI usando amount + currency.
Planeje negativas cedo. Reembolsos, créditos e chargebacks são normais. Um inteiro com sinal deixa um reembolso claramente como -999 na mesma moeda.
Passo a passo: implemente precificação em centavos inteiros em um protótipo
Escolha uma moeda base e escreva-a: em que você cobra, qual símbolo mostra e como arredonda. Essa única decisão evita muitos “achamos que era USD” surpresas.
Então armazene dinheiro como centavos inteiros em todo lugar que importe. No banco de dados, mantenha amount_cents como inteiro (999 significa $9.99) e currency como um código curto como USD. No código, passe valores como inteiros também.
Um fluxo simples e previsível:
- Mantenha listas de preços em centavos (plan_price_cents = 999)
- Multiplique e some usando inteiros (quantidade, add-ons, unidades de uso)
- Aplique descontos com matemática inteira, usando uma regra de arredondamento única
- Adicione imposto e taxas usando essa mesma regra
- Salve resultados como
subtotal_cents,tax_cents,total_cents, além da moeda
Arredonde apenas onde suas regras permitem. Uma abordagem comum é calcular o subtotal em centavos, calcular o imposto a partir desse subtotal e então arredondar o imposto uma vez para centavos.
Para auditoria, registre entradas (preços, quantidades, desconto, taxa, moeda) e saídas (subtotal_cents, tax_cents, total_cents). Quando um total parecer errado, esses logs deixam o problema óbvio em vez de misterioso.
Impostos, descontos e taxas sem surpresas de arredondamento
Se você armazena preços base como centavos inteiros, a maioria dos problemas aparece depois: imposto, gorjetas, taxas e descontos percentuais. Essas etapas criam frações de centavo, então você precisa de regras consistentes.
Escolha uma regra de arredondamento e use-a em todo lugar
Escolha um método de arredondamento e mantenha-o. Duas escolhas comuns são half-up (0.5 arredonda para cima) e half-even (arredondamento bancário). Qualquer uma pode ser adequada. Misturá-las é o que causa discussões do tipo “seu recibo difere do meu”.
Também decida quando o arredondamento é permitido. Uma regra prática é: faça cálculos em unidades menores e arredonde só quando for necessário converter o resultado percentual de volta para centavos.
Descontos percentuais: evite a armadilha do centavo
Um desconto de 10% em 999 centavos é 99.9 centavos. Esse décimo de centavo precisa ir para algum lugar, e deve ir sempre do mesmo jeito.
Uma sequência confiável:
- Calcule
discount_centsa partir do subtotal original - Arredonde
discount_centsuma vez usando seu método escolhido - Subtraia
discount_centsdesubtotal_cents - Calcule imposto a partir do subtotal com desconto (se essa for sua política)
Isso evita arredondamento duplo, onde você arredonda ao longo do caminho e depois arredonda de novo no final.
Torne taxas explícitas
Taxas são mais fáceis de entender e depurar quando são campos separados, não enterradas nos totais. Use nomes claros como shipping_cents, service_fee_cents, platform_fee_cents e tip_cents. Seu recibo pode então espelhar seu banco de dados.
Se você consegue explicar cada centavo na fatura, normalmente evita disputas.
Multimoeda: o que fazer agora, o que adiar
A maioria dos protótipos não precisa de multimoeda de verdade no primeiro dia. Se seus usuários pagam em um país e você liquida em uma moeda, mantenha uma só moeda e acerte o básico. Você ainda pode mostrar uma estimativa convertida, mas trate-a apenas como exibição, não como o valor cobrado.
Se você suportar múltiplas moedas, cada valor deve vir acompanhado de um código de moeda (USD, EUR, GBP). O padrão de unidades menores inteiras ainda se aplica, mas “centavos” não são universais. Algumas moedas têm 0 unidades menores (JPY) e outras têm 3 (KWD). Então armazene:
- Quantia inteira em unidades menores
- Código da moeda
- A precisão de unidades menores da moeda (derivada do código)
Também aceite que taxas de câmbio não são reversíveis. Converter USD para EUR e voltar não vai retornar de forma confiável o mesmo valor inteiro. Isso é normal. O erro é fingir que conversões não têm perda e depois discutir o centavo faltante.
Se você suporta múltiplas moedas, documente:
- De onde vêm as taxas
- Por quanto tempo uma taxa é válida
- O que você armazena (valor cobrado, taxa usada, qualquer referência de conversão)
- Quando você converte (checkout, fatura, liquidação)
- Como você arredonda
Evite misturar moedas em um único total a menos que você também defina o passo de conversão.
Erros comuns que levam a disputas de cobrança
A maioria das disputas de cobrança começa pequena: uma tela mostra $19.99, o recibo mostra $20.00 e a cobrança no cartão é $19.98. Usuários não se importam por quê. Eles acham que parece desleixado ou desonesto.
Uma causa frequente é armazenar valores “bonitos” em vez dos valores brutos. Se você salva “$10.00” ou “10.00” (já formatado), diferentes partes do app vão reparsear, arredondar de novo ou assumir uma moeda. Uma escolha de exibição inofensiva vira totais errados.
Outra causa é ter vários lugares calculando totais. Se o carrinho arredonda cada item, a fatura arredonda o subtotal e a requisição de pagamento arredonda o total final, você pode ter três respostas diferentes.
Padrões que frequentemente criam totais desencontrados:
- Calcular totais no navegador e confiar neles sem verificação no servidor
- Gerar e-mails ou faturas a partir de um caminho de cálculo diferente do checkout
- Hardcodar um símbolo de moeda e assumir que todas as moedas se comportam como USD
- Aplicar desconto-depois-imposto em um lugar e imposto-depois-desconto em outro
- Pular testes de borda (valores muito pequenos, muitas linhas, adicionar/remover repetidamente)
Checagens rápidas antes de liberar pagamentos
Antes de colocar cartões reais por trás de um protótipo, faça uma revisão de sanidade do dinheiro. A maioria dos bugs de pagamento é inconsistência pequena que gera grande volume de suporte.
Comece pelo armazenamento: valores devem ser inteiros na menor unidade dessa moeda, incluindo reembolsos e créditos. Se você vê decimais em colunas de valor do banco, trate como sinal vermelho.
Depois verifique o tratamento de moeda: todo valor deve viajar com um código de moeda. Se uma API retorna amount: 1999 sem currency: "USD", alguém vai chutar a moeda errado mais tarde.
Por fim, escolha um dono para os totais. Uma função ou serviço calcula subtotal, imposto, descontos, taxas e total. Todo mundo lê os resultados salvos. Se a página de checkout e o handler de webhook ambos recalcularem, eles eventualmente vão discordar.
Uma lista curta que pega a maioria dos problemas:
- Valores inteiros em todo lugar que importa (incluindo reembolsos)
- Código de moeda presente em todo valor monetário
- Totais calculados em um só lugar, salvos e reutilizados
- Regras de arredondamento documentadas e cobertas por testes
- Números de fatura/recibo batendo com o valor cobrado ao centavo
Teste com casos extremos reais, não apenas exemplos de “$1.00”: descontos percentuais mais imposto, reembolsos parciais após descontos, taxas adicionadas antes vs depois do imposto e carrinhos com muitos itens pequenos.
Um exemplo realista: para onde foi um centavo
Digamos que você venda um plano de $9.99, ofereça 20% de desconto e cobre 8.25% de imposto. O plano é cobrado por 3 assentos em uma fatura.
Com centavos inteiros, cada assento é 999 centavos. A discordância vem de quando você arredonda.
Duas formas razoáveis de arredondar
Método A: arredondar cada assento depois do desconto
O desconto por assento é 20% de 999 = 199.8 centavos, arredondado para 200 centavos. Líquido por assento é 999 - 200 = 799 centavos. Para 3 assentos: 799 x 3 = 2.397 centavos ($23.97). Imposto: 2.397 x 0.0825 = 197.7525 centavos, arredondado para 198 centavos. Total: 2.397 + 198 = 2.595 centavos ($25.95).
Método B: arredondar uma vez no subtotal
Subtotal é 999 x 3 = 2.997 centavos ($29.97). Desconto é 20% de 2.997 = 599.4 centavos, arredondado para 599 centavos. Líquido: 2.997 - 599 = 2.398 centavos ($23.98). Imposto: 2.398 x 0.0825 = 197.835 centavos, arredondado para 198 centavos. Total: 2.398 + 198 = 2.596 centavos ($25.96).
Ambos os métodos são defensáveis. Eles diferem por um centavo. Se sua UI mostra um método e seu backend cobra o outro, você criou uma disputa.
Na fatura, explique em linguagem simples para que a matemática seja fácil de seguir. Para reembolsos, não recalcule. Reembolse os centavos cobrados exatamente, ou você pode criar uma discrepância depois.
Registre o suficiente para reproduzir a decisão:
- Código da moeda, taxa de imposto, taxa de desconto
- Regra de arredondamento e onde o arredondamento ocorre
- Itens de linha, quantidades e centavos cobrados finais
- Valores intermediários chave (antes e depois do arredondamento)
- IDs do processador de pagamento para cobrança e reembolso
Próximos passos: torne o tratamento de dinheiro chato e confiável
O objetivo não é código de cobrança esperto. É totais que sempre batem: carrinho, fatura, recibo, reembolsos e relatórios.
Escreva suas regras de dinheiro em um doc curto compartilhado: moedas suportadas, método de arredondamento, onde o arredondamento é permitido e ordem de operações. Depois adicione um pequeno conjunto de testes que fixem esses comportamentos, especialmente em torno de descontos pequenos, muitos itens e reembolsos parciais.
Se você está herdando um protótipo gerado por IA (especialmente de ferramentas como Lovable, Bolt, v0, Cursor ou Replit), uma rápida limpeza vale a pena antes de clientes reais usarem. Matemática em ponto flutuante frequentemente se esconde em helpers, funções de formatação da UI ou colunas de banco com precisão inconsistente.
FixMyMess (fixmymess.ai) ajuda times a transformar protótipos “pagamentos que mais ou menos funcionam” em fluxos de cobrança prontos para produção, diagnosticando a matemática, normalizando o armazenamento de dinheiro e apertando regras de arredondamento para que o valor cobrado permaneça consistente em cada etapa.