Modelo de dados de preços multi-moeda para impostos e faturas
Aprenda um modelo de dados multi-moeda que lida com taxas de câmbio, arredondamento, impostos e exibição localizada de faturas sem surpresas.

O que geralmente dá errado com faturas multi-moeda
Faturas multi-moeda falham de maneiras chatas e caras. Os números parecem corretos na interface, então alguém exporta um PDF, o departamento contábil importa, e os totais já não batem com os itens da fatura.
A causa raiz é simples: matemática de dinheiro não é apenas multiplicar por uma taxa de câmbio. Impostos, arredondamento e o momento exato em que você captura a taxa afetam o resultado. Se seu modelo de preços não torna essas escolhas explícitas, diferentes partes do sistema vão "ajudar" recalculando e produzir respostas diferentes.
Três visões precisam concordar, mesmo que mostrem moedas diferentes:
- Visão do cliente: preços claros na moeda do cliente, com totais que somam.
- Visão contábil: valores base consistentes para relatórios e conciliação.
- Visão de auditoria: um rastro em papel que mostra qual taxa e regras de arredondamento produziram a fatura.
Desajustes normalmente vêm de pequenas decisões tomadas em lugares diferentes. Uma equipe arredonda cada item da linha; outra arredonda apenas o total. Uma página usa a taxa atual; outra usa a taxa na data da fatura. Alguns impostos aplicam-se antes de descontos, outros depois. Alguns impostos estão incluídos no preço; outros são adicionados por cima.
Um exemplo concreto: você vende um item a 19,99 EUR, fatura em GBP e reporta em USD. Se você converte por linha e arredonda para 2 casas, pode obter um total de imposto diferente do que somar primeiro e converter uma vez. Ambas as abordagens podem ser razoáveis, mas apenas uma pode ser sua regra.
O objetivo é ter cálculos previsíveis e valores armazenados claros. Toda fatura deve responder, sem adivinhação: quais valores foram inseridos, em qual moeda foram inseridos, qual taxa foi usada (e quando), o que foi arredondado (e como) e quais regras fiscais foram aplicadas. Essa clareza mantém os totais estáveis entre UI, PDFs, exports e auditorias.
Termos-chave para concordar antes de desenhar o esquema
Antes de desenhar tabelas, certifique-se de que todos usam as mesmas palavras. A maioria dos bugs multi-moeda não é técnica, são bugs de "quisemos dizer coisas diferentes". Um esquema limpo começa com definições que produto, finanças e engenharia aceitem.
Termos de moeda que afetam seu banco de dados
Para cada fluxo de dinheiro, concorde em três moedas:
- Moeda transacional: a moeda que o cliente vê e aceita (lista de preços, carrinho, exibição da fatura).
- Moeda de liquidação: a moeda em que você realmente recebe o pagamento (o que o provedor de pagamento ou banco liquida).
- Moeda de reporte: a moeda usada em dashboards e resumos contábeis.
Defina também as unidades menores. USD tem 2 (cents), JPY tem 0, KWD tem 3. Isso importa porque armazenar "12.34" como número flutuante vai gerar deriva, enquanto armazenar um inteiro em unidades menores (como 1234 cents) permanece exato e torna o arredondamento previsível.
Termos de imposto que mudam os totais
Impostos precisam do mesmo nível de concordância. Decida se os preços são com imposto incluído (preço exibido já inclui imposto) ou sem imposto (imposto adicionado por cima). Também escolha se o imposto é calculado por item da linha (depois somado) ou por fatura (somar primeiro, depois calcular imposto). Essas escolhas afetam arredondamento e podem mudar o valor final em algumas unidades menores.
Finalmente, decida o que deve ser armazenado vs o que pode ser derivado:
- Armazene o que é legal e financeiramente importante: preços acordados, alíquotas aplicadas, totais finais arredondados e a moeda usada.
- Derive o que pode ser recalculado com segurança: strings formatadas, conversões apenas para exibição e a maioria dos cálculos intermediários.
Campos principais de dinheiro: o que armazenar e como
Comece decidindo quais números são fatos que você armazena e quais são resultados que pode recomputar. Misturar os dois é como faturas acabam mostrando um total na tela e outro diferente nos exports.
Armazene dinheiro como inteiros em unidades menores (como cents) e sempre guarde o código da moeda junto. Um valor sem sua moeda é dado incompleto. Inteiros também evitam pequenos erros de ponto flutuante que aparecem quando você soma muitas linhas.
Um padrão prático é manter tanto entradas brutas quanto saídas calculadas. Entradas explicam o que o usuário digitou. Saídas explicam o que você faturou. Quando alguém disputa uma fatura meses depois, você precisa de ambos.
Para linhas de fatura, um conjunto limpo e durável de campos fica assim:
unit_price_minor+unit_currencyquantity(euomse necessário)line_subtotal_minor(antes de imposto/desconto)line_discount_minorline_tax_minor(ou dividido por tipo de imposto)line_total_minor(valor final da linha)
Arredondamento não é apenas detalhe de UI. Persista o método de arredondamento e a precisão usada nos cálculos, especialmente se você suporta moedas com unidades menores diferentes (JPY vs USD) ou regras de arredondamento para dinheiro em espécie. Um pequeno registro de "contexto de cálculo" pode capturar calc_precision, rounding_mode e se o arredondamento ocorre por linha ou somente no total da fatura.
Mantenha formatação de exibição separada dos valores armazenados. Valores armazenados devem ser números simples e códigos; formatação (símbolos de moeda, separadores, formato localizado) pertence à camada de apresentação. Por exemplo, armazene 12345 + EUR e deixe a UI decidir se mostra "€123.45" ou "123,45 €".
Taxas de câmbio: tabelas, timestamps e rastro de auditoria
Trate taxas de câmbio como dados baseados em tempo com uma fonte clara. "Taxa de hoje" não é algo que você possa recalcular com segurança mais tarde.
Uma tabela prática de taxas geralmente inclui:
base_currency(converter de, por exemplo USD)quote_currency(converter para, por exemplo EUR)rate(quote por 1 base, armazenado como decimal de alta precisão)effective_at(timestamp em que a taxa é válida)source(provedor) esource_ref(o ID deles ou um hash do payload)
Essa informação de fonte importa em auditorias. Se um cliente contestar um valor, você pode apontar exatamente de onde o número veio.
Em seguida, escolha uma regra para qual taxa uma fatura usa e mantenha-a. Opções comuns são taxa na hora do pedido, na hora do envio ou na hora da fatura. A melhor escolha depende do seu negócio. O importante é consistência e documentação.
Qualquer que seja a regra, armazene a taxa exata usada na fatura (e muitas vezes em cada linha da fatura). Não dependa de consultar novamente depois. Adicione campos como fx_rate, fx_rate_effective_at e fx_rate_source para que a fatura possa ser reproduzida exatamente.
Para taxas ausentes (fins de semana, feriados, queda do provedor), torne o comportamento de fallback explícito. Abordagens típicas: usar a taxa anterior mais recente, bloquear emissão até a taxa estar disponível, ou permitir uma taxa manual com flag de aprovação.
Exemplo: um pedido é feito no sábado, enviado na segunda e faturado na segunda. Se sua regra é no momento da fatura mas seu provedor não publica taxas no fim de semana, você pode usar a última taxa de sexta-feira e registrar effective_at = Sexta 23:59, junto com um source_ref para rastreabilidade.
Conversão de moeda e regras de arredondamento que permanecem consistentes
Problemas de arredondamento raramente parecem bugs óbvios. Eles aparecem como diferenças de 1 cent entre o total da fatura e a soma das linhas, ou pequenas diferenças de imposto entre o que você exibe e o que o provedor de pagamento espera. Bons modelos fazem do arredondamento uma regra clara, não uma surpresa para corrigir depois.
Decida exatamente onde o arredondamento acontece
Escolha os pontos onde você permite arredondamento e mantenha todo o resto em precisão maior. Pontos comuns de arredondamento incluem:
- Extensão da linha (quantidade x preço unitário, após descontos)
- Cada valor de imposto (por linha ou por fatura, dependendo da jurisdição)
- O total final da fatura
Se você não definir esses pontos, diferentes partes do sistema vão arredondar quando for conveniente, e os totais vão divergir.
Use um modo de arredondamento único, com exceções claras para regras fiscais
Escolha um modo de arredondamento (half-up ou bankers) e aplique-o consistentemente. Half-up é mais fácil de explicar a clientes. Bankers rounding pode reduzir viés em grandes volumes. Qualquer um pode funcionar, mas sua API, UI, exports e relatórios contábeis devem usar o mesmo modo.
Mantenha precisão extra durante conversões e etapas intermediárias (por exemplo, armazene taxas FX com muitas casas decimais), então arredonde somente nos pontos escolhidos.
Descontos também precisam de uma regra única: aplicar descontos antes do imposto (comum para cupons) ou depois do imposto (às vezes exigido para certas taxas). Documente e codifique isso uma vez.
Algumas jurisdições exigem imposto arredondado por linha, por imposto ou no total da fatura. Em vez de espalhar casos pontuais pelo código, armazene uma "política de arredondamento" (por país/estado/regime fiscal) e faça os cálculos seguirem essa política.
Um verificador simples: se você reconstruir a mesma fatura amanhã com as mesmas taxas e política armazenadas, deve obter exatamente os mesmos centavos sempre.
Campos de imposto: padrões para IVA, sales tax e múltiplos impostos
Impostos se complicam rápido porque dependem de onde está o comprador, o que você vende e se preços são líquidos ou brutos. Um modelo limpo mantém impostos explícitos, não implícitos.
Deixe a base do imposto sem ambiguidade. Armazene se o preço que você está tributando é líquido (NET) ou bruto (GROSS). Depois armazene tanto a alíquota aplicada quanto o valor do imposto calculado como um snapshot, para que mudanças nas regras depois não reescrevam o histórico.
Em cada linha de fatura, um conjunto mínimo que permanece claro anos depois:
tax_basis(NET ou GROSS)tax_rate(decimal, por exemplo 0.20)taxable_amount_minor(a base que você realmente tributou)tax_amount_minortax_method(PER_LINE ou ON_SUBTOTAL)
Se mais de um imposto puder ser aplicado (por exemplo IVA mais uma sobretaxa municipal), adicione uma tabela de detalhamento separada como InvoiceLineTax com uma linha por imposto aplicado. Cada linha deve incluir um tax_code, tax_rate, taxable_amount_minor, tax_amount_minor, moeda e pistas de jurisdição usadas no cálculo (país, região e código postal quando relevante).
Mantenha um snapshot dos detalhes da regra aplicada na fatura ou na linha, como rule_version ou um blob JSON com as entradas de decisão (status fiscal do cliente, reverse charge, isenções). Se regras de IVA mudarem no ano seguinte, faturas antigas ainda devem corresponder ao que você cobrou.
Exemplo: uma assinatura SaaS vendida a um cliente na Alemanha pode aplicar 19% de IVA sobre um preço NET, mais 1% de imposto local. Armazene totais da linha como faturados e mantenha uma linha de detalhamento para cada imposto para exibição e auditoria.
Como desenhar as tabelas passo a passo
Isto é menos sobre matemática esperta e mais sobre congelar os fatos certos no momento certo. O objetivo é que uma fatura possa ser reaberta meses depois e ainda mostrar os mesmos números.
Comece decidindo onde mora a verdade para preços de produto. Muitas equipes mantêm um preço em moeda base por produto e opcionalmente adicionam overrides por mercado (por exemplo, linhas de preço separadas para USD e EUR). Seja o que for, torne explícito no esquema para não misturar "preço do catálogo" com "preço convertido".
Uma sequência simples que mantém tabelas compreensíveis:
- Produtos e preços:
product_id,price_amount_minor,price_currency,effective_from(se preços mudam ao longo do tempo). - Cabeçalhos de pedido e fatura:
document_currency,customer_locale,billing_countrye timestamps (issued_at,tax_point_at). - Itens de linha:
unit_price_amount_minor,quantity,discount_amount_minor,tax_amount_minor,line_total_amount_minor, e moeda para cada campo monetário armazenado. - Snapshot de taxa de câmbio: a taxa exata usada (
rate_value,rate_provider,rate_timestamp) referenciada do pedido ou da fatura. - Registros de detalhamento de imposto: uma linha por imposto (
tax_type,rate_percent,taxable_base_minor,tax_amount_minor) mais uma flagcalculation_method.
Não confie em recalcular depois. Quando você cria uma fatura, copie os preços unitários finais, descontos e totais para as linhas da fatura, mesmo se vieram de um pedido.
Para rastreabilidade, adicione um calculation_version (ou calc_hash) na fatura e uma pequena tabela calculation_log que registre quem disparou uma recalculação e por quê (por exemplo, "taxa atualizada antes de emitir").
Exibição localizada da fatura sem quebrar os números
Localização deve mudar como a fatura parece, não o que ela significa. Faça todos os cálculos usando valores numéricos armazenados (unidades menores ou decimais de precisão fixa), depois aplique formatação de localidade no final.
Mantenha configurações de apresentação da fatura no próprio documento, não só no perfil do cliente. Clientes mudam de país, contatos de faturamento e preferências ao longo do tempo. Uma fatura é um snapshot legal. Armazene coisas como invoice_language, invoice_locale e flags de formatação (por exemplo, se mostrar zeros finais) com o documento para que uma reimpressão em seis meses bata com o original.
Símbolos de moeda são preocupação de exibição. Alguns locais colocam o símbolo antes do valor, outros depois. Alguns exigem espaço, outros não. Trate posicionamento do símbolo, espaçamento, separador decimal e agrupamento de milhares na hora de renderizar, baseado na localidade da fatura e na moeda. Não insira símbolos em campos monetários armazenados e não reconverta strings formatadas de volta para números.
Se você precisa de relatório em uma segunda moeda (frequentemente a moeda doméstica como USD ou EUR), mostre isso explicitamente como um total secundário, não como substituto. A moeda do documento permanece a fonte legal da verdade.
Uma configuração prática para saída da fatura:
- Mostre itens e totais na moeda do documento, usando formatação da localidade da fatura.
- Opcionalmente mostre um total secundário de reporte rotulado com a fonte da taxa e timestamp.
- Mostre o detalhamento de impostos como linhas separadas (base tributável, cada imposto, total de impostos), não um único valor misturado.
- Gere PDFs e e-mails a partir dos mesmos totais armazenados para que os números não descolem.
Exemplo: um cliente francês é cobrado em CHF. A localidade da fatura usa vírgula como decimal e coloca a moeda depois do valor, mas os cálculos ainda usam valores CHF armazenados e totais de imposto armazenados. A saída formatada muda; os números não.
Erros comuns e armadilhas a evitar
A maneira mais rápida de quebrar faturas multi-moeda é tratar dinheiro como um número normal. Tipos float para preços, impostos e totais criam pequenos erros que depois aparecem como "diferença de $0.01". Armazene valores como inteiros em unidades menores (cents) ou use um tipo decimal fixo com escala clara, e use-o consistentemente.
Outra armadilha clássica é mudar o histórico por acidente. Se você recalcula uma fatura antiga com a taxa de câmbio de hoje, ou com regras fiscais atualizadas, você não tem mais o documento que o cliente viu e pagou. Faturas devem ser imutáveis: uma vez emitidas, armazene a taxa de câmbio exata, regras de arredondamento e método fiscal usados, e não recalcule os totais armazenados.
Misturar moedas dentro de um único item de linha também é um bug silencioso de esquema. Se o preço unitário está em EUR, o desconto em USD e o imposto em GBP, você não consegue explicar a matemática depois. Escolha uma moeda do documento para exibição e liquidação, e uma moeda base para reporte interno (se necessário). Todo valor armazenado deve ter uma moeda explícita.
Erros de arredondamento frequentemente vêm de arredondar demais. Se você arredonda no preço unitário, depois no total da linha, depois no imposto por linha, depois no subtotal novamente, os totais podem parar de bater com a soma das linhas.
Armadilhas comuns para vigiar:
- Usar floats para dinheiro ou taxas de câmbio sem precisão fixa
- Reexecutar conversões para faturas antigas em vez de usar taxas armazenadas
- Permitir que um item de linha contenha valores em múltiplas moedas
- Arredondar em muitos passos em vez de pontos claramente definidos
- Não armazenar timestamp da taxa, modo de arredondamento e método fiscal por documento
Exemplo: você cria uma fatura em CAD, converte um serviço cotado em EUR e depois atualiza sua tabela de taxas. Se você armazenou apenas o valor EUR e converte ao exibir, o total em CAD muda na semana seguinte. Armazene o valor EUR, a taxa FX aplicada (e tempo) e os valores CAD finais usados na fatura.
Checklist rápido antes de enviar
Antes de declarar faturas multi-moeda "prontas", faça uma rodada final focada em consistência. A maioria dos bugs aqui não é complexa. Vem de desajustes entre o que você armazena, o que você exibe e o que você soma.
Use isto como portão de release:
- Cada fatura tem exatamente uma moeda de documento no cabeçalho, e todo total armazenado na fatura está nessa moeda.
- Todo valor monetário que você armazena é um inteiro em unidades menores, incluindo totais de linha, impostos, descontos e frete.
- A fatura armazena a taxa de câmbio exata usada (como decimal preciso), mais o timestamp e a fonte da taxa.
- Regras de arredondamento são documentadas e implementadas em um lugar compartilhado.
- Se mais de um imposto pode aplicar, você armazena um detalhamento por linha (e opcionalmente por jurisdição), não apenas um único total de imposto no cabeçalho.
Depois que o esquema passar, valide a matemática como um auditor faria. Totais de fatura devem ser iguais à soma dos totais de linha armazenados e dos valores de imposto armazenados. Não recompute totais a partir de valores exibidos ou strings formatadas.
Um teste prático: pegue uma fatura com pelo menos três linhas, aplique um desconto e inclua dois impostos em uma linha. Depois imprima em outra localidade (separadores diferentes e símbolo de moeda) e confirme que os números armazenados não mudam.
Cenário de exemplo: um pedido, três moedas e impostos
Um cliente dos EUA é faturado em USD, seu fornecedor da UE o cobra em EUR e seu time financeiro reporta em GBP. É aqui que um modelo ou permanece calmo ou vira uma pilha de diferenças de 1 cent.
Pedido: 3 unidades de um produto.
- Preço ao cliente: $19.99 por unidade (USD)
- Desconto: 10% na linha
- Sales tax dos EUA: 8.25% (taxado depois do desconto)
- Custo do fornecedor: EUR 12.40 por unidade (EUR)
- Moeda de reporte: GBP
Passo a passo: o que acontece e quando converter
Escolha um momento de conversão e mantenha-o. Em muitos sistemas de faturamento, uma escolha segura é converter na hora da emissão da fatura e então armazenar a taxa exata usada.
Na criação da fatura:
- Calcule o subtotal USD da linha: 3 x 19.99 = 59.97 USD.
- Aplique o desconto: 59.97 x 10% = 5.997, arredondado para 6.00 USD.
- Linha líquida: 59.97 - 6.00 = 53.97 USD.
- Imposto: 53.97 x 8.25% = 4.452525, arredondado para 4.45 USD.
- Total: 53.97 + 4.45 = 58.42 USD.
Arredondamento ocorre somente em pontos definidos (desconto, cada valor de imposto, totais de linha). Armazene esses resultados arredondados e sempre some valores armazenados. Isso previne o clássico problema em que seu PDF mostra 58.42 mas um export recomputa 58.43.
O que você armazena para reproduzir a fatura depois
Na fatura (e nas linhas), armazene o código da moeda (USD), valores em unidades menores (cents), detalhamento de imposto por tipo, e os IDs de registro de taxa usados para converter USD para GBP para reporte. Para o custo do fornecedor, armazene o custo em EUR e seu próprio registro de taxa se você também converter custos para GBP.
O cliente vê uma fatura USD limpa (preços, desconto, imposto, total). O financeiro exporta valores USD mais os equivalentes em GBP congelados e os timestamps exatos das taxas, de modo que os números do fechamento do mês ainda batem mesmo se as taxas mudarem amanhã.
Próximos passos: implementar, testar e manter
Escreva seu esquema mínimo como um contrato curto: quais valores são armazenados (originais, convertidos, impostos), em qual moeda cada valor está, qual regra de arredondamento se aplica e qual timestamp trava uma taxa de câmbio para uma fatura. Mantenha-o chato e específico.
Antes de construir telas, escreva testes. Não teste só faturas normais. Adicione casos de borda que sejam pequenos o suficiente para expor ruído de arredondamento e grandes o suficiente para expor problemas de agregação.
Um conjunto inicial de casos de teste:
- Preços unitários minúsculos (como 0.01) multiplicados por grandes quantidades
- Descontos que criam decimais repetidos após conversão
- Mudanças de taxa entre a data do pedido e a data da fatura
- Regras fiscais mistas (imposto incluído vs imposto excluído) no mesmo tipo de fatura
- Reembolsos e notas de crédito que devem casar com o arredondamento original
Para reduzir tickets de suporte, adicione uma visão de auditoria que explique cada número em uma fatura: valores armazenados, códigos de moeda, ID e timestamp da taxa de câmbio e método de arredondamento usado. Quando alguém perguntar "por que este total é diferente?", você pode responder com fatos armazenados.
Se você está construindo uma ferramenta interna de billing, uma plataforma no-code como AppMaster (appmaster.io) pode ajudar a manter isso consistente colocando o esquema em um só lugar e a lógica de cálculo em um fluxo reutilizável, para que web e telas mobile não façam cada uma sua própria versão da matemática.
Por fim, atribua propriedade. Decida quem atualiza taxas de câmbio, quem atualiza regras fiscais e quem aprova mudanças que afetam faturas emitidas. Estabilidade é um processo, não apenas um esquema.


