يواجه المطورون في السعودية تحديًا تقنيًا متزايد التعقيد مع تطبيق المرحلة الثانية من الفوترة الإلكترونية. لم يعد الأمر مجرد إنشاء ملف PDF وإضافة QR Code بسيط، بل أصبح يتطلب فهمًا عميقًا لمعايير UBL 2.1 وآليات التوقيع الرقمي والتكامل مع ZATCA API. هذا الدليل موجه للمهندسين الذين يبنون أنظمة فوترة من الصفر أو يدمجون منصات التجارة الإلكترونية مع متطلبات هيئة الزكاة والضريبة والجمارك.
لفهم الصورة الكاملة لنظام الفوترة الإلكترونية في السعودية، بما في ذلك المتطلبات النظامية، مراحل التطبيق، والفرق بين الفواتير القياسية والمبسطة، يمكن الرجوع إلى دليل الفوترة الإلكترونية في السعودية 2026: المتطلبات والربط مع ZATCA . هذا الدليل يوفر الأساس المفاهيمي قبل الدخول في التفاصيل التقنية لـ XML UBL و ZATCA API.
ما هو UBL 2.1 ولماذا تعتمد ZATCA عليه؟
Universal Business Language (UBL) هو معيار دولي تم تطويره من قبل منظمة OASIS لتوحيد تبادل المستندات التجارية إلكترونيًا. النسخة 2.1 تحديدًا توفر هيكلية محددة وقابلة للتحقق آليًا لتمثيل الفواتير والإشعارات الدائنة والمدينة.
الفرق الجوهري بين ملف XML تقليدي وملف UBL يكمن في التوحيد المعياري. في XML العادي، يمكن للمطور تسمية العناصر كما يشاء، لكن UBL يفرض أسماء محددة، تسلسل هرمي معرّف مسبقًا، وقواعد تحقق صارمة. هذا يضمن أن أي نظام يدعم UBL 2.1 يمكنه قراءة ومعالجة الفاتورة بدون الحاجة لاتفاقيات خاصة بين الأطراف.
اختارت ZATCA هذا المعيار لثلاثة أسباب رئيسية: قابلية التشغيل البيني بين الأنظمة المختلفة، إمكانية التحقق الآلي من صحة البيانات، والتوافق مع معايير الفوترة الإلكترونية المعتمدة عالميًا مثل Peppol وe-Invoicing الأوروبي.
بنية ملف XML UBL في الفوترة الإلكترونية
كل فاتورة UBL تبدأ بعنصر جذري يحدد نوع المستند. في حالة الفواتير الضريبية نستخدم Invoice، بينما الإشعارات الدائنة تستخدم CreditNote والمدينة تستخدم DebitNote. كل عنصر جذري يتطلب تعريف مجموعة من Namespaces الإلزامية.
Namespaces الأساسية
هناك ثلاثة Namespaces أساسية يجب تضمينها:
- xmlns: الـ namespace الافتراضي لـ UBL Invoice
- xmlns:cac: Common Aggregate Components (العناصر المركبة)
- xmlns:cbc: Common Basic Components (العناصر الأساسية)
أي خطأ في كتابة هذه الـ Namespaces سيؤدي لرفض الفاتورة فورًا من ZATCA API. يجب نسخها بدقة تامة من المواصفات الرسمية.
مثال الهيكل الأساسي
<?xml version="1.0" encoding="UTF-8"?>
<Invoice xmlns="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2"
xmlns:cac="urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2"
xmlns:cbc="urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2">
<cbc:UBLVersionID>2.1</cbc:UBLVersionID>
<cbc:ProfileID>reporting:1.0</cbc:ProfileID>
<cbc:ID>INV-2026-001</cbc:ID>
<cbc:UUID>123e4567-e89b-12d3-a456-426614174000</cbc:UUID>
<cbc:IssueDate>2026-02-16</cbc:IssueDate>
<cbc:IssueTime>14:30:00</cbc:IssueTime>
<cbc:InvoiceTypeCode name="0200000">388</cbc:InvoiceTypeCode>
<cac:AccountingSupplierParty>
<!-- بيانات البائع -->
</cac:AccountingSupplierParty>
<cac:AccountingCustomerParty>
<!-- بيانات المشتري -->
</cac:AccountingCustomerParty>
<cac:InvoiceLine>
<!-- سطور الفاتورة -->
</cac:InvoiceLine>
<cac:LegalMonetaryTotal>
<!-- المجاميع -->
</cac:LegalMonetaryTotal>
<cac:TaxTotal>
<!-- الضريبة -->
</cac:TaxTotal>
</Invoice>
هذا الهيكل يمثل الحد الأدنى المطلوب. أي عنصر مفقود من العناصر الإلزامية سيسبب فشل التحقق.
العناصر الإلزامية في فاتورة ZATCA
معرفات الفاتورة
كل فاتورة تحتاج ثلاثة معرفات مختلفة:
UBLVersionID: يجب أن يكون دائمًا "2.1" للتوافق مع المعيار الحالي.
ID: رقم تسلسلي فريد للفاتورة ضمن نظامك. يمكن أن يكون "INV-001" أو أي نمط تسلسلي تستخدمه.
UUID: معرف فريد عالميًا يجب توليده باستخدام UUID v4. هذا المعرف لا يمكن أن يتكرر أبدًا، حتى عبر أنظمة مختلفة. استخدم مكتبات UUID المعيارية في لغة البرمجة التي تستخدمها، ولا تحاول توليده يدويًا.
ProfileID وأنواع الفواتير
هذا العنصر يحدد نوع المعالجة المطلوبة من ZATCA. القيمتان الأساسيتان هما:
- reporting:1.0: للفواتير المبسطة (B2C) التي تُرسل للعميل فورًا ويتم إبلاغ ZATCA خلال 24 ساعة
- clearance:1.0: للفواتير الضريبية (B2B) التي تحتاج موافقة من ZATCA قبل إرسالها للعميل
الاختيار الخاطئ هنا يؤدي لرفض الفاتورة أو معالجتها بطريقة غير صحيحة. إذا كنت تبني نظامًا يدعم كلا النوعين، يجب أن تكون هذه القيمة ديناميكية بناءً على نوع العميل (منشأة أم فرد).
InvoiceTypeCode
الأكواد الأساسية المستخدمة:
- 388: فاتورة ضريبية (Tax Invoice)
- 381: إشعار دائن (Credit Note)
- 383: إشعار مدين (Debit Note)
يجب إضافة attribute اسمه "name" يحتوي على كود فرعي يحدد نوع الفاتورة حسب تصنيف ZATCA. للفواتير المبسطة: "0200000"، وللفواتير الضريبية: "0100000".
التاريخ والوقت
عنصر IssueDate يجب أن يكون بصيغة ISO 8601: YYYY-MM-DD. أي صيغة أخرى سترفض. عنصر IssueTime يجب أن يكون بصيغة HH:MM:SS. هذان العنصران يحددان لحظة إصدار الفاتورة وليس لحظة البيع أو التسليم.
بيانات الأطراف
عنصر AccountingSupplierParty يحتوي على بيانات البائع (منشأتك)، ويجب أن يتضمن: الرقم الضريبي، اسم المنشأة، العنوان. عنصر AccountingCustomerParty يحتوي على بيانات المشتري. في الفواتير الضريبية (B2B)، الرقم الضريبي للمشتري إلزامي. في الفواتير المبسطة (B2C)، يمكن الاكتفاء بالاسم.
المجاميع والضريبة
عنصر LegalMonetaryTotal يحتوي على أربعة قيم أساسية: LineExtensionAmount (مجموع السطور قبل الضريبة)، TaxExclusiveAmount (الإجمالي بدون ضريبة)، TaxInclusiveAmount (الإجمالي مع الضريبة)، وPayableAmount (المبلغ المستحق الدفع).
عنصر TaxTotal يجب أن يحتوي على TaxAmount (إجمالي الضريبة) وعلى الأقل TaxSubtotal واحد يحدد نسبة الضريبة المطبقة (عادة 15% للسعودية).
آلية التوقيع الرقمي في XML (XMLDSec)
التوقيع الرقمي في UBL يتم باستخدام معيار XML Digital Signature. العملية تتكون من ثلاث خطوات حرجة: Canonicalization، Hash Generation، ثم التوقيع بالمفتاح الخاص.
Canonicalization (التوحيد القياسي)
قبل حساب الـ Hash، يجب تحويل XML لصيغة قياسية موحدة. هذه العملية تزيل الاختلافات غير الجوهرية مثل المسافات الزائدة، ترتيب Attributes، وطريقة إغلاق العناصر الفارغة. المعيار المستخدم عادة هو Exclusive XML Canonicalization.
السبب وراء هذه الخطوة: نفس محتوى XML يمكن كتابته بطرق مختلفة تقنيًا. بدون Canonicalization، مسافة واحدة زائدة أو تغيير بسيط في الترتيب سيؤدي لـ Hash مختلف تماماً، وبالتالي فشل التحقق من التوقيع.
حساب SHA-256 Hash
بعد Canonicalization، يتم حساب SHA-256 hash للمحتوى. هذا الـ Hash يمثل "بصمة" الفاتورة. أي تغيير ولو بحرف واحد سينتج Hash مختلف تماماً.
التوقيع بـ ECDSA
يتم توقيع الـ Hash باستخدام المفتاح الخاص من الشهادة الرقمية (CSID) التي حصلت عليها من ZATCA. الخوارزمية المستخدمة هي ECDSA مع منحنى secp256r1 (المعروف أيضًا بـ P-256). التوقيع الناتج يُشفّر بـ Base64 ويوضع داخل عنصر UBLExtensions في بداية الفاتورة.
مكان إدراج التوقيع
<Invoice>
<ext:UBLExtensions>
<ext:UBLExtension>
<ext:ExtensionURI>urn:oasis:names:specification:ubl:dsig:enveloped:xades</ext:ExtensionURI>
<ext:ExtensionContent>
<sig:UBLDocumentSignatures>
<sac:SignatureInformation>
<cbc:ID>urn:oasis:names:specification:ubl:signature:1</cbc:ID>
<!-- التوقيع الرقمي هنا -->
</sac:SignatureInformation>
</sig:UBLDocumentSignatures>
</ext:ExtensionContent>
</ext:UBLExtension>
</ext:UBLExtensions>
<!-- باقي عناصر الفاتورة -->
</Invoice>
الأخطاء الشائعة في التوقيع
أكثر خطأ يواجه المطورين: عدم إجراء Canonicalization بشكل صحيح قبل الـ Hash. إذا قمت بحساب Hash للملف مباشرة كما هو، ثم أرسلته لـ ZATCA، سيقوم السيرفر بإجراء Canonicalization على نسخته ويحصل على Hash مختلف، مما يؤدي لرفض التوقيع.
الخطأ الثاني: استخدام شهادة منتهية أو شهادة Compliance في البيئة الإنتاجية. شهادة Compliance صالحة فقط في Sandbox، أما الإنتاج فيتطلب Production CSID.
Hash Chain و Previous Invoice Hash (PIH)
من المتطلبات الفريدة في ZATCA: كل فاتورة يجب أن تحتوي على Hash الفاتورة السابقة. هذا ينشئ "سلسلة" (Chain) من الفواتير يصعب التلاعب بها. إذا حاول أحد تعديل فاتورة قديمة، سيتغير Hash الخاص بها، وبالتالي ستصبح جميع الفواتير اللاحقة غير صالحة.
الفاتورة الأولى
المشكلة: الفاتورة الأولى في النظام لا يوجد لها فاتورة سابقة. الحل: القيمة المتفق عليها هي "0" كـ Previous Invoice Hash للفاتورة الأولى فقط.
حفظ الـ Hash
بعد توليد فاتورة وإرسالها لـ ZATCA، يجب حفظ الـ Hash الخاص بها في قاعدة البيانات. هذا الـ Hash سيستخدم كـ PIH في الفاتورة التالية. إذا فقدت هذا الـ Hash، ستنكسر السلسلة ولن تستطيع إصدار فواتير جديدة إلا بإعادة Onboarding.
مثال كود Python لحساب Hash
import hashlib
from lxml import etree
def calculate_invoice_hash(xml_content, previous_hash):
# Canonicalization باستخدام lxml
tree = etree.fromstring(xml_content.encode('utf-8'))
canonical_xml = etree.tostring(
tree,
method='c14n',
exclusive=True,
with_comments=False
)
# دمج المحتوى مع PIH
data_to_hash = canonical_xml + previous_hash.encode('utf-8')
# حساب SHA-256
sha256_hash = hashlib.sha256(data_to_hash).hexdigest()
return sha256_hash
# مثال الاستخدام
xml_invoice = "<Invoice>...</Invoice>"
previous_invoice_hash = "a1b2c3d4e5f6..." # من قاعدة البيانات
current_hash = calculate_invoice_hash(xml_invoice, previous_invoice_hash)
print(f"Hash: {current_hash}")
# احفظ current_hash في قاعدة البيانات للفاتورة التالية
استخدام مكتبة lxml يضمن Canonicalization صحيح ومطابق لمعيار W3C. مكتبة xml.etree.ElementTree القياسية لا تدعم Canonicalization الكامل في جميع إصدارات Python.
Clearance vs Reporting في ZATCA API
أحد الجوانب المحيرة للمطورين الجدد: الفرق بين Clearance و Reporting. الأمر ليس مجرد اختلاف في API endpoint، بل اختلاف جذري في سير العمل.
إذا كنت بحاجة لفهم الفرق التنظيمي والتشغيلي بين الفواتير الضريبية والمبسطة ومتطلبات كل منها حسب هيئة الزكاة والضريبة والجمارك، راجع الدليل الكامل للفوترة الإلكترونية في السعودية الذي يشرح متى يتم استخدام Clearance ومتى يتم استخدام Reporting من منظور النظام والامتثال.
Clearance (الفواتير الضريبية - B2B)
في هذا النمط، يجب إرسال الفاتورة لـ ZATCA والحصول على موافقة (Clearance) قبل إرسالها للعميل. السير العملي:
- العميل يقوم بالشراء
- النظام ينشئ فاتورة XML UBL
- يتم إرسالها لـ ZATCA عبر
/invoices/clearance/single - ZATCA يتحقق ويعيد Clearance ID وتوقيع رقمي
- فقط بعد ذلك يمكن إرسال الفاتورة للعميل
إذا رفضت ZATCA الفاتورة، لا يمكن إصدارها للعميل. يجب تصحيح الأخطاء وإعادة المحاولة.
Reporting (الفواتير المبسطة - B2C)
هنا الأمر أبسط. الفاتورة ترسل للعميل فورًا، ثم يتم إبلاغ ZATCA خلال 24 ساعة:
- العميل يقوم بالشراء
- النظام ينشئ فاتورة وترسل للعميل فوراً
- في الخلفية، ترسل نسخة لـ ZATCA عبر
/invoices/reporting/single - ZATCA يتحقق ويرسل تأكيد الاستلام
الفرق الزمني كبير. Clearance يجب أن يكون متزامنًا (Synchronous)، بينما Reporting يمكن أن يكون غير متزامن (Asynchronous) طالما تم خلال 24 ساعة.
جدول المقارنة
| الجانب | Clearance (B2B) | Reporting (B2C) |
|---|---|---|
| ProfileID | clearance:1.0 | reporting:1.0 |
| الرقم الضريبي للمشتري | إلزامي | اختياري |
| التوقيت | قبل الإرسال للعميل | خلال 24 ساعة |
| API Endpoint | /invoices/clearance/single | /invoices/reporting/single |
| الموافقة | تحتاج موافقة صريحة | تأكيد استلام فقط |
| QR Code | اختياري | إلزامي |
اختيار النوع الخطأ يؤدي لرفض الفاتورة أو معالجتها بشكل غير صحيح. في الأنظمة التي تدعم كلا النوعين، يجب أن يكون التحديد تلقائيًا بناءً على نوع العميل.
مثال API Response من ZATCA
عند نجاح عملية Clearance، ترجع ZATCA استجابة JSON تحتوي على البيانات الحرجة:
{
"clearanceStatus": "CLEARED",
"clearedInvoice": "base64_signed_xml_here",
"clearanceId": "c1a2b3c4d5e6f7g8h9i0",
"invoiceHash": "a1b2c3...xyz",
"qrCode": "base64_qr_code_here",
"reportingStatus": "NOT_REPORTED",
"validationResults": {
"status": "PASS",
"warningMessages": [],
"errorMessages": []
}
}
بينما في Reporting، الاستجابة أبسط:
{
"reportingStatus": "REPORTED",
"invoiceHash": "a1b2c3...xyz",
"validationResults": {
"status": "PASS",
"warningMessages": [],
"infoMessages": ["Invoice reported successfully"]
}
}
يجب حفظ clearanceId و invoiceHash في قاعدة البيانات للمراجعة والتدقيق. هذه القيم ضرورية للتحقق من صحة الفواتير لاحقًا.
إدراج QR Code داخل UBL
في الفواتير المبسطة (B2C)، QR Code إلزامي. ليس QR عاديًا، بل يحتوي على بيانات مشفرة بصيغة TLV (Tag-Length-Value) محولة لـ Base64.
محتوى QR Code
المرحلة الثانية تتطلب 5 حقول أساسية كحد أدنى:
- اسم البائع
- الرقم الضريبي للبائع
- تاريخ ووقت الفاتورة
- إجمالي الفاتورة (مع الضريبة)
- قيمة الضريبة
كل حقل يُشفّر بصيغة TLV: رقم Tag (بايت واحد)، طول البيانات (بايت واحد)، ثم البيانات نفسها. جميع الحقول تُدمج في مصفوفة بايتات واحدة، ثم تحول لـ Base64.
إدراجه في XML
الـ QR Code يوضع في عنصر AdditionalDocumentReference:
<cac:AdditionalDocumentReference>
<cbc:ID>QR</cbc:ID>
<cac:Attachment>
<cbc:EmbeddedDocumentBinaryObject mimeCode="text/plain">
VGVzdCBCYXNlNjQgUVIgQ29kZQ==
</cbc:EmbeddedDocumentBinaryObject>
</cac:Attachment>
</cac:AdditionalDocumentReference>
القيمة داخل EmbeddedDocumentBinaryObject هي Base64 الناتج من TLV encoding. استخدام نص عادي بدلاً من TLV هو خطأ شائع جداً يؤدي لرفض الفاتورة.
التحقق والاختبار قبل الإرسال إلى ZATCA
قبل إرسال أي فاتورة لبيئة الإنتاج، يجب المرور بثلاث مراحل تحقق.
XML Schema Validation
أول خطوة: التحقق من أن الملف يطابق UBL 2.1 schema. يمكن استخدام أدوات مثل xmllint من سطر الأوامر:
xmllint --noout --schema UBL-Invoice-2.1.xsd invoice.xml
إذا ظهرت أخطاء، يجب تصحيحها قبل المتابعة. الأخطاء الشائعة: عنصر في مكان خاطئ، نوع بيانات خاطئ، أو Namespace مفقود.
ZATCA Sandbox
بعد التحقق من Schema، استخدم بيئة Sandbox للاختبار. هذه البيئة تحاكي الإنتاج تماماً لكن بدون تأثير على البيانات الفعلية. يمكنك اختبار:
- عملية Onboarding كاملة
- إصدار فواتير تجريبية
- اختبار Clearance و Reporting
- محاكاة سيناريوهات الفشل
الخطأ الأكبر: تجاوز Sandbox والاختبار مباشرة في الإنتاج. هذا يؤدي لمشاكل يصعب تتبعها.
Schematron Validation
ZATCA تستخدم Schematron rules إضافية فوق XML Schema. هذه القواعد تتحقق من منطق الأعمال، مثل: هل مجموع السطور يساوي الإجمالي؟ هل نسبة الضريبة صحيحة؟ استخدم ZATCA SDK للتحقق من هذه القواعد قبل الإرسال.
أخطاء شائعة يقع فيها المطورون
| الخطأ | السبب | الحل |
|---|---|---|
| invalid-signature | فشل التحقق من التوقيع الرقمي | تأكد من Canonicalization قبل Hash، واستخدم الشهادة الصحيحة |
| missing-element | عنصر إلزامي مفقود | راجع قائمة العناصر الإلزامية وتأكد من وجودها جميعاً |
| invalid-namespace | Namespace خاطئ أو مفقود | انسخ Namespaces من المواصفات الرسمية بدقة |
| hash-mismatch | Previous Invoice Hash غير صحيح | تأكد من حفظ Hash كل فاتورة في قاعدة البيانات |
| invalid-qr | QR Code بصيغة خاطئة | استخدم TLV encoding ثم Base64، ليس نص عادي |
| date-format | صيغة تاريخ غير صحيحة | استخدم YYYY-MM-DD للتاريخ و HH:MM:SS للوقت |
| duplicate-uuid | UUID مكرر | استخدم UUID v4 الذي يضمن عدم التكرار |
| certificate-expired | الشهادة الرقمية منتهية | راقب تاريخ انتهاء CSID وجددها قبل 30 يوم |
معظم هذه الأخطاء يمكن اكتشافها في Sandbox قبل الإنتاج. خصص وقتًا كافيًا للاختبار الشامل.
خطوات تنفيذ تكامل ZATCA من الصفر
للمطورين الذين يبدأون من الصفر، هذه خارطة الطريق الكاملة لتنفيذ تكامل ناجح مع ZATCA API:
الخطوة الأولى: التسجيل والحصول على بيانات الوصول
ابدأ بالتسجيل في بوابة ZATCA للحصول على: اسم مستخدم API، كلمة مرور، ومعرف الجهاز (Device ID). هذه البيانات ضرورية لعملية Onboarding والحصول على الشهادة الرقمية.
الخطوة الثانية: توليد CSR والحصول على CSID
استخدم OpenSSL لتوليد Certificate Signing Request (CSR)، ثم أرسله لـ ZATCA عبر API Onboarding. ستحصل على Compliance CSID للاختبار في Sandbox، ثم Production CSID بعد اجتياز الاختبارات.
الخطوة الثالثة: بناء XML Generator
اكتب وحدة برمجية تحول بيانات الفاتورة (من قاعدة البيانات أو API) إلى XML UBL 2.1 صالح. استخدم مكتبات XML موثوقة مثل lxml في Python أو xmlbuilder2 في Node.js. تأكد من Namespaces الصحيحة وترتيب العناصر.
الخطوة الرابعة: تطبيق التوقيع الرقمي
ابنِ وحدة منفصلة للتوقيع الرقمي تقوم بـ: Canonicalization، حساب SHA-256 Hash، التوقيع بـ ECDSA، وإدراج التوقيع في UBLExtensions. استخدم مكتبات مثل signxml بدلاً من كتابة الكود من الصفر.
الخطوة الخامسة: إدارة Hash Chain
أنشئ جدول في قاعدة البيانات لحفظ hash كل فاتورة. عند إنشاء فاتورة جديدة، اجلب hash الفاتورة السابقة وأدرجه في PIH. تأكد من آلية قفل (Lock) لمنع إصدار فاتورتين في نفس اللحظة.
الخطوة السادسة: التكامل مع ZATCA API
اكتب client لـ ZATCA API يدعم Clearance و Reporting endpoints. استخدم HTTPS مع Mutual TLS (mTLS) للأمان. تأكد من إرسال الشهادة الصحيحة (Compliance أو Production) حسب البيئة.
الخطوة السابعة: الاختبار الشامل في Sandbox
قبل الإطلاق، اختبر جميع السيناريوهات: فواتير B2B و B2C، إشعارات دائنة ومدينة، رفض الفواتير، انقطاع الشبكة، انتهاء الشهادة. سجل كل خطأ واحفظ طريقة حله.
للمطورين الذين يعملون على تكامل منصات تجارة إلكترونية محددة، يمكن الاطلاع على ربط Shopify مع ZATCA لفهم كيفية تطبيق هذه المفاهيم عمليًا. بالنسبة للمطورين المهتمين بمعايير الفوترة الإلكترونية الإقليمية، يمكن مراجعة دليل الفوترة الإلكترونية Peppol في الإمارات لفهم الاختلافات بين الأنظمة.
أفضل الممارسات في تطوير تكامل ZATCA
لا تبنِ التوقيع الرقمي من الصفر
التوقيع الرقمي، Canonicalization، وإدارة الشهادات معقدة جداً. استخدم مكتبات مثبتة مثل signxml في Python، xml-crypto في Node.js، أو System.Security.Cryptography.Xml في .NET. محاولة بناء هذه الوظائف يدوياً ستؤدي لأخطاء أمنية وثغرات صعبة الاكتشاف.
افصل طبقة التوقيع عن طبقة إنشاء XML
في المعمار البرمجي، يجب أن تكون هناك طبقتان منفصلتان: واحدة لإنشاء محتوى الفاتورة، وأخرى للتوقيع والتشفير. هذا يسهل الاختبار، الصيانة، وإعادة الاستخدام. يمكنك اختبار XML generation بدون الحاجة لشهادات، واختبار التوقيع بملفات ثابتة.
تحقق قبل الإرسال
قبل استدعاء ZATCA API، قم بـ:
- XML Schema validation
- Schematron rules validation
- التحقق من وجود جميع العناصر الإلزامية
- التحقق من صحة المعادلات (مجموع السطور = الإجمالي)
- التحقق من صحة التوقيع محلياً
كل خطوة تتطلب وقتًا إضافيًا، لكنها توفر ساعات من التصحيح لاحقاً.
إدارة الشهادات بأمان
الشهادة الرقمية (CSID) والمفتاح الخاص يجب حمايتهما بعناية فائقة:
- لا تخزنهما في الكود المصدري
- استخدم Key Vault مثل AWS KMS أو Azure Key Vault
- راقب تاريخ انتهاء الصلاحية وأتمت عملية التجديد
- استخدم Compliance CSID فقط في Sandbox، و Production CSID في الإنتاج
معالجة الأخطاء بذكاء
عند فشل API call، لا ترسل نفس الفاتورة مباشرة. حلل رسالة الخطأ أولاً:
- إذا كان خطأ في البنية (invalid XML)، صححه قبل إعادة المحاولة
- إذا كان timeout، أعد المحاولة مع exponential backoff
- إذا كان خطأ في الشهادة، تحقق من صلاحيتها
- احفظ الأخطاء في logs للتحليل لاحقاً
اختبر Hash Chain بشكل منفصل
سلسلة Hash حرجة جداً. اكتب unit tests تتحقق من:
- أول فاتورة تستخدم PIH = "0"
- كل فاتورة لاحقة تستخدم hash الفاتورة السابقة
- إذا فشلت فاتورة وسط السلسلة، كيف ستتعامل مع ذلك
استخدم قاعدة بيانات موثوقة لحفظ Hash كل فاتورة. إذا فقدت hash واحد، ستنكسر السلسلة.
استخدم Queue للمعالجة غير المتزامنة
في حالة Reporting (B2C)، لست مضطرًا للإرسال فورياً. استخدم نظام Queue مثل RabbitMQ أو Redis Queue لمعالجة الفواتير في الخلفية. هذا يحسن أداء التطبيق ويسمح بإعادة المحاولة التلقائية عند الفشل.
Checklist قبل الإطلاق إلى بيئة الإنتاج
- ✔ XML Valid حسب UBL 2.1 Schema
- ✔ XML Schema Validation ناجحة
- ✔ Schematron Validation ناجحة
- ✔ التوقيع الرقمي تم التحقق منه محليًا
- ✔ Previous Invoice Hash محفوظ في قاعدة البيانات
- ✔ تم اختبار API في Sandbox بالكامل
- ✔ تم تثبيت Production CSID بشكل صحيح
- ✔ تم اختبار سيناريوهات الفشل (Timeout / Invalid Data)
الأسئلة الشائعة للمطورين حول ZATCA و XML UBL 2.1
ما الفرق بين UUID و Invoice ID في فاتورة UBL؟
Invoice ID هو الرقم التسلسلي الداخلي للفاتورة داخل نظامك (مثل INV-2026-001)، بينما UUID هو معرف فريد عالميًا يتم توليده باستخدام UUID v4 ولا يمكن أن يتكرر أبدًا. ZATCA تعتمد على UUID لضمان عدم ازدواجية الفواتير حتى عبر أنظمة مختلفة.
ماذا يحدث إذا انقطعت سلسلة Hash (Previous Invoice Hash)؟
إذا فُقد أو تم تعديل Hash إحدى الفواتير، ستنكسر السلسلة بالكامل ولن تتمكن من إصدار فواتير جديدة بشكل صحيح. الحل غالبًا يتطلب إعادة Onboarding للنظام والحصول على CSID جديد. لذلك يجب حفظ Hash كل فاتورة في قاعدة البيانات بشكل آمن.
هل يمكن تعديل الفاتورة بعد الحصول على Clearance؟
لا يمكن تعديل الفاتورة بعد حصولها على Clearance من ZATCA. أي تعديل يتطلب إصدار إشعار دائن (Credit Note) ثم إصدار فاتورة جديدة. التعديل المباشر يؤدي إلى عدم تطابق Hash ورفض النظام.
هل يجب استخدام mTLS عند الربط مع ZATCA API؟
نعم، ZATCA تتطلب استخدام HTTPS مع Mutual TLS (mTLS) لضمان المصادقة الثنائية بين النظام وواجهة API. يجب تثبيت الشهادة الرقمية (CSID) في التطبيق وإرسالها مع كل طلب API.
الخلاصة
تطوير تكامل مع ZATCA API باستخدام XML UBL 2.1 ليس مجرد ترجمة بيانات لصيغة XML. يتطلب فهماً عميقاً للمعايير الدولية، آليات التوقيع الرقمي، وإدارة سلاسل Hash. الفوترة الإلكترونية في السعودية باستخدام XML UBL 2.1 تمثل نقلة نوعية في الامتثال الضريبي، وتتطلب من المطورين إتقان تقنيات متقدمة في معالجة XML، التشفير، وأمان البيانات.
النقاط الحرجة تشمل: استخدام Namespaces الصحيحة، إجراء Canonicalization قبل التوقيع، حفظ Previous Invoice Hash بدقة، والتفريق الواضح بين Clearance و Reporting. الاستثمار في فهم هذه التفاصيل التقنية واختبارها بدقة في Sandbox يوفر أسابيع من استكشاف الأخطاء لاحقاً.
استخدم المكتبات المثبتة، افصل المنطق التقني عن منطق الأعمال، واحفظ الشهادات بأمان. الأنظمة المبنية بهذه الطريقة تكون أكثر استقرارًا، أسهل في الصيانة، وأقل عرضة للأخطاء الأمنية. المطورون الناجحون في بناء تكاملات ZATCA هم من يجمعون بين الفهم العميق للمعايير التقنية والاهتمام بالتفاصيل الأمنية والتشغيلية.
لمزيد من الاستفسارات التقنية أو المساعدة في تطوير تكامل مخصص، يمكن التواصل عبر منصة Trelyoon.