جزء من مجموعة: زاتكا والضريبة
أعمال

فاكّ ترميز ZATCA TLV QR

ألصق QR Base64 واستخرج الحقول الخمسة وفق ZATCA

TLV عكسيBase64تحقق5 حقول

حالة الفك

5 / 5
الحقول المستخرجة
حجم البيانات94 بايت
الحالةصالح
حقول Phase 2 إضافية0

الحقول الخمسة الإلزامية

اسم البائع (Tag 1)متجر الرياض المحدودة
الرقم الضريبي (Tag 2)311111111111113
الطابع الزمني (Tag 3)2026-05-29T10:30:00Z
إجمالي الفاتورة (Tag 4)115.00
إجمالي الضريبة (Tag 5)15.00
الحقول الخمسة كاملة وتتوافق مع متطلبات ZATCA Phase 1. تأكد أن التاريخ يطابق وقت إصدار الفاتورة الفعلي.
أداة عكسية للمطورين فقط. لا ترسل بيانات الفواتير الحقيقية على شبكات غير موثوقة، وتحقق من الختم الإلكتروني وسلسلة CSID في Phase 2 عبر بيئة Sandbox الرسمية لـ ZATCA.

تنبيه: هذه الحاسبة لأغراض إرشادية فقط

النتائج تقديرية وقد تختلف عن الأرقام الرسمية المعتمدة من جهة العمل أو المؤسسة المعنية. لا تُعدّ هذه الحاسبة استشارة مالية أو قانونية. للاعتماد الرسمي، يُرجى مراجعة الجهة المختصة.

دليل شامل

كيف يعمل TLV QR في ZATCA — وكيف تفكّه

بنية TLV (Tag-Length-Value)، الحقول الخمسة الإلزامية، فك Base64، حالات الفشل الشائعة، ومقارنة Phase 1 و Phase 2.

11 دقائق قراءة·تحديث مايو 2026·1148+ كلمة

لماذا QR في فواتير ZATCA؟

أوجبت هيئة الزكاة والضريبة والجمارك (ZATCA) إدراج كود QR على كل فاتورة ضريبية مبسطة منذ ديسمبر 2021 ضمن المرحلة الأولى من الفوترة الإلكترونية. الهدف ليس مجرد عرض رقم، بل تضمين هوية البائع ورقمه الضريبي ووقت الفاتورة وقيمها داخل صورة واحدة يقرؤها أي تطبيق مستقل فورًا، بما في ذلك تطبيق ZATCA الرسمي للمستهلكين الذي يتحقق ميدانيًا من صحة البائع.

من منظور المطور، QR ليس صورة فقط — إنه حاوية بيانات منظمة. لكي تطبع رمزًا صحيحًا أو تفكّ رمزًا قائمًا عليك أن تفهم الطبقتين: طبقة الترميز الثنائية (TLV) وطبقة النقل النصي (Base64). هذه المقالة تشرح الطبقتين معًا وتمنحك مرجعًا تعود إليه عند أي خطأ تحقق في بيئة Sandbox الخاصة بـ ZATCA.

ما هو ترميز TLV؟

TLV اختصار لـ Tag-Length-Value، نمط ترميز ثنائي قديم يستخدم في البطاقات الذكية (EMV) وبروتوكولات SIM. الفكرة بسيطة: كل حقل بيانات يُمثَّل بثلاث قطع متتالية: بايت Tag يحدد معنى الحقل، ثم بايت Length يحدد طول القيمة بالبايت (0–255)، ثم Value يحتوي عدد البايتات المحدد مرمَّزًا UTF-8.

المفكّك يمشي على البايتات بشكل متتابع: يقرأ بايت Tag ثم بايت Length ثم يقطع Length بايتات كقيمة، ويكرر العملية حتى نهاية البيانات. هذا النمط يسمح بإضافات مستقبلية دون كسر القارئات القديمة — فهي ببساطة تتجاهل الـ Tags غير المعروفة. هذا هو السبب الذي جعل ZATCA تختاره: التوافق الخلفي بين Phase 1 و Phase 2.

الحقول الخمسة الإلزامية في Phase 1

اسم البائع

يجب أن يطابق الاسم المسجل لدى ZATCA لا الاسم التجاري. الترميز UTF-8، والأسماء العربية تُرمَّز بعدة بايتات لكل حرف، لذلك قد يكون Length أكبر من عدد الأحرف على الشاشة. الحرف العربي الواحد عادة بايتان، ما يعني أن اسمًا مكونًا من 10 أحرف عربية قد يأخذ 20 بايتًا أو أكثر إذا تضمن مسافات أو علامات.

الرقم الضريبي

الـ VAT Number في السعودية يتكون من 15 رقمًا يبدأ بـ3 وينتهي بـ3، والأرقام الـ13 الوسطى يصدرها النظام عند التسجيل. أي رقم لا يطابق هذا النمط علامة فورية على فاتورة مزوّرة أو خطأ في إصدار QR. التحقق من النمط يجب أن يحدث قبل أي حسابات لاحقة.

الطابع الزمني

ISO 8601 بصيغة UTC، يحتوي على T والـ Z في النهاية. الخطأ الشائع استخدام التاريخ فقط دون وقت، أو صيغة محلية مثل 29/05/2026 — وكلاهما يُرفض عند التحقق. يفضل توليده عبر new Date().toISOString() في JavaScript أو DateTimeFormatter.ISO_INSTANT في Java.

الإجمالي والضريبة

كلاهما سلسلة عشرية (string لا float)، الفاصلة العشرية نقطة (.) لا فاصلة (,). يجب منطقيًا أن تكون الضريبة أقل أو تساوي إجمالي الفاتورة. عند نسبة 15٪ القياسية، النسبة بين الضريبة والإجمالي تساوي 15/115 = 13.04٪ تقريبًا.

قراءة البايتات خطوة بخطوة

لنفترض أن الحقل الأول اسم البائع Shop (4 أحرف ASCII). البايتات تكون: 01 04 53 68 6F 70 — أي Tag=1، Length=4، ثم بايتات Shop في UTF-8. الخوارزمية في JavaScript واضحة: نحرّك مؤشرًا (cursor) من 0. في كل لفّة نقرأ bytes[cursor] كـ tag ثم bytes[cursor+1] كـ length ثم نقطع شريحة من cursor+2 إلى cursor+2+length، ونرفع cursor إلى cursor+2+length. نتوقف عند تجاوز طول المصفوفة.

let cursor = 0;
while (cursor < bytes.length) {
  const tag = bytes[cursor];
  const len = bytes[cursor + 1];
  const value = bytes.subarray(cursor + 2, cursor + 2 + len);
  fields[tag] = new TextDecoder("utf-8").decode(value);
  cursor += 2 + len;
}

ملاحظة مهمة: إذا كانت قيمة Length أكبر من البايتات المتبقية فعلًا، فهذا دليل على ملف تالف أو تم اقتطاعه — يجب إيقاف الفك وتسجيل خطأ truncated_value بدلًا من قراءة بايتات عشوائية من خارج النطاق.

دور Base64 في النقل

QR Code يحمل سلسلة نصية لا بايتات خام. ولأن TLV ثنائي ويحتوي على بايتات صفرية أو غير مطبوعة، يجب لفّه بـ Base64 قبل إدراجه في QR. العملية ميكانيكية: تأخذ كل 3 بايتات (24 بت) وتحوّلها إلى 4 أحرف من مجموعة 64 حرفًا آمنة (A-Z, a-z, 0-9, +, /). كل حرف يمثل 6 بت، ويُعاد التركيب بسهولة في الاتجاه المعاكس.

في JavaScript المتصفح، يكفي استدعاء atob(b64) للحصول على binary string ثم تحويلها إلى Uint8Array عبر charCodeAt لكل حرف. في Node.js يكفي Buffer.from(b64, "base64"). الأداة هنا تجرّب الاثنين تلقائيًا فتعمل في كل من Edge Runtime والمتصفح. لاحظ أن atob يرمي SyntaxError إذا احتوت السلسلة حروفًا غير مسموحة، لذلك نلفّ المحاولة في try/catch ونعيد invalid_base64 كخطأ منظم.

معالجة UTF-8 للأسماء العربية

الخطأ الأكثر إحراجًا في أدوات الفك العربية أن تظهر أسماء البائعين كمربعات أو علامات استفهام. السبب دائمًا واحد: استخدام String.fromCharCode على البايتات مباشرة بدل TextDecoder. الأول يفترض ASCII أحادي البايت، والثاني يدرك أن الحرف العربي قد يأخذ 2-3 بايتات. الحل القياسي: new TextDecoder("utf-8").decode(uint8Array).

الحرف الواحد في UTF-8 يأخذ بين بايت واحد (ASCII) و4 بايتات (رموز Emoji). الأحرف العربية الأساسية كلها في النطاق U+0600 إلى U+06FF وتُرمَّز ببايتين. مع المسافات وعلامات الترقيم الإنجليزية يأخذ كل منها بايت واحد، ما يفسر التفاوت في طول Tag 1 من فاتورة لأخرى.

حقول Phase 2 الإضافية (6–9)

المرحلة الثانية أضافت أربعة tags إلى نفس الـ QR دون كسر القارئات القديمة: Tag 6 (هاش الفاتورة) و Tag 7 (التوقيع الرقمي) و Tag 8 (المفتاح العام لشهادة CSID) و Tag 9 (توقيع الـ CA). هذه التوقيعات مرمَّزة Base64 داخل TLV، وتُضاف فقط لفواتير B2B (الضريبية الكاملة)، أما الفواتير المبسطة B2C فتبقى على الحقول 1-5 وتُختم لاحقًا عبر Cryptographic Stamp.

المفكِّك الجيد يجب أن يتجاهل بصمت أي tag أكبر من 5 ولا يعتبره خطأ، وأن يعدّ عدد tags الإضافية في حقل extraTags كي يميّز المستخدم فورًا بين QR مرحلة أولى وأخرى مرحلة ثانية. هذا ما تفعله هذه الأداة.

التحقق من صحة الحقول

الفك الناجح لا يعني فاتورة صحيحة. التحقق المنطقي يأتي بعد الفك ويغطي: نمط VAT (15 رقمًا 3...3)، صيغة ISO 8601 للوقت، كون الإجمالي والضريبة أعدادًا عشرية موجبة، وألا تتجاوز الضريبة الإجمالي. هذه الأداة تنفذ التحقّقات الأربعة وتُرجع رمز خطأ منفصل لكل منها: invalid_vat_number_format و invalid_timestamp_format و invalid_invoice_total و vat_exceeds_total.

الفصل بين الفك والتحقق مفيد عمليًا: قد تريد قبول فاتورة بصيغة وقت غير قياسية للأرشفة فقط، أو ترفض أي قيمة سالبة في الضريبة قبل إدخالها قاعدة بياناتك. كل خطأ منفصل يسمح لك بإطفاء أو تشديد التحقّقات حسب سياق الاستخدام.

أخطاء الفك الشائعة

أبرز الأسباب لفشل الفك: نسخ ناقص للـ QR من شاشة الكاشير (truncated_value)، استخدام صورة QR التُقطت بكاميرا منخفضة الدقة فظهر طابع زمني مشوّش، أو لصق محتوى الـ QR ككود URL Encoded بدل Base64. التحقق الأول دائمًا يجب أن يكون: هل السلسلة تطابق /^[A-Za-z0-9+/=]+$/؟ إن لم تطابق فهي ليست Base64 خامة.

سبب آخر شائع: ZATCA Phase 1 تتطلب الحقول 1-5 جميعًا. غياب أي منها (missing_tag_N) يعني أن المصدر لم يلتزم بالمواصفة وليس عيبًا في الفك. أبلغ المورّد المعني فورًا لأن الفاتورة قد تُرفض من قِبل هيئة الزكاة عند المراجعة.

حالات الاستخدام

المراجعون والمحاسبون يستخدمون فك الـ QR للتأكد من تطابق رقم VAT المطبوع مع الرقم المرمَّز داخل QR (احتيال شائع: طباعة رقم VAT صحيح بصريًا مع QR يحوي رقمًا مختلفًا). مديرو ضبط الجودة في أنظمة POS يستخدمونه للتأكد من صحة الـ QR قبل إطلاق تحديث، لأن خطأ في توليده ينعكس على آلاف الفواتير قبل اكتشافه.

المطوّرون عند بناء تكامل ZATCA الأولي يستخدمون أداة الفك العكسي للتأكد من أن مولّد الـ QR لديهم ينتج البايتات المتوقعة. اقرأ مقالنا عن إنشاء فاتورة ZATCA للجانب الآخر من المعادلة.

ملاحظات أمنية

QR الفواتير ليس سريًا — مطبوع على ورق الفاتورة ومرئي لأي عميل. لكن أرقام VAT للمنشآت عند تجميعها بكميات كبيرة تصبح أصلًا ذا قيمة لخصوم تجاريين. لا تنشر سجلات قواعد البيانات الكاملة، ولا تستخدم بيانات QR إنتاجية في اختبارات تشاركها مع طرف ثالث دون إذن المنشأة المعنية.

الأداة هنا تعمل كاملة في المتصفح: لا يُرسل الـ Base64 إلى أي خادم. تستطيع التحقق بفتح Network tab وملاحظة عدم وجود أي طلب صادر أثناء الفك.

قراءات ذات صلة

مقالات وأدلّة مرتبطة بنفس الموضوع على ArabToolBox.

أدوات قد تهمّك