npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

zatca-qr

v1.0.2

Published

ZATCA (Saudi FATOORA) e-invoice QR: encode, decode and validate in TLV + Base64. Byte-accurate Arabic text, zero dependencies. مولّد وقارئ رمز فاتورة زاتكا.

Readme

zatca-qr

تولّد رمز QR لفواتير زاتكا السعودية وتقرأه. بدون مكتبات خارجية وبدون خادم.

npm CI dependencies license release

المشكلة

في السعودية كل فاتورة ضريبية لازم يكون عليها رمز QR. الرمز مكتوب بصيغة اسمها TLV وبعدها Base64. إذا كان الرمز غلط، نظام المشتري يرفض الفاتورة.

الخطأ المشهور هو حساب طول الحقل بعدد الحروف. لكن TLV يحسب الطول بعدد البايتات. الحرف العربي = بايتين. لذلك:

| اسم البائع | عدد الحروف | عدد البايتات | النتيجة | | --- | --- | --- | --- | | Acme Saudi | 10 | 10 | ✅ سليم | | شركة النخبة | 10 | 20 | ❌ الرمز يُرفض | | ش × 128 | 128 | 256 | ❌ أكبر من حد TLV (255) |

هذه المكتبة تحسب البايتات صح، وتقرأ الرمز لو وصلك من جهة ثانية، وتفحص الفاتورة قبل ما ترسلها.

التثبيت

npm install zatca-qr
# أو مباشرة من المستودع
npm install github:exeerkit/zatca-qr

بدون أي مكتبة خارجية. تعمل في Node 18 وأحدث، وفي المتصفح، وفي Cloudflare Workers وDeno وBun.

الاستخدام

import { encodeZatcaTlv, toZatcaAmount, validateZatcaInvoice } from 'zatca-qr'

const invoice = {
  sellerName: 'مؤسسة النخبة التجارية',
  vatNumber: '300000000000003',        // 15 رقماً، يبدأ بـ3 وينتهي بـ3
  timestamp: new Date(),               // أو نص ISO 8601 مع منطقة زمنية
  totalWithVat: toZatcaAmount(1150),   // يطلع '1150.00'
  vatTotal: toZatcaAmount(150),        // يطلع '150.00'
}

// 1) افحص الفاتورة قبل الإرسال
const report = validateZatcaInvoice(invoice)
if (!report.valid) throw new Error(report.errors[0].message)

// 2) خذ نص الرمز
const payload = encodeZatcaTlv(invoice, { strict: true })

// 3) حطه في أي مولد QR

مع React

import { QRCodeSVG } from 'qrcode.react'
import { encodeZatcaTlv, type ZatcaInvoiceData } from 'zatca-qr'

export function InvoiceQr({ invoice }: { invoice: ZatcaInvoiceData }) {
  return <QRCodeSVG value={encodeZatcaTlv(invoice)} size={220} level="M" />
}

على Cloudflare Workers

import { encodeZatcaTlv } from 'zatca-qr'

export default {
  fetch(request: Request) {
    const url = new URL(request.url)
    const payload = encodeZatcaTlv({
      sellerName: url.searchParams.get('seller') ?? '',
      vatNumber: url.searchParams.get('vat') ?? '',
      timestamp: new Date(),
      totalWithVat: url.searchParams.get('total') ?? '0.00',
      vatTotal: url.searchParams.get('vat_amount') ?? '0.00',
    })
    return Response.json({ qrPayloadBase64: payload })
  },
}

اقرأ رمز وصل إليك

تنفع لما تفحص رمز من نظام ثاني، أو تكتشف رمز مكتوب يدوي غلط.

import { decodeZatcaTlv } from 'zatca-qr'

const decoded = decodeZatcaTlv(payloadFromScanner)
console.log(decoded.phase)            // 1 أو 2
console.log(decoded.data.sellerName)  // 'مؤسسة النخبة التجارية'
console.log(decoded.fields)           // كل حقل مع طوله بالبايتات
console.log(decoded.warnings)         // حقل مكرر؟ حقل ناقص؟ حقول غريبة؟

تقرأ المكتبة أيضاً Base64 الآمن للروابط، وترد الرموز الغريبة بدون ما تكسر الباقي.

جدول الحقول

| الرقم | الحقل | النوع | المرحلة | | --- | --- | --- | --- | | 1 | اسم البائع | نص UTF-8 | الأولى (مطلوب) | | 2 | الرقم الضريبي | 15 رقماً يبدأ وينتهي بـ3 | الأولى (مطلوب) | | 3 | وقت الفاتورة | ISO 8601 مع منطقة زمنية | الأولى (مطلوب) | | 4 | الإجمالي مع الضريبة | رقم بفاصلة نقطية | الأولى (مطلوب) | | 5 | مبلغ الضريبة | رقم بفاصلة نقطية | الأولى (مطلوب) | | 6 | بصمة ملف الفاتورة | Base64 (SHA-256) | الثانية (اختياري) | | 7 | التوقيع الرقمي | Base64 (ECDSA) | الثانية (اختياري) | | 8 | المفتاح العام | Base64 | الثانية (اختياري) | | 9 | ختم الهيئة | Base64 | بعد الاعتماد فقط |

المرحلة الثانية: وش تسوي المكتبة وش ما تسوي

بصراحة: المكتبة تغلّف الحمولة وتفكها فقط. ما تحسب لك بصمة الملف، وما توقّع، وما تتصل بهيئة الزكاة. البصمة والتوقيع تجيبهم من شهادتك (CSID) وتعطيها للمكتبة:

const payload = encodeZatcaTlv({
  ...invoice,
  invoiceHash,   // بصمة ملف الفاتورة XML
  signature,     // التوقيع من شهادة CSID
  publicKey,     // المفتاح العام من نفس الشهادة
  // stamp تضيفه بعد اعتماد الهيئة
})

قاعدة بسيطة: الحقول 6 و7 و8 تجي مع بعض. وختم الهيئة ما ينقبل بدونهم.

أخطاء ترفض الفاتورة

| الغلط | الصح | | --- | --- | | وقت بدون منطقة (2026-04-18 13:30) | 2026-04-18T13:30:00+03:00 أو ...Z | | فاصلة عربية (1150,00) أو فاصلة آلاف (1,150.00) | 1150.00 | | مبلغ بدون منزلتين (115) | 115.00 استخدم toZatcaAmount | | ترتيب الحقول غلط | المكتبة ترتبها صح دايم | | رقم ضريبي من 14 رقماً | 15 رقماً يبدأ وينتهي بـ3 |

العملة في هذا الحقل ريال سعودي دايم، حتى لو الفاتورة بعملة ثانية في ملف XML.

رموز الأخطاء والتنبيهات

كل خطأ وتنبيه له رمز ثابت في code، فتقدر تتعامل معه برمجياً بدل ما تقارن نص الرسالة. الأخطاء توقف validateZatcaInvoice وترجع valid: false، والتنبيهات ترجع مع valid: true.

| الرمز | النوع | متى يظهر | وش تسوي | | --- | --- | --- | --- | | MISSING_FIELD | خطأ | حقل إلزامي فاضي أو غير موجود | مرّر الحقل الخمسة كاملة | | INVALID_FIELD | خطأ | نوع غير مدعوم، أو رقم في حقل نصي | مرّر نصاً، والمبالغ فقط تقبل رقماً | | FIELD_TOO_LONG | خطأ | الحقل أكبر من 255 بايتاً | قصّر النص، وتذكّر الحرف العربي بايتان | | VAT_NUMBER_FORMAT | خطأ | الرقم الضريبي مو 15 رقماً يبدأ وينتهي بـ3 | صحّح الرقم | | TIMESTAMP_FORMAT | خطأ | وقت بلا منطقة زمنية، أو تاريخ غير صحيح | استخدم 2026-04-18T13:30:00+03:00 أو new Date() | | AMOUNT_FORMAT | خطأ | مبلغ بفاصلة عربية أو آلاف أو نوع غلط | 1150.00 بأرقام إنجليزية ونقطة | | PHASE2_INCOMPLETE | خطأ | حقل من 6 و7 و8 ناقص، أو ختم بلا توقيع | مرّر الثلاثة مع بعض | | TRUNCATED_TLV | خطأ | الرمز مقطوع عند فك الترميز | أعد قراءة الرمز كاملاً | | INVALID_BASE64 | خطأ | حمولة فيها حروف خارج Base64 | تحقق من نص الرمز | | INVALID_TLV | خطأ | علامة صفرية أو بنية غير صحيحة | الرمز ليس رمز زاتكا | | AMOUNT_PRECISION | تنبيه | المبلغ مو بمنزلتين عشريتين | استخدم toZatcaAmount | | VAT_EXCEEDS_TOTAL | تنبيه | الضريبة أكبر من الإجمالي | راجع حساب الفاتورة | | PAYLOAD_TOO_LARGE | تنبيه | الحمولة طويلة وقد تفشل مع ماسحات الميدان | اختبر الرمز على جهاز حقيقي | | DUPLICATE_TAG | تنبيه | نفس العلامة مكررة في الرمز | ارفض الرمز أو راجع مصدره | | MISSING_REQUIRED_TAG | تنبيه | علامة إلزامية ناقصة عند القراءة | الرمز ناقص وليس فاتورة صحيحة | | UNKNOWN_TAGS | تنبيه | علامات خارج المواصفة | تظهر في unknown وتُتجاهل | | ZATCA_QR_ERROR | خطأ | الرمز الافتراضي حين لا يوجد رمز أدق | اقرأ نص الرسالة |

try {
  const payload = encodeZatcaTlv(invoice, { strict: true })
} catch (error) {
  if (error.code === 'FIELD_TOO_LONG') {
    // الاسم عربي طويل، قصّره أو راجع بيانات البائع
  }
}

جرّبها

git clone https://github.com/exeerkit/zatca-qr
cd zatca-qr
python3 -m http.server 8080
# افتح http://localhost:8080/examples/demo.html

أو افتح النسخة الجاهزة: https://exeerkit.github.io/zatca-qr/

صفحة عربية تولّد الرمز قدامك وتفككه بايت بايت، وكل شي يصير في متصفحك بدون ما يطلع حرف من جهازك.

node examples/node.mjs   # مثال سريع بدون تثبيت
node --test        # 38 اختبار بدون مكتبات

الاختبارات

تشغل بدون أي تثبيت. فيها مثال جاهز لفاتورة بسيطة تحققنا منه بايت بايت (66 بايت):

01 0A "Acme Saudi" 02 0F "300000000000003" 03 14 "2026-04-18T10:30:00Z" 04 06 "115.00" 05 05 "15.00"
→ AQpBY21lIFNhdWRpAg8zMDAwMDAwMDAwMDAwMDMDFDIwMjYtMDQtMThUMTA6MzA6MDBaBAYxMTUuMDAFBTE1LjAw

وفيه كمان اختبارات لطول الحروف العربية، والرموز المقطوعة، وBase64 الغلط، والتطابق بين الملفات.

أسئلة متكررة

تكفي للمرحلة الثانية؟ لا. هي تغلّف وتفك الرمز. التوقيع والشهادات شيء ثاني تتعامل معه بنفسك.

ليش ESM فقط؟ عشان تشتغل في المتصفح مباشرة بدون أدوات بناء. لو تستخدم CommonJS استخدم await import('zatca-qr').

ترسل بياناتي لخادم؟ لا. ما فيها أي اتصال بالنت أصلاً.

أقدر استخدمها في نظام تجاري؟ نعم، الرخصة MIT. والتأكد أن فاتورتك مطابقة هو مسؤوليتك، ولهذا موجود validateZatcaInvoice.

المراجع

الرخصة

MIT. استخدمها كيف ما تبي.


مين اللي سواها؟

هذه المكتبة جزء صغير من قالب VibeIO لبناء تطبيقات SaaS عربية. القالب يجهز لك: دخول، فوترة، دفع، بريد، وملف AGENTS.md يوجه وكيلك الذكي.

حطيناها مفتوحة المصدر لأن مشكلة رمز زاتكا تواجه كل مطور سعودي. إذا عجبك الكود هنا، الأصل كله في vibeio.dev.