Skip to content
Mohammad.
All posts

Implementing ZATCA E-Invoicing in Node.js

A practical walkthrough of Saudi ZATCA-compliant QR e-invoicing and automated PDF invoices in a Node.js commerce backend.

Node.jsZATCAE-commerce

If you're shipping commerce in Saudi Arabia, ZATCA e-invoicing isn't optional — every invoice needs a TLV-encoded QR code carrying the seller name, VAT number, timestamp, total, and VAT amount. Here's how I wired it into a Node.js backend.

TLV, then Base64

ZATCA's Phase 1 QR is a Tag-Length-Value byte string, Base64-encoded. Each field is a tag byte, a length byte, then the UTF-8 value:

function tlv(tag, value) {
  const buf = Buffer.from(value, "utf8");
  return Buffer.concat([Buffer.from([tag, buf.length]), buf]);
}
 
const qrPayload = Buffer.concat([
  tlv(1, sellerName),
  tlv(2, vatNumber),
  tlv(3, isoTimestamp),
  tlv(4, total),
  tlv(5, vatAmount),
]).toString("base64");

Render the invoice deterministically

Generate the PDF server-side from an HTML template so every invoice is reproducible and archivable. Embed the QR as a data URL and you have a self-contained, compliant document.

Make compliance a service

Don't scatter ZATCA logic across controllers. Put encoding, rendering and storage behind one InvoicingService — when Phase 2 cryptographic signing lands, you change one module, not ten.

Compliance work is rarely glamorous, but getting the primitives right once means you never think about it again.