Invocie

Tech

Progettare un'API e-invoicing multi-regione: lezioni dalle trincee

Se stai costruendo infrastruttura che serve sia paesi clearance sia post-audit, hai una sola occasione per azzeccare le astrazioni. Ecco cosa abbiamo imparato a nostre spese.

Invocie Team · 4 dicembre 2025 · 6 min di lettura


We built Invocie's compliance engine to handle MENA clearance, EU Peppol, and global post-audit out of one codebase. The architecture took three iterations to settle. These are the design choices that survived.

1. Strategy Pattern at the country level, not the region level

Region is too coarse. Saudi (clearance, ZATCA, 15%) and the UAE (interoperable, FTA, 5%) are both "MENA" but architecturally very different. We dispatch by ISO country code first, region second, with a Default 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 everywhere, never floats

We use BigInt with fixed-point scaling (10000x for 4-decimal precision) in performance paths and decimal.js where the SDK is friendlier. The only forbidden type for monetary values is JavaScript's number — we caught three rounding bugs in pre-production tracing back to a stray Number().

3. Async-by-default, sync-by-exception

The HTTP route that creates an invoice never blocks on the government API. It writes to the database, enqueues a job, and returns 201. The worker handles the gov submission with retries (exponential backoff, max 6 attempts, jitter). This insulates the user-facing API from gov-side latency, and it makes retries safe even when ZATCA has a bad afternoon.

4. Separate the canonical model from the wire format

Our internal Invoice type is the same regardless of country. Each strategy's buildArtifacts() turns it into the country-specific wire format (TLV QR, UBL 2.1 XML, or just a Default JSON). The canonical model never knows about XML. The XML never escapes the strategy. This makes adding India IRP later a one-file change.

5. Logging is part of the contract

Every gov interaction writes a ComplianceLog row with prev_hash → hash chaining. When auditors come — and they will come — you don't want to be reconstructing what happened from app logs. The DB row is the receipt.


Letture correlate

Integrating UAE E-Invoicing With Your ERP: A Technical Guide

A technical walkthrough for the Head of IT and ERP transformation lead: how UAE PINT maps onto SAP, Oracle, Microsoft Dynamics, and custom ERPs, the data you must clean first, and the integration patterns that survive production.

UBL 2.1 in parole semplici: lo standard XML dietro la fatturazione elettronica moderna

Quasi ogni grande obbligo di e-invoicing nel mondo emette UBL 2.1 sotto il cofano. Ecco un tour senza gergo delle parti che devi davvero capire.

Catene di hash per audit trail a prova di manomissione

ZATCA le impone, i revisori UE le adorano e sono economiche da implementare. Ecco come SHA-256 concatenato trasforma un registro fatture in qualcosa di cui puoi dimostrare l'integrità.

Emetti fatture conformi in ogni mercato

ZATCA, FTA, Peppol e post-audit globale — un'unica API.

Parla con il nostro team