هل يقبل مُحقِّق الهيئة رمزَ فاتورتك؟
مقياسٌ مفتوح يبني رموز QR فعلية بـ13 مكتبة، ثم يفكّها بايتاً بايت بفاكٍّ مستقلٍّ لا يستدعي كود أحد. 10 حالة، كلٌّ تحمل قاعدتها ومصدرَها: نصّ المواصفة، أو حسمٌ منشور في منتدى الهيئة نفسها.
من 44,915 تنزيلٍ شهري قِيست لمكتبات ZATCA على npm تذهب إلى حزمٍ تُخفق في قاعدةٍ أو أكثر. ومن 11 مكتبةً قِيست، 2 فقط اجتازت الحالات كلّها.
وأشدُّ ما وُجد أثراً: 41,195 تنزيلٍ شهري في حزمٍ تكسر ترميز الطول عند 128 بايتاً — أي عند اسمٍ يقارب 64 حرفاً عربياً، وهو طولُ اسم منشأةٍ سعودية عادي — فالحرفُ العربي بايتان والمسافةُ بايت.
In English
The ZATCA spec sentence everyone copies is “The length shall be stored in one byte.” It stops being true at 128 bytes — and Arabic is two bytes per character in UTF-8, so a 64-character Arabic company name is 128 bytes. That is an ordinary Saudi trade name, not an edge case.
91.7% of the 44,915 monthly npm downloads measured here go to packages that fail at least one rule. Of 11 third-party packages, 2 pass every case. The rule is settled on ZATCA’s own forum, not by us.
We found the same bug in our own code first, in three places including our published checker. All were fixed before anyone else was measured, and every finding was filed as a public issue with a runnable reproduction.
Read this page in English → · method and the adapters we got wrong
وكودُنا نحن كان أحدها
ذهبنا نفحص حزمة غيرنا فوجدنا أن الخطأ عندنا: كنّا نكتب الطول
في بايتٍ واحد، منقولاً حرفياً عن جملة المواصفة
The length shall be stored in one byte. وهي تبسيطٌ ينكسر
عند 128. أصلحناه في ثلاث نسخٍ عندنا — مكتبة PDF، وخادم MCP،
وأداةُ الفحص المنشورة — قبل أن نقيس أحداً. والمقياس يرفض أن يُنتج
صفحته أصلاً إن أخفق محرّكٌ من محرّكاتنا.
افحص رمزك أنت
الصق نصّ Base64 الذي يُنتجه مُشفّرك. لا يُرفع شيء — يعمل الفاكُّ نفسه الذي حكم على الجدول أدناه، في متصفّحك.
أو ثبّت واحدةً تفعلها أصلاً
الأسطر الأربعة أعلاه تكفي لمن يريد الرمز وحده. ومن يريد معه نصّاً عربياً ينجو في PDF — مبالغُ لا تنقلب وأسماءٌ لا تُبتر — فهاتان المكتبتان تفعلان الاثنين. ولم تُنشرا على npm بعد، وتُثبَّتان اليوم من إصدار:
npm install https://github.com/opusstudiohq-max/arabic-invoice-mcp/releases/download/libs-v0.2.0/fatura-zatca-0.2.0.tgz
وfatura-zatca تسحب nasq معها. وجُرّبت السلسلة من هذا العنوان
في مشروعٍ نظيف: شكلُ الطول صحيح، والاسمُ سليمٌ بعد الفكّ، والمجاميع بالهللات.
النتيجة
| المكتبة | تنزيلات شهرية قِيست 2026-09-01 |
النتيجة |
|---|---|---|
@talha7k/zatca-qr
|
1,991 |
10/10 100%
|
@talha7k/zatca
|
1,729 |
10/10 100%
|
|
فاتورة
نحن
نسختنا — أُصلحت بعد أن كشفها هذا المقياس نفسه
|
— |
10/10 100%
|
|
خادم MCP — البناء المشحون
نحن
البناء المشحون فعلاً لا نسخة المصدر. يُفحص هنا عمداً: من ينشر مقياساً يبدأ بنفسه
|
— |
10/10 100%
|
zatca-xml-js
|
21,943 |
6/10 60%
|
@axenda/zatca
|
17,376 |
6/10 60%
|
@zatca/qr
|
503 |
6/10 60%
|
zatca-sdk
يبني المرحلة الثانية فقط — تُمرَّر حقول التوقيع صورياً وتُفحص الوسوم 1-5
|
300 |
6/10 60%
|
zatca-qr-tlv
|
123 |
6/10 60%
|
zatca-qr-generator
|
104 |
6/10 60%
|
zatca-simplified-invoice-sdk
|
10 |
6/10 60%
|
@pioneersoft/zatca-einvoice
|
646 |
5/10 50%
|
zatca
منشورة أيضاً باسم @tatwerat/zatca
⚑ ترفض «2022-04-25T15:30:00Z» وتشترط كسور الثواني .000Z — تُقاد بها ليُقاس الترميز
|
190 |
2/10 20%
|
ما وُجد بالضبط
zatca-xml-js
21,943 تنزيلاً شهرياً
- Q2
arabic-64-chars— لا يُفكّ: الوسم 1: بايت الطول 0x80 — الطول غير المحدَّد لا يجوز هنا - Q2
arabic-100-chars— لا يُفكّ: الوسم 1: بايت الطول 0xc8 غير صالح - Q3
arabic-128-chars— لا يُفكّ: الوسم 1: الطول المُعلن صفر — بترُ طولٍ يتجاوز 255
@axenda/zatca
17,376 تنزيلاً شهرياً
- Q2
arabic-64-chars— لا يُفكّ: الوسم 1: موضع الطول يحمل محرف الاستبدال U+FFFD - Q2
arabic-100-chars— لا يُفكّ: الوسم 1: موضع الطول يحمل محرف الاستبدال U+FFFD - Q3
arabic-128-chars— لا يُفكّ: الوسم 216: بايت الطول 0xb4 غير صالح
zatca-qr-tlv
123 تنزيلاً شهرياً
- Q2
arabic-64-chars— لا يُفكّ: الوسم 1: بايت الطول 0x80 — الطول غير المحدَّد لا يجوز هنا - Q2
arabic-100-chars— لا يُفكّ: الوسم 1: بايت الطول 0xc8 غير صالح - Q3
arabic-128-chars— استثناء: ZATCA QR field 1 is 256 bytes; the format allows at most 255
zatca-sdk
300 تنزيلاً شهرياً
- Q2
arabic-64-chars— لا يُفكّ: الوسم 1: بايت الطول 0x80 — الطول غير المحدَّد لا يجوز هنا - Q2
arabic-100-chars— لا يُفكّ: الوسم 1: بايت الطول 0xc8 غير صالح - Q3
arabic-128-chars— استثناء: TLV tag 1 value is 256 bytes; ZATCA TLV supports at most 255
zatca-qr-generator
104 تنزيلاً شهرياً
- Q2
arabic-64-chars— لا يُفكّ: الوسم 1: بايت الطول 0x80 — الطول غير المحدَّد لا يجوز هنا - Q2
arabic-100-chars— لا يُفكّ: الوسم 1: بايت الطول 0xc8 غير صالح - Q3
arabic-128-chars— لا يُفكّ: الوسم 1: الطول المُعلن صفر — بترُ طولٍ يتجاوز 255
zatca-simplified-invoice-sdk
10 تنزيلاً شهرياً
- Q2
arabic-64-chars— لا يُفكّ: الوسم 1: بايت الطول 0x80 — الطول غير المحدَّد لا يجوز هنا - Q2
arabic-100-chars— لا يُفكّ: الوسم 1: بايت الطول 0xc8 غير صالح - Q3
arabic-128-chars— استثناء: tag 1 value exceeds 255 UTF-8 bytes
@pioneersoft/zatca-einvoice
646 تنزيلاً شهرياً
- Q2
arabic-64-chars— لا يُفكّ: الوسم 1: بايت الطول 0x80 — الطول غير المحدَّد لا يجوز هنا - Q2
arabic-100-chars— لا يُفكّ: الوسم 1: بايت الطول 0xc8 غير صالح - Q3
arabic-128-chars— لا يُفكّ: الوسم 1: الطول المُعلن صفر — بترُ طولٍ يتجاوز 255
@zatca/qr
503 تنزيلاً شهرياً
- Q2
arabic-64-chars— لا يُفكّ: الوسم 1: موضع الطول يحمل محرف الاستبدال U+FFFD - Q2
arabic-100-chars— لا يُفكّ: الوسم 1: موضع الطول يحمل محرف الاستبدال U+FFFD - Q3
arabic-128-chars— لا يُفكّ: الوسم 216: بايت الطول 0xb4 غير صالح
zatca
190 تنزيلاً شهرياً
- Q1
arabic-byte-length— لا يُفكّ: الوسم 217: بايت الطول 0x83 غير صالح - Q1
arabic-63-chars— لا يُفكّ: الوسم 180: بايت الطول 0xd8 غير صالح - Q2
arabic-64-chars— لا يُفكّ: الوسم 216: بايت الطول 0xb4 غير صالح
وليست علّةَ جافاسكربت
الدرجاتُ أعلاه تقيس npm، لأنها ما يستطيع هذا المُشغِّل تشغيله. لكنّ العيب نفسه في أكثر مستودعات ZATCA نجوماً على GitHub، بثلاث لغاتٍ أخرى — ومنها مكتباتُ منصّاتٍ سعودية كبرى:
| المستودع | اللغة | سطرُ الطول |
|---|---|---|
SallaApp/ZATCA | PHP | pack("H*", sprintf("%02X", $len)) |
Saleh7/php-zatca-xml | PHP | pack("H*", sprintf("%02X", $len)) |
mrsool/zatca | Ruby | @value.bytesize.chr |
Haraj-backend/zatca-sdk-go | Go | buf.WriteByte(byte(len(val))) |
وأربعتُها تعدّ البايتات لا الأحرف — أي تجاوزت الفخّ الذي يُسقط أكثر التطبيقات — ثم كتبت ذلك العدد في بايتٍ واحد. ومكتبةُ جو تقول القاعدة الخاطئة نصّاً:
// since the length could only be 1 byte, that means the maximum length for
// every field values is 255.
const maxValueLength = 255
فالخطأ ليس زلّةً في بيئةٍ بعينها، بل قراءةٌ شائعة لجملةٍ تقول «بايتٌ واحد» — ولذلك يبلغ 91.7% من التنزيلات المقيسة، ويبلغ قمّةَ GitHub معاً.
وهذه الأربعة قُرئت ولم تُشغَّل — لا PHP ولا روبي ولا جو على الجهاز الذي بنى هذه الصفحة. والاقتباساتُ حرفيةٌ من الفرع الافتراضي لكلٍّ منها. أما درجاتُ npm أعلاه فمُشغَّلة.
ما أُبلِغ به أصحابُه
مقياسٌ يجد عيباً ولا يُبلغ صاحبه نميمةٌ مُحكمة. فكل ما وُجد هنا رُفع مسألةً عامة، بالبرهان وسطور الإصلاح — وروابطها مفتوحة للحكم.
| الحزمة | العيب | المسألة |
|---|---|---|
zatca-xml-js |
الطول في بايتٍ واحد: 128 بايتاً تُكتب 0x80، و256 بايتاً تُعلن طولاً صفراً | wes4m/zatca-xml-js/issues/61 |
@axenda/zatca |
الطول يمرّ بجولة UTF-8 فيصير محرف الاستبدال EF BF BD عند 128 بايتاً | axenda/zatca/issues/19 |
zatca |
الطول بعدد الأحرف لا البايتات — كل اسمٍ عربي يُنتج رمزاً مشوّهاً | abdo-host/ZATCA-JS/issues/1 |
zatca-qr-generator |
الطول في بايتٍ واحد؛ و256 بايتاً تُعلن طولاً صفراً بلا خطأ | Wasim-Zaman/zatca-qr-generator/issues/1 |
zatca-sdk |
الطول في بايتٍ واحد عند 128 بايتاً بلا خطأ؛ ويرفض ما فوق 255 | aashahin/zatca-sdk/issues/1 |
zatca-simplified-invoice-sdk |
الطول في بايتٍ واحد عند 128 بايتاً بلا خطأ؛ ويرفض ما فوق 255 | sharahsa0-creator/zatca-simplified-invoice-sdk/issues/6 |
SallaApp/ZATCA PHP |
pack("H*", sprintf("%02X", $len)) — بايتٌ واحد دائماً؛ وأطولُ اسمٍ في اختباراتهم ستةُ بايتات | SallaApp/ZATCA/issues/74 |
Saleh7/php-zatca-xml PHP |
الكود نفسه حرفاً بحرف — بايتٌ واحد للطول | Saleh7/php-zatca-xml/issues/38 |
mrsool/zatca Ruby |
@value.bytesize.chr — البايتاتُ صحيحة، ثم chr تكتب بايتاً واحداً | mrsool/zatca/issues/41 |
Haraj-backend/zatca-sdk-go Go |
تعليقُهم يقول «since the length could only be 1 byte» — والفجوةُ 128-255 تمرّ من حارسهم بلا اختبار | Haraj-backend/zatca-sdk-go/issues/7 |
وما تعذّر الإبلاغ عنه:
@pioneersoft/zatca-einvoice— المسائل مُعطَّلة في المستودع — لا سبيل للإبلاغ العام@zatca/qr— لا مستودع معلن في بيانات الحزمة على npmzatca-qr-tlv— لا مستودع معلن في بيانات الحزمة على npm
الحالة × المكتبة
| الحالة | القاعدة | @talha7k/zatca-qr | @talha7k/zatca | فاتورة | خادم MCP — البناء المشحون | zatca-xml-js | @axenda/zatca | @zatca/qr | zatca-sdk | zatca-qr-tlv | zatca-qr-generator | zatca-simplified-invoice-sdk | @pioneersoft/zatca-einvoice | zatca |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| ascii-baseline الحدّ الأدنى: حقول لاتينية قصيرة. من أخفق هنا لا يبني TLV أصلاً. |
Q4 | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| arabic-byte-length «شركة» أربعة أحرف وثمانِ بايتات — أشهر فخّ يُسقط التطبيقات الساذجة. |
Q1 | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✗ |
| arabic-63-chars 126 بايتاً — تحت الحدّ مباشرة، فبايتٌ واحد يحمله. |
Q1 | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✗ |
| arabic-64-chars 128 بايتاً بالضبط — هنا ينكسر كل من قرأ نصّ المواصفة حرفياً. واسمُ منشأةٍ سعودية من 64 حرفاً عاديّ تماماً. |
Q2 | ✓ | ✓ | ✓ | ✓ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ |
| arabic-100-chars 200 بايتاً — داخل مدى 0x81 وبعيداً عن الحدّ، فلا يُنجي من الخطأ حظُّ الحدود. |
Q2 | ✓ | ✓ | ✓ | ✓ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ |
| arabic-128-chars 256 بايتاً — يعبر مدى البايت الواحد كلّه إلى 0x82. |
Q3 | ✓ | ✓ | ✓ | ✓ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ |
| roundtrip-long-arabic اسمٌ من 64 حرفاً يخرج كما دخل. القاعدة نفسها من جهة القيمة لا من جهة الطول. |
Q5 | ✓ | ✓ | ✓ | ✓ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ |
| realistic-saudi-name اسمٌ سعودي واقعي بطوله الطبيعي — لا سلسلةَ حرفٍ مكرّر. |
Q5 | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✗ |
| amount-as-printed «1000.00» على وجه الفاتورة يجب أن يكون «1000.00» في الرمز — لا «1000». |
Q6 | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| timestamp-verbatim الطابع الزمني يدخل في المطابقة — وإعادةُ صياغته صامتاً تغيير قيمة. |
Q7 | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✗ | ✗ |
القواعد ومصادرها
الحَكَم مصدرٌ مسمّى لا رأيُنا. فمن يخالف حالةً يخالف مصدرها، ويستطيع أن ينازع في المصدر نفسه علناً.
| الرمز | القاعدة |
|---|---|
| Q1 | الطول بعدد البايتات لا بعدد الأحرف طول القيمة يُكتب بعدد بايتات ترميز UTF-8 لا بعدد الأحرف (§4.1). و«شركة» أربعة أحرف وثمانِ بايتات — فمن عدّ الأحرف أنتج رمزاً يفشل قارئه بلا رسالة تشخيص.
المصدر: ZATCA — Electronic Invoice Security Features Implementation Standards, §4.1 والجدول 3 |
| Q2 | الطول بقواعد BER عند 128 بايتاً نصّ المواصفة «The length shall be stored in one byte» تبسيطٌ ينكسر عند 128: البايت الواحد يحمل حتى 127، وما فوقه يلزمه 0x81 ثم بايت الطول. والحسم من منتدى الهيئة نفسه بنصّ صاحب المشكلة بعد إصلاحها: «our code assumed that the maximum length of the value is 1 byte … we were not properly convert it to TLV value». وحدُّ 128 يبلغه اسمٌ عربي من 64 حرفاً — فالحرف بايتان.
المصدر: منتدى الهيئة |
| Q3 | الطول بقواعد BER فوق 255 بايتاً ما جاوز 255 بايتاً يلزمه 0x82 ثم بايتان بترتيب كبير-أولاً. واسمٌ عربي من 128 حرفاً يبلغ 256 بايتاً.
المصدر: منتدى الهيئة |
| Q4 | ترتيب الوسوم 1 ثم 2 ثم 3 ثم 4 ثم 5 الجدول 3 يُرتّب الوسوم: اسم البائع، ثم الرقم الضريبي، ثم الطابع الزمني، ثم الإجمالي شامل الضريبة، ثم مبلغ الضريبة. والترتيب جزءٌ من البنية لا تفصيلٌ تجميلي.
المصدر: ZATCA — Electronic Invoice Security Features Implementation Standards, §4.1 والجدول 3 |
| Q5 | القيمة تصل كما كُتبت الاسم المُدخَل يجب أن يخرج من الفكّ كما دخل — لا بتراً ولا تحريفاً في الترميز. وهذه القاعدة تكشف عيب الطول من الجهة الأخرى: طولٌ خاطئ يُنتج قيمةً مبتورة.
المصدر: ZATCA — Electronic Invoice Security Features Implementation Standards, §4.1 والجدول 3 |
| Q6 | المبلغ كما يُطبع على الفاتورة الوسمان 4 و5 يحملان المبلغ نصّاً كما يُطبع على الفاتورة. فمن أعاد تنسيقه («1000.00» ⇐ «1000») خالف المطبوع، ورمزُه لا يطابق وجه الفاتورة.
المصدر: ZATCA — Electronic Invoice Security Features Implementation Standards, §4.1 والجدول 3 |
| Q7 | الطابع الزمني كما أُعطي الوسم 3 طابعٌ زمني بصيغة ISO 8601 مع منطقة زمنية. ومن أعاد صياغته صامتاً غيّر قيمةً تدخل في المطابقة.
المصدر: ZATCA — Electronic Invoice Security Features Implementation Standards, §4.1 والجدول 3 |
أعِد تشغيله بنفسك
git clone https://github.com/opusstudiohq-max/arabic-invoice-mcp
cd arabic-invoice-mcp/zatca-qr-benchmark
npm install
node run.mjs --fetch && node build.mjs
الحالات في cases.json، والمحوّلات في run.mjs.
ومن رأى محوّلاً يظلم مكتبته فليفتح مسألةً — أخطأنا في تسعةٍ من
محوّلاتنا في التشغيل الأول، فأصلحناها قبل النشر، ولا نستبعد بقيّة.