Invocie

Tech

Tsara API na E-Invoicing Mai Yankuna Da Yawa: Darussa daga Filin

Idan kuna gina infrastructure mai hidima ga ƙasashen clearance da post-audit, kuna da dama ɗaya tilo na gyara abstractions. Ga abin da muka koya ta hanyar wahala.

Invocie Team · 4 Disamba, 2025 · 6 min karatu


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.


Karatu masu alaƙa

Aika invoice masu bin doka a kowace kasuwa

ZATCA, FTA, Peppol da post-audit na duniya — API ɗaya.

Yi magana da ƙungiyarmu