Invocie

Tech

Desenhar uma API de faturação eletrónica multirregião: lições do campo

Se está a construir infraestrutura que serve países de clearance e de post-audit, só tem uma hipótese de acertar nas abstrações. Aqui fica o que aprendemos da forma difícil.

Invocie Team · 4 de dezembro de 2025 · 6 min de leitura


Construímos o motor de compliance da Invocie para tratar clearance MENA, Peppol UE e post-audit global a partir de uma única codebase. A arquitetura levou três iterações a estabilizar. Estas foram as decisões de design que sobreviveram.

1. Strategy Pattern ao nível do país, não da região

Região é granularidade demasiado grossa. Arábia Saudita (clearance, ZATCA, 15 %) e EAU (interoperable, FTA, 5 %) são ambos "MENA" mas arquiteturalmente muito diferentes. Despachamos primeiro pelo código de país ISO, depois pela região, com um Default como fallback.

function pickStrategy(t: { country_code: string; tax_region: string }) {
  if (MENA.includes(t.country_code)) return new MENAStrategy();
  if (EU.includes(t.country_code))   return new EUStrategy();
  if (t.tax_region === "MENA") return new MENAStrategy();
  if (t.tax_region === "EU")   return new EUStrategy();
  return new DefaultStrategy();
}

2. Decimal em todo o lado, nunca floats

Usamos BigInt com escalado de vírgula fixa (10000x para precisão de 4 casas decimais) nos caminhos de performance e decimal.js onde o SDK é mais amigável. O único tipo proibido para valores monetários é o number do JavaScript — apanhámos três bugs de arredondamento em pré-produção, todos a apontar para um Number() descontrolado.

3. Async por defeito, sync por exceção

A rota HTTP que cria uma fatura nunca bloqueia na API governamental. Escreve na BD, mete um job na queue e devolve 201. O worker trata da submissão à autoridade com retries (backoff exponencial, máx. 6 tentativas, com jitter). Isto isola a API voltada ao utilizador da latência do lado do governo, e mantém os retries seguros mesmo quando a ZATCA tem uma má tarde.

4. Separar o modelo canónico do formato de wire

O nosso tipo Invoice interno é o mesmo independentemente do país. O buildArtifacts() de cada strategy converte-o no formato wire específico do país (TLV QR, UBL 2.1 XML, ou apenas um JSON default). O modelo canónico nunca sabe sobre XML. O XML nunca sai da strategy. Isto faz com que adicionar o IRP indiano mais tarde seja uma mudança num único ficheiro.

5. O logging faz parte do contrato

Cada interação com o governo escreve uma linha ComplianceLog com encadeamento prev_hash → hash. Quando os auditores aparecerem — e vão aparecer — não vais querer reconstruir o que aconteceu a partir dos logs da app. A linha na BD é o recibo.


Leituras relacionadas

Integrar a faturação eletrónica dos EAU com o teu ERP: guia técnico

Um percurso técnico para o Head of IT e o líder de transformação ERP: como o UAE PINT se mapeia sobre SAP, Oracle, Microsoft Dynamics e ERPs à medida, os dados que tens de limpar primeiro, e os padrões de integração que sobrevivem em produção.

UBL 2.1 em português simples: o padrão XML por trás da faturação eletrónica moderna

Quase todos os mandatos importantes de faturação eletrónica geram UBL 2.1 por baixo. Aqui fica um passeio sem jargão pelas partes que realmente precisas perceber.

Cadeias de hash para trilhas de auditoria à prova de adulteração

A ZATCA exige-as, os auditores da UE adoram-nas e são baratas de implementar. Eis como o SHA-256 encadeado transforma um livro de faturas em algo cuja integridade pode provar.

Emita faturas conformes em todos os mercados

ZATCA, FTA, Peppol e pós-auditoria global — uma API.

Falar com a nossa equipa