هذا الدليل يأخذك من فاتورة مبسطة واحدة إلى رمز QR جاهز، خطوة ببايت. لن نتحدث عن البنية في المجرّد، بل نأخذ قيمًا حقيقية لخمسة حقول، ونحوّل كل حقل إلى بايتات بصيغة TLV، ونضمّها في سلسلة واحدة، ثم نرمّزها إلى Base64 لنحصل على النص الذي يُرسم داخل المربّع الأسود. بعدها نعكس العملية: نأخذ نص Base64 ونفكّه عائدين إلى الحقول الأصلية. الهدف أن تخرج بمثال تشغيلي تقارن نتيجتك به بايتًا ببايت.
هذا الدليل عملي بحت ويفترض أنك قرأت الأساس أولًا. إن أردت الفكرة العامة عن الرمز ولماذا فرضته الهيئة وكيف تقرأه من هاتفك فالمكان الصحيح هو دليلنا المفاهيمي: رمز الاستجابة السريع (QR) في الفاتورة الإلكترونية. وإن أردت المرجع التقني الكامل لترتيب الوسوم التسعة وقواعد بنائها فهو في دليل بنية رمز QR التقنية في الفاتورة. أما هنا فنشتغل على عينة واحدة من البداية إلى النهاية، ذهابًا وإيابًا.
يصدر برنامج الفاتورة الإلكترونية من قيود هذا الرمز مطابقًا لمواصفة الهيئة على كل فاتورة تلقائيًا، فلا تبنيه بيدك في الإنتاج. لكن بناء عينة يدويًا مرة واحدة يمنحك القدرة على قراءة أي رمز، والتحقق من صحته عند التدقيق، وتشخيص أي خطأ يظهر عند التكامل مع منصة فاتورة.
القيم الخمس التي سنبني منها العينة
المرحلة الأولى من الفوترة الإلكترونية تطلب خمسة حقول إلزامية على الفاتورة الضريبية المبسّطة (B2C). نأخذ القيم التالية ونثبّتها لبقية الدليل. أي رقم تراه لاحقًا مشتقّ من هذه القيم بالضبط، فيمكنك إعادة الحساب والمطابقة:
- اسم البائع (الوسم 1):
شركة قيود - الرقم الضريبي (الوسم 2):
300000000000003 - الطابع الزمني (الوسم 3):
2026-06-24T11:45:30Z - الإجمالي شامل الضريبة (الوسم 4):
115.00 - إجمالي ضريبة القيمة المضافة (الوسم 5):
15.00
هذه القيم نفسها التي استخدمناها في الدليل التقني، لكن هناك وقفنا عند بناء القطع. هنا نكمل حتى النص النهائي ونعكسه. لاحظ أن الإجمالي 115.00 يساوي الصافي 100.00 زائد ضريبة 15.00 بنسبة 15%، فالأرقام متّسقة محاسبيًا وليست عشوائية.
الحقول الخمسة
قطع TLV
سلسلة 73 بايت
Base64 ← رمز QR
بناء قطعة TLV لكل وسم على حدة
قبل أي شيء نتذكر القاعدة: كل وسم يُكتب على هيئة ثلاث خانات متتابعة. الخانة الأولى رقم الوسم في بايت واحد، والخانة الثانية طول القيمة بعدد بايتاتها لا بعدد حروفها، والخانة الثالثة بايتات القيمة نفسها بترميز UTF-8. للتعمّق في هذه القاعدة راجع دليل ترميز TLV في رمز الفاتورة.
نبدأ بالوسم 1 لأنه الأهم في كشف الالتباس بين الحروف والبايتات. اسم البائع شركة قيود يتكوّن من تسعة محارف ظاهرة بينها مسافة واحدة. لكن الحرف العربي الواحد يشغل بايتين في UTF-8، والمسافة تشغل بايتًا واحدًا. فيكون مجموع البايتات سبعة عشر بايتًا لا تسعة:
هنا الدرس الأهم في الدليل كله: بايت الطول يحمل عدد البايتات، لا عدد الأحرف. من يضع الرقم 9 مكان 17 يكسر السلسلة كلها، لأن القارئ سيقرأ تسعة بايتات فقط للاسم ثم يحسب البايت العاشر على أنه بداية الوسم التالي، فينهار كل ما بعده.
الوسوم الأربعة الباقية قيمها لاتينية أو رقمية، فكل محرف فيها بايت واحد ASCII، ويتطابق عدد الأحرف مع عدد البايتات:
لاحظ بايت الطول في كل وسم. الوسم 2 طوله 0x0F أي 15 لأن الرقم الضريبي السعودي خمس عشرة خانة دائمًا. الوسم 3 طوله 0x14 أي 20 لأن صيغة ISO 8601 الكاملة المنتهية بحرف Z تشغل عشرين محرفًا. القيمتان 115.00 و15.00 تحتفظان بخانتين عشريتين كما تتطلب المواصفة، فلا تكتب 115 ولا 15 مجرّدتين.
تجميع البايتات في سلسلة واحدة
بعد بناء القطع الخمس نضمّها بالترتيب من الوسم 1 إلى الوسم 5 دون أي فاصل بينها. لا مسافات، ولا فواصل، ولا أسطر جديدة. كل قطعة تلتصق بالتي تليها مباشرة، فيقرأ المفكّك رقم الوسم، ثم الطول، ثم يقفز عدد البايتات المحدد، ثم يصل تلقائيًا إلى الوسم التالي.
الناتج سلسلة طولها ثلاثة وسبعون بايتًا. هذه هي السلسلة الخام كاملة بالنظام الست عشري، كل بايت في خانتين:
تتبّع البداية لتفهم البنية. أول بايت 01 رقم الوسم. البايت الثاني 11 الطول وهو 17. تليه سبعة عشر بايتًا هي اسم البائع، تبدأ بـD8 B4 (حرف الشين) وتنتهي بـD8 AF (حرف الدال). مباشرة بعدها يأتي 02 بداية الوسم الثاني. هكذا تقرأ السلسلة بصريًا دون أي أداة.
كل حقل: بايت وسم + بايت طول + قيمة
الوسم 1 طوله 17 بايت (لا 9 حروف) بعد UTF-8
خمس مناطق متتابعة للحقول الخمسة
تنتهي القراءة عند آخر بايت
تحويل السلسلة إلى Base64
السلسلة الخام بايتات ثنائية لا تُرسم مباشرة كنص. لذلك تُحوّل إلى Base64، وهو ترميز يمثّل كل ثلاثة بايتات بأربعة محارف نصية آمنة. لتفصيل آلية هذا الترميز راجع دليل ترميز Base64 في الفاتورة الإلكترونية.
نأخذ البايتات الثلاثة والسبعين ونمرّرها على دالة Base64 القياسية. الناتج النصي النهائي هو ما يُرسم داخل رمز QR:
طول هذا النص مئة محرف، وينتهي بعلامتي ==. هذه العلامة حشو يضيفه Base64 لأن عدد البايتات الأصلية لا يقبل القسمة على ثلاثة دون باقٍ. ثلاثة وسبعون بايتًا تترك باقيًا بمقدار بايت واحد، فيُضاف حشوان لإكمال الكتلة الأخيرة. لو رأيت نصًا بلا هذه العلامة فهذا لا يعني خطأً بالضرورة، إذ يعتمد وجودها على طول البيانات.
هذا النص بالتحديد هو ما تحوّله مكتبة توليد QR إلى مربّع نقاط. لو أدخلت النص نفسه في أي مولّد QR قياسي بترميز نصي، فستحصل على مربّع يطابق ما يطبعه نظام مطابق للمواصفة لفاتورة بهذه القيم.
ماذا يحدث في المرحلة الثانية: الوسوم من 6 إلى 9
المثال السابق يغطي المرحلة الأولى بحقولها الخمسة. في المرحلة الثانية تُضاف أربعة وسوم تشفيرية تجعل الرمز قابلًا للتحقق لا مجرّد ملخّص بصري. نوضّح كيف تنضمّ إلى السلسلة دون أن نطيل في تفاصيل التشفير المشروحة في أدلتها المخصصة:
- الوسم 6: بصمة الفاتورة، وهي تجزئة SHA-256 لملف XML بعد تقييسه، مخزَّنة بصيغة Base64 يبلغ طولها أربعة وأربعين محرفًا.
- الوسم 7: الختم التشفيري، توقيع رقمي بخوارزمية المنحنى الإهليلجي (ECDSA) يُحسب على البصمة.
- الوسم 8: المفتاح العام المستخرَج من شهادة البائع، يتيح للقارئ التحقق من التوقيع.
- الوسم 9: ختم الهيئة، توقيع تضعه هيئة الزكاة والضريبة والجمارك على المفتاح العام لإثبات اعتماده.
هذه الوسوم تأتي بعد الوسم 5 بالترتيب نفسه. خذ الوسم 6 مثالًا. لنفترض أن بصمة العينة بصيغة Base64 هي القيمة التالية المكوّنة من أربعة وأربعين محرفًا:
هنا الطول 0x2C أي 44، وهو عدد محارف نص Base64 وليس عدد بايتات البصمة الأصلية (32 بايتًا). نقطة مهمة: قيمة الوسم 6 تُخزَّن كنص Base64 جاهز، فطولها يُحسب على المحارف لا على البايتات الثنائية الأصلية. بعد إلحاق الوسوم الأربعة تكبر السلسلة كثيرًا لأن الختم والمفتاح بايتات طويلة، فتتجاوز الحمولة بسهولة ثلاثمئة بايت قبل ترميز Base64.
تذكّر فرقًا جوهريًا. الفاتورة المبسطة (B2C) تحمل الوسوم التسعة كاملة لأنها تُشارك مع المشتري فورًا وتُبلَّغ الهيئة خلالها 24 ساعة. أما الفاتورة الضريبية المعيارية (B2B) فتمرّ بالمقاصة المسبقة لدى الهيئة، ومحتوى رمزها يختلف بحسب ما تطلبه المواصفة لكل نوع.
| المعيار | المرحلة الأولى | المرحلة الثانية |
|---|---|---|
| الوسوم | 5 وسوم | 9 وسوم |
| الطول | ≈73 بايت | يتجاوز 300 بايت |
| الإضافة | — | تجزئة وختم ومفتاح عام |
فكّ الترميز: من Base64 إلى الحقول الأصلية
القيمة الحقيقية في فهم البناء تظهر عند العكس. التحقق من فاتورة يبدأ دائمًا بفكّ رمزها وقراءة حقوله. نأخذ نص Base64 الذي بنيناه ونعكس العملية في ثلاث خطوات.
الخطوة الأولى: فكّ Base64 لاستعادة البايتات الخام الثلاثة والسبعين. الخطوة الثانية: نقرأ البايتات تباعًا. نقرأ بايت الوسم، ثم بايت الطول، ثم نقتطع عدد البايتات المحدد كقيمة، ثم نقفز إلى البايت التالي ونكرّر. الخطوة الثالثة: نحوّل بايتات كل قيمة من UTF-8 إلى نص مقروء. الناتج يطابق القيم الخمس التي بدأنا بها تمامًا:
هذه الدورة المغلقة هي جوهر التحقق. لو فككت رمز فاتورة فعلية وحصلت على رقم ضريبي بطول مختلف عن 15، أو طابع زمني لا يطابق صيغة ISO، أو إجمالي ضريبة لا يساوي 15% من الصافي، فأنت أمام فاتورة مشبوهة أو مولّد غير مطابق. الفكّ يكشف العيب فورًا.
للتحقق العملي من السلسلة، طابق ثلاثة أشياء بعد الفكّ. أولًا أن مجموع أطوال القطع زائد بايتي الوسم والطول لكل وسم يساوي طول السلسلة الكلي. ثانيًا أن كل بايت طول يطابق عدد بايتات قيمته فعلًا. ثالثًا أن القيم منطقية محاسبيًا، فالإجمالي شامل الضريبة يساوي الصافي زائد الضريبة.
تفاصيل الطابع الزمني وترتيب الوسوم
الطابع الزمني في الوسم 3 يستحق دقة خاصة، لأنه أكثر حقل يقع فيه خطأ صامت. المواصفة تطلب صيغة ISO 8601 الكاملة بالتوقيت العالمي، أي التاريخ ثم حرف T فاصلًا، ثم الوقت بالساعات والدقائق والثواني، ثم حرف Z دلالة على توقيت غرينتش. في عينتنا القيمة 2026-06-24T11:45:30Z تعني الرابع والعشرين من يونيو 2026، الساعة الحادية عشرة وخمسًا وأربعين دقيقة وثلاثين ثانية بالتوقيت العالمي.
الخطأ الشائع كتابة الوقت بالتوقيت المحلي دون تحويله، أو حذف حرف Z، أو استخدام مسافة بدل حرف T. كل من هذه يغيّر طول القيمة أو يخالف الصيغة، فيفشل التحقق. احسب الطابع دائمًا بلحظة إصدار الفاتورة الفعلية، وحوّله إلى التوقيت العالمي قبل كتابته. وتذكّر أن طوله عشرون محرفًا بالضبط بهذه الصيغة، فإن خرج عن العشرين فالصيغة خاطئة.
أما ترتيب الوسوم فقاعدة ملزمة لا اجتهاد فيها. تبدأ السلسلة بالوسم 1 وتنتهي بالوسم 5 في المرحلة الأولى، أو بالوسم 9 في المرحلة الثانية، بهذا التسلسل الرقمي حصرًا. لا يجوز تقديم الرقم الضريبي على اسم البائع، ولا تأخير الطابع الزمني. القارئ يعتمد على الترتيب ليعرف معنى كل قطعة، فلو بدّلت الترتيب لقرأ القيم في غير حقولها رغم سلامة بنائها بايتًا ببايت.
تنبيه أخير على القيم الرقمية. المبالغ في الوسمين 4 و5 نصوص لا أعداد، فتُكتب كما تُعرض بالخانتين العشريتين. لا تطبّق عليها تنسيقًا برمجيًا للأعداد قد يحذف الأصفار اللاحقة أو يضيف فاصل آلاف. القيمة 115.00 نص من ستة محارف، فلو حوّلها برنامجك إلى العدد 115 لاختصرها وكسر طولها. عامل كل قيم الوسوم كنصوص خام من البداية إلى النهاية.
لماذا تختلف البايتات عن الأحرف: جدول العينة
الالتباس بين عدد الأحرف وعدد البايتات هو مصدر معظم الأخطاء، فيستحق وقفة أطول. الحرف في ذهن الكاتب وحدة واحدة، لكن الحاسوب يخزّن النص بايتات بحسب ترميزه. في UTF-8، يأخذ الحرف اللاتيني والرقم وعلامات الترقيم بايتًا واحدًا، بينما يأخذ الحرف العربي بايتين، وبعض الرموز ثلاثة أو أربعة. هذا التفاوت هو ما يجعل بايت الطول في TLV يحسب البايتات لا الأحرف.
لنطبّق القاعدة على قيم عينتنا الخمس ونقارن العدّين جنبًا إلى جنب. القيم الأربع الأخيرة لاتينية ورقمية فيتساوى فيها العدّان، أما اسم البائع العربي فيختلف:
- الوسم 1،
شركة قيود: تسعة أحرف ظاهرة، لكن سبعة عشر بايتًا. ثمانية أحرف عربية بايتين لكل حرف يساوي ستة عشر بايتًا، زائد بايت المسافة الواحد. - الوسم 2، الرقم الضريبي: خمس عشرة خانة رقمية، وخمسة عشر بايتًا. تطابق تام لأن الأرقام لاتينية.
- الوسم 3، الطابع الزمني: عشرون محرفًا بين أرقام وشرطات وحرفي
TوZونقطتين، وعشرون بايتًا. تطابق تام. - الوسم 4، الإجمالي: ستة محارف، وستة بايتات. تطابق تام.
- الوسم 5، الضريبة: خمسة محارف، وخمسة بايتات. تطابق تام.
الخلاصة العملية بسيطة. متى احتوت القيمة على أي حرف عربي، فاحسب طولها بعد ترميزها UTF-8، لا بعدّ حروفها على الشاشة. كل لغة برمجة توفّر دالة تعيد عدد بايتات النص بعد الترميز، فاستخدمها بدل دالة طول النص العادية. الاعتماد على طول النص الظاهر هو ما يكسر الوسم 1 تحديدًا، لأنه الوسم الوحيد ذو القيمة العربية في الفاتورة المبسطة.
أين يُحقن نص Base64 داخل الفاتورة؟
نص Base64 الذي بنيناه لا يعيش وحده، بل يُحقن داخل ملف الفاتورة الإلكترونية بصيغة XML المبني على معيار UBL 2.1. يوضع الرمز في خانة مخصصة ضمن عناصر إضافات المستند، فيحمله الملف نفسه إلى جانب بقية بيانات الفاتورة. هكذا يصل الرمز إلى منصة فاتورة وإلى المشتري في وثيقة واحدة متماسكة.
عند طباعة الفاتورة أو عرضها، يقرأ النظام نص Base64 من موضعه في XML، ويمرّره على مولّد QR ليرسم المربّع الأسود على الإيصال. فالنص هو المصدر، والمربّع تمثيل بصري له. من يقرأ المربّع بكاميرا هاتفه يستعيد النص نفسه، ثم يفكّه إلى الحقول كما فعلنا في قسم الفكّ. هذه السلسلة المتصلة من XML إلى المربّع إلى الكاميرا هي ما يجعل التحقق ممكنًا في أي مكان دون اتصال بقاعدة بيانات.
هذا الترابط يفسّر أهمية الدقة بايتًا ببايت. أي خطأ في بناء السلسلة ينتقل من XML إلى المربّع المطبوع، فيقرأ المدقّق رمزًا لا يطابق بيانات الفاتورة الظاهرة، وتُرفض الفاتورة. لذلك يولّد قيود الرمز ويحقنه في موضعه الصحيح آليًا، فيضمن تطابق المربّع مع البيانات في كل مرة.
قراءة الرمز عند التدقيق والتحقق الميداني
التطبيق الأكثر شيوعًا لفهم هذه البنية هو التحقق الميداني. مفتّش الهيئة أو المحاسب الذي يراجع فاتورة لا يفتح ملف XML غالبًا، بل يصوّر رمز QR بتطبيق قارئ، فيستعيد نص Base64 ويفكّه إلى الحقول الخمسة. عندها يقارن اسم البائع ورقمه الضريبي والإجمالي والضريبة بما هو مطبوع على وجه الفاتورة.
هنا تظهر قيمة المثال الذي بنيناه. إذا أعاد القارئ رقمًا ضريبيًا بطول غير خمس عشرة خانة، فالرمز مبني خطأ أو الفاتورة مزوّرة. وإذا كان الإجمالي المفكوك لا يساوي الصافي زائد ضريبة 15%، فهناك تلاعب أو خطأ حسابي. وإذا لم يتطابق الإجمالي في الرمز مع الإجمالي المطبوع، فالفاتورة غير موثوقة. الفكّ يحوّل المربّع الصامت إلى أداة كشف فورية.
في المرحلة الثانية يضيف التحقق طبقة أعمق. بعد قراءة الحقول الخمسة، يتحقق القارئ المتقدّم من الوسم 7 (الختم التشفيري) باستخدام المفتاح العام في الوسم 8، ثم يتأكد من اعتماد المفتاح عبر ختم الهيئة في الوسم 9. بهذا لا يكتفي التحقق بمطابقة القيم الظاهرة، بل يثبت أن الفاتورة وُقّعت بشهادة معتمدة ولم تُعدَّل بعد توقيعها. كل هذه السلسلة تبدأ من البايتات نفسها التي بنيناها في هذا المثال.
أخطاء شائعة عند بناء العينة يدويًا
أغلب من يبني السلسلة بيده يقع في أخطاء متكررة. حصرها يوفّر عليك ساعات تشخيص:
- وضع عدد الحروف مكان عدد البايتات في بايت الطول، وهو الخطأ الأشهر مع القيم العربية مثل اسم البائع.
- كتابة الطول بالنظام العشري بدل الست عشري داخل البايت، فيُكتب 15 على هيئة
0x15وهو 21 لا 15. الصواب0x0F. - إدراج فاصل أو مسافة بين القطع، بينما المواصفة تطلب التصاقًا تامًا بلا أي بايت زائد.
- حذف الخانتين العشريتين من المبالغ فتُكتب 115 بدل 115.00، فيتغيّر الطول وتفشل المقارنة.
- ترميز الاسم العربي بترميز غير UTF-8 مثل windows-1256، فتختلف البايتات ويصبح الرمز غير مقروء على الأجهزة القياسية.
- نسيان حشو
=أو إضافته يدويًا في غير موضعه، بينما تتكفّل دالة Base64 القياسية به تلقائيًا.
القاعدة الذهبية: لا تبنِ السلسلة في الإنتاج يدويًا. استخدم مكتبة قياسية للبايتات وأخرى لـBase64، واحتفظ بهذا المثال لاختبار مخرجاتك فقط.
كيف يساعدك قيود في رمز QR المطابق؟
كل ما شرحناه هنا يدويًا يجري داخل برنامج الفاتورة الإلكترونية من قيود تلقائيًا على كل فاتورة. لا تبني سلسلة TLV، ولا ترمّز Base64، ولا تحسب أطوال البايتات بنفسك:
- يولّد قيود رمز QR مطابقًا لمواصفة المرحلة الثانية على كل فاتورة مبسطة، موقّعًا ومختومًا ومحمّلًا بالوسوم التسعة.
- يدير شهادة الختم التشفيري (CSID) آليًا، ويحفظ سلسلة بصمات الفواتير لأغراض التحقق والتدقيق.
- يتولّى الإبلاغ خلال 24 ساعة للفواتير المبسطة (B2C) والمقاصة المسبقة للفواتير المعيارية (B2B) مع منصة فاتورة.
- يصدر ملف XML مطابقًا لمعيار UBL 2.1 ويحقن فيه الرمز في موضعه الصحيح دون أي تدخّل يدوي.
يبقى دورك تسجيل شهادتك لدى الهيئة، ويرشدك قيود خلال هذه الخطوة. وحين تحتاج إلى التحقق من فاتورة أو تشخيص خطأ تكامل، يمنحك فهم هذا المثال القدرة على قراءة أي رمز ومطابقته.
رموز QR مطابقة للهيئة على كل فاتورة، تلقائيًا
دع قيود يبني سلسلة TLV ويرمّزها ويختمها على كل فاتورة، وركّز أنت على عملك بدل حساب البايتات بيدك.
الأسئلة الشائعة
لماذا طول الوسم 1 في مثالنا 17 وليس 9؟
لأن بايت الطول يحمل عدد البايتات لا عدد الأحرف. اسم البائع شركة قيود ثمانية حروف عربية ومسافة، وكل حرف عربي بايتان في UTF-8 والمسافة بايت واحد، فالمجموع 17 بايتًا.
لماذا ينتهي نص Base64 في المثال بعلامتي ==؟
لأن طول البيانات الخام 73 بايتًا لا يقبل القسمة على 3 دون باقٍ، فيضيف Base64 حشوًا لإكمال الكتلة الأخيرة. وجود العلامة من عدمه يعتمد على طول البيانات وحده، وليس مؤشّر خطأ.
هل القيم في هذا المثال حقيقية يمكن استخدامها في فاتورة فعلية؟
لا. القيم الخمس للتعليم فقط، والرقم الضريبي وقيمة البصمة عيّنات توضيحية. استخدم المثال لاختبار مخرجات مكتبتك ومطابقة الأطوال، لا لإصدار فاتورة.
كيف أتحقق من صحة سلسلة بنيتها بنفسي؟
فكّ Base64، ثم اقرأ البايتات تباعًا (وسم فطول فقيمة)، وتأكد أن كل بايت طول يطابق عدد بايتات قيمته، وأن مجموع الأطوال زائد بايتات الوسوم يساوي طول السلسلة الكلي، وأن القيم منطقية محاسبيًا.
هل تختلف العينة بين الفاتورة المبسطة والمعيارية؟
نعم. الفاتورة المبسطة (B2C) تحمل الوسوم التسعة وتُبلَّغ الهيئة خلال 24 ساعة، والمعيارية (B2B) تمرّ بالمقاصة المسبقة، ومحتوى الرمز يختلف بحسب ما تطلبه المواصفة لكل نوع.
هل أحتاج إلى بناء الرمز يدويًا في الإنتاج؟
لا. يبنيه ويرمّزه ويختمه برنامج الفاتورة الإلكترونية من قيود تلقائيًا على كل فاتورة. بناء العينة مرة واحدة هدفه الفهم والتحقق فقط.