Invocie

Tech

다지역 e-invoicing API 설계: 현장에서 얻은 교훈

clearance와 post-audit 국가를 동시에 지원하는 인프라를 만든다면, 추상화를 제대로 잡을 기회는 한 번뿐입니다. 우리가 어렵게 얻은 교훈을 공유합니다.

Invocie Team · 2025년 12월 4일 · 6 분 분량


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.


관련 글

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 쉬운 말로: 현대 전자세금계산서를 떠받치는 XML 표준

세계의 거의 모든 주요 e-invoicing 의무 규정은 내부적으로 UBL 2.1을 내놓습니다. 정말 이해해야 할 부분만 전문 용어 없이 안내합니다.

변조 방지 감사 추적을 위한 해시 체인

ZATCA는 이를 의무화하고, EU 감사인들이 좋아하며, 구현 비용도 저렴합니다. 체인화된 SHA-256이 인보이스 원장을 무결성을 증명할 수 있는 무언가로 어떻게 바꾸는지 살펴봅니다.

모든 시장에서 컴플라이언트한 청구서 발행

ZATCA, FTA, Peppol과 글로벌 사후 감사 — 단일 API.

팀에 문의