Invocie

Tech

Eine multiregionale E-Rechnungs-API entwerfen: Lektionen aus der Praxis

Wenn Sie Infrastruktur bauen, die Clearance- und Post-Audit-Länder zugleich bedient, haben Sie genau eine Chance, die Abstraktionen richtig zu wählen. Das haben wir auf die harte Tour gelernt.

Invocie Team · 4. Dezember 2025 · 6 Min. Lesezeit


Wir haben Invocies Compliance-Engine so gebaut, dass sie MENA-Clearance, EU-Peppol und globales Post-audit aus einer Codebase bedient. Die Architektur brauchte drei Iterationen, bis sie sich gesetzt hat. Das sind die Designentscheidungen, die überlebt haben.

1. Strategy Pattern auf Länderebene, nicht Regionsebene

Region ist zu grob. Saudi-Arabien (Clearance, ZATCA, 15 %) und die VAE (interoperable, FTA, 5 %) sind beide "MENA", aber architektonisch sehr unterschiedlich. Wir dispatchen zuerst nach ISO-Ländercode, dann nach Region, mit einem Default als 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. Überall Decimal, niemals Floats

Auf performancekritischen Pfaden nutzen wir BigInt mit Festkomma-Skalierung (10000x für 4 Dezimalstellen), an anderer Stelle decimal.js, wo das SDK bequemer ist. Der einzige verbotene Typ für Geldbeträge ist JavaScripts number — wir haben drei Rundungsbugs vor der Produktion gefangen, alle ließen sich auf ein verirrtes Number() zurückführen.

3. Async by default, sync nur als Ausnahme

Die HTTP-Route, die eine Rechnung erzeugt, blockiert nie auf der Behörden-API. Sie schreibt in die DB, queued einen Job und liefert 201 zurück. Der Worker übernimmt die Übermittlung an die Behörde mit Retries (Exponential-Backoff, max. 6 Versuche, mit Jitter). Das isoliert die User-facing-API von Latenz auf Behördenseite und macht Retries auch dann sicher, wenn ZATCA einen schlechten Nachmittag hat.

4. Trennen Sie das kanonische Modell vom Wire-Format

Unser interner Invoice-Typ ist landesunabhängig derselbe. Das buildArtifacts() jeder Strategy macht daraus das landesspezifische Wire-Format (TLV-QR, UBL-2.1-XML oder einfach Default-JSON). Das kanonische Modell weiß nie etwas von XML. Das XML verlässt nie die Strategy. So wird das spätere Hinzufügen des indischen IRP zu einer Ein-Datei-Änderung.

5. Logging ist Teil des Vertrags

Jede Behörden-Interaktion schreibt eine ComplianceLog-Zeile mit prev_hash → hash-Verkettung. Wenn die Prüfer kommen — und sie kommen — wollen Sie nicht aus App-Logs rekonstruieren, was passiert ist. Die DB-Zeile ist die Quittung.


Verwandte Beiträge

Versenden Sie konforme Rechnungen in jedem Markt

ZATCA, FTA, Peppol und globales Post-Audit — eine API.

Mit unserem Team sprechen