Khaled Ahmed
الرئيسية المدوّنة الخلفيه والمعماريه
الخلفيه والمعماريه

ربط الفاتورة الإلكترونية ZATCA المرحلة الثانية مع Laravel

Khaled Ahmed 18 min read

إذا كنت تدير متجراً إلكترونياً أو نظام ERP مبنياً خصيصاً لك ويصدر فواتير داخل السعودية، فاعلم أن المرحلة الثانية من الفاتورة الإلكترونية (ZATCA) ليست إضافة تثبّتها وتنتهي. إنها منظومة توقيع رقمي، ودورة حياة شهادات، واعتماد مباشر على واجهة برمجية حكومية تقف بين نقطة البيع لديك وبين إيصال العميل. أنا خالد أحمد، مطوّر Full Stack من القاهرة، ونفّذت هذا الربط وأصلحته داخل تطبيقات Laravel لعملاء سعوديين. هذا هو المسار كاملاً، بصراحة، بما فيه المواضع التي تنكسر فيها الأمور.

1. هل متجري المبني خصيصاً ملزم قانوناً بالربط مع ZATCA؟

الجواب المختصر: إذا كنت مسجلاً في ضريبة القيمة المضافة في السعودية وتُصدر فواتير ضريبية، فنعم — وعبارة «هذا نظام برمجه لي مطوّر، وليس برنامج محاسبة» ليست إعفاءً.

الهيئة تنظّم الفاتورة، لا فئة البرنامج. الالتزام يقع على المكلّف، وينتقل تلقائياً إلى أي نظام يولّد فواتيره. متجر مبني على Laravel يرسل إيصال PDF فيه سطر ضريبة يُعد «حل توليد فواتير إلكترونية» — أو ما تسميه الهيئة وحدة EGS — سواء أطلقتَ عليه هذا الاسم أم لا. ونقطة البيع كذلك. ولوحة التحكم التي يصدر منها محاسبك فاتورة يدوية كذلك.

هناك عاملان يحددان توقيتك:

  • هل أنت مسجل في ضريبة القيمة المضافة داخل المملكة؟ غير المقيمين خارج نطاق الإلزام، أما المقيمون فداخله.
  • في أي «موجة» ربط أنت؟ الهيئة تطبّق المرحلة الثانية على مجموعات متتالية بحسب الإيرادات الخاضعة للضريبة سنوياً، وتُبلّغ كل مجموعة قبل موعدها بستة أشهر على الأقل.

الحدود انخفضت بلا توقف. الموجة الأولى شملت من تجاوزت إيراداته 3 مليارات SAR اعتباراً من 1 يناير 2023. ومع نهاية 2025 كانت الحدود المعلنة قد نزلت إلى ما يقارب مليون SAR، واستمرت موجات إضافية خلال 2026.

وأقولها بوضوح لأنني أكتب هذا في أغسطس 2026: رقم الموجة الحالي وحدّها هو أكثر معلومة في هذا المقال عرضة للتقادم قبل أن تقرأه. لا تأخذها من مدونة — ولا من مدونتي. افحص رقمك الضريبي في أداة الاستعلام على منصة فاتورة، وأكّد الأمر مع مستشارك الضريبي.

المهم استراتيجياً هو الاتجاه: النزول من 3 مليارات SAR إلى نحو مليون SAR يعني انخفاضاً بمقدار ثلاثة آلاف ضعف — أي نحو ثلاث مراتب عشرية ونصف خلال ثلاث سنوات. إذا كنت نشاطاً تجارياً جاداً على الإنترنت في السعودية فأنت إما داخل النطاق الآن أو ستدخله قريباً. ومهلة الستة أشهر تبدو كريمة حتى تكتشف أن جدول الفواتير لديك لا يحتوي على عدّاد غير قابل للتصفير، وأن المبالغ مخزّنة كأرقام عشرية عائمة.

الاختبار العملي: إذا كان نظامك قادراً على إنتاج مستند يمكن لعميل سعودي أن يسلّمه لمحاسبه لاسترداد الضريبة، فهذا المستند داخل النطاق. ابنِ للمرحلة الثانية الآن، حتى قبل وصول خطاب الموجة.

2. ما الفرق الحقيقي بين المرحلة الأولى والمرحلة الثانية؟

المرحلة الأولى — «الإصدار» — إلزامية على جميع المكلفين المقيمين منذ 4 ديسمبر 2021، ومتطلباتها متواضعة عمداً:

  • أن تُصدر الفاتورة إلكترونياً وبصيغة منظمة. لا فواتير مكتوبة بخط اليد، ولا ملفات Word، ولا جداول Excel تُحرَّر يدوياً.
  • اللغة العربية إلزامية على الفاتورة. يمكنك إضافة الإنجليزية، لكن لا يمكن غياب العربية.
  • الفواتير المبسطة (للأفراد) تحتاج QR code يحمل خمسة حقول: اسم البائع، ورقمه الضريبي، والتاريخ والوقت، والإجمالي شامل الضريبة، وقيمة الضريبة — مُرمَّزة بصيغة TLV ثم base64.
  • لا اتصال بالهيئة إطلاقاً. لا شيء يغادر خادمك.

المرحلة الثانية — «الربط والتكامل» — شيء آخر تماماً. تضيف أربعة متطلبات صلبة:

  1. صيغة XML محددة: معيار UBL 2.1 مع امتدادات الهيئة الخاصة بالمملكة، وليس «XML صممناه بأنفسنا».
  2. ختم تشفيري: كل فاتورة تُوقَّع بمفتاح خاص موجود على خادمك، بشهادة صادرة من الهيئة.
  3. اعتماد مباشر على واجهة برمجية: الفواتير الضريبية يجب أن تُجاز (Clearance) من الهيئة قبل تسليمها للمشتري، والفواتير المبسطة تُبلَّغ خلال 24 ساعة.
  4. مقاومة العبث: لا تصدير للمفتاح، ولا تصفير لعدّاد الفواتير، ولا تلاعب بالساعة، ولا ختم يمكن للمستخدم تعديله.
المحورالمرحلة الأولى (الإصدار)المرحلة الثانية (الربط)
سارية منذ4 ديسمبر 2021 على الجميع1 يناير 2023 بنظام الموجات
صيغة الفاتورةأي صيغة إلكترونية منظمةUBL 2.1 XML بامتدادات المملكة
QR codeللمبسطة فقط، 5 حقول TLVللمبسطة، 9 حقول تشمل التوقيع والمفتاح العام
التوقيع الرقميغير مطلوبتوقيع XAdES مضمّن، ECDSA على منحنى secp256k1 بخوارزمية SHA-256
الشهادةلا توجدProduction CSID لكل وحدة EGS عبر منصة فاتورة
التواصل مع الهيئةلا يوجدإجازة فورية أو إبلاغ خلال 24 ساعة
تسلسل الفواتيرغير مطلوبعدّاد ICV متسلسل + hash الفاتورة السابقة (PIH)
الجهد المعتاد على Laravelمن يوم إلى ثلاثةمن 4 إلى 8 أسابيع

الفجوة بين آخر سطرين هي التي تدمّر الميزانيات. كثير من الفرق تسعّر المرحلة الثانية وكأنها «QR code زائد استدعاء API». وهي ليست هذا ولا ذاك.

3. الإجازة مقابل الإبلاغ: مساران ونمطا فشل مختلفان

هذا أهم قرار معماري في المشروع، والخطأ فيه مكلف لأنه يغيّر ما يحدث عند إتمام الطلب.

الفاتورة الضريبية (B2B و B2G) — الإجازة

الفاتورة الضريبية هي الصادرة إلى منشأة مسجلة في الضريبة أو جهة حكومية. ويجب إرسالها إلى الهيئة بشكل متزامن، قبل أن يستلمها المشتري. تتحقق الهيئة من الـ XML، وتضيف ختمها التشفيري و QR الخاص بها، وتعيد لك نسخة مُجازة. النسخة المجازة هي الفاتورة الصحيحة قانوناً؛ أما التي ولّدها نظامك فلا.

معنى ذلك أن إصدار الفاتورة صار معلقاً على استدعاء HTTP لطرف ثالث. إن كانت الهيئة بطيئة فاتورتك بطيئة، وإن كانت متوقفة فلا يمكنك قانوناً إصدار فاتورة ضريبية في تلك اللحظة. تصميمك يجب أن يضع الطلب في طابور، ويعيد المحاولة، ويُظهر الحالة للمستخدم بصدق.

الفاتورة المبسطة (B2C) — الإبلاغ

الفاتورة المبسطة هي إيصال المستهلك المعتاد. تسلّمها للعميل فوراً ومعها QR الخاص بك، ثم تُبلّغ الهيئة بالـ XML خلال 24 ساعة. هذا المسار غير متزامن، وهذه نعمة: ضعه في queue مع إعادة محاولة تصاعدية، ولن تنتظر عملية الشراء لديك الرياض أبداً.

أغلب المتاجر الإلكترونية أنشطة فواتير مبسطة في جوهرها، مع ذيل B2B رفيع. إن كان هذا حالك، فابنِ مسار الإبلاغ أولاً واجعل الإجازة مساراً ثانياً مُقيَّداً — وهو التدرّج نفسه الذي أتبعه في تخطيط أي مشروع متجر إلكتروني مخصص يهيمن فيه مسار واحد على حجم المعاملات.

قاعدة تصميم: لا تستدعِ ZATCA مباشرة داخل طلب HTTP ينتظره عميل — ولا حتى للإجازة. احفظ الفاتورة، وأطلق job، ودع الـ job يتولى الحوار مع الواجهة البرمجية. زمن استجابة متجرك يجب ألا يكون دالة في مزاج بوابة حكومية بعد الظهر.

4. ماذا تحتوي الفاتورة المطابقة فعلياً؟

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

  • UUID — معرّف من النوع v4 لكل فاتورة، منفصل عن رقم الفاتورة الذي يقرأه الإنسان.
  • ICV — عدّاد الفواتير — رقم صحيح متسلسل تماماً لكل وحدة EGS، يبدأ من 1، ولا يجوز تصفيره أبداً. ليس مفتاحك الأساسي إذا كان مفتاحك يقفز أو يُعاد استخدامه. وليس عدّاداً لكل فرع إلا إذا كان الفرع وحدة EGS مسجلة بذاتها.
  • PIH — hash الفاتورة السابقة — قيمة SHA-256 بترميز base64 لنسخة الـ XML المُقنّنة من الفاتورة السابقة، وهي تربط فواتيرك في سلسلة صغيرة تشبه blockchain خاصاً. أما الفاتورة الأولى فتستخدم القيمة الابتدائية المعرّفة من الهيئة، واكتبها ثابتة حرفياً: NWZlY2ViNjZmZmM4NmYzOGQ5NTI3ODZjNmQ2OTZjNzljMmRiYzIzOWRkNGU5MWI0NjcyOWQ3M2EyN2ZiNTdlOQ==. وتأمّل ما هي هذه القيمة، فهي أشهر سبب لفشل الفاتورة الأولى: إنها base64 للنص السداسي عشري المكوّن من 64 حرفاً صغيراً لناتج SHA-256 على الحرف 0، وليست base64 لـ bytes الملخّص الخام. لو رمّزت الـ bytes الخام لحصلت على X+zrZv/IbzjZUnhsbWlsecLbwjndTpG0ZynXOif7V+k=، وهي قيمة مختلفة سترسبك في فحص المطابقة. وكل PIH بعد ذلك هو base64 للـ bytes الخام تماماً كما يحسبه الكود أدناه؛ الاستثناء هو القيمة الابتدائية وحدها.
  • InvoiceTypeCode — القيمة تحدد نوع المستند (388 فاتورة ضريبية، 383 إشعار مدين، 381 إشعار دائن)، أما الخاصية name فهي سلسلة أعلام موضعية من سبعة محارف، أول محرفين فيها 01 للفاتورة الضريبية و02 للمبسطة، تتبعهما خمسة أعلام للطرف الثالث والصورية والتصدير والملخّص والفوترة الذاتية. فالفاتورة الضريبية العادية تُكتب name="0100000" والمبسطة العادية name="0200000". وإن أخرجت ستة محارف بدل سبعة رفض المدقق المستند.
  • مبلغ الضريبة بالريال — حتى لو كانت الفاتورة بعملة أخرى، يجب التعبير عن إجمالي الضريبة بالـ SAR أيضاً.
  • التقريب — منزلتان عشريتان، بثبات. مجاميع البنود والضرائب الفرعية والمبلغ المستحق يجب أن تتطابق حسابياً بدقة وإلا رُفض المستند.

هذا الثنائي — ICV و PIH — هو السبب في استحالة تركيب المرحلة الثانية على نظام طبقة بياناته مهلهلة. أنت بحاجة إلى عدّاد دائم، بلا فجوات، وآمن أمام التزامن، وإلى مؤشر موثوق لآخر فاتورة وُلّدت بنجاح. لو أصدر عاملان الفاتورة رقم 4,102 في اللحظة ذاتها، انكسرت السلسلة وورثت كل فاتورة تالية هذا الكسر. تناولت الأنماط الأساسية بتوسع في دليلي عن تصميم قواعد البيانات لتطبيقات الويب، لكن الخلاصة في Laravel: جدول عدّادات مستقل، وSELECT ... FOR UPDATE داخل transaction، ولا تستخدم MAX(id)+1 أبداً.

// تخصيص ICV آمن أمام التزامن لوحدة EGS واحدة
DB::transaction(function () use ($egsUnitId, $invoice) {
    $counter = DB::table('egs_counters')
        ->where('egs_unit_id', $egsUnitId)
        ->lockForUpdate()
        ->first();

    $icv = $counter->last_icv + 1;

    DB::table('egs_counters')
        ->where('egs_unit_id', $egsUnitId)
        ->update(['last_icv' => $icv]);

    $invoice->update([
        'icv'  => $icv,
        'pih'  => $counter->last_invoice_hash,
        'uuid' => (string) Str::uuid(),
    ]);
});

5. الختم التشفيري: الـ hash والتوقيع و QR

هنا تفشل التنفيذات فعلياً، وهنا تحرق شركة «نحن نعمل تكاملات API» ثلاثة أسابيع من مالك.

حساب الـ hash

hash الفاتورة هو SHA-256 بترميز base64 لنسخة الـ XML المُقنّنة (canonicalized)، ويُحسب بعد إزالة ثلاثة عناصر: كتلة ext:UBLExtensions، وكتلة cac:Signature، وعنصر cac:AdditionalDocumentReference الذي قيمة cbc:ID فيه هي QR. تُزال لأنها إما تحتوي التوقيع أو تعتمد عليه.

والتقنين (canonicalization) هو مقبرة المشاريع. مواصفة الهيئة تنص على C14N 1.1، بينما دالة DOMDocument::C14N() في PHP تنفّذ C14N 1.0. عملياً، ولمجموعة العناصر التي تستخدمها الهيئة، تنتج الطريقتان الـ bytes نفسها — لم أصادف اختلافاً حقيقياً — لكن إن اختلف الـ hash لديك وبدا كل شيء آخر سليماً فابدأ من هنا، ومعها ثلاثة أسباب أكثر شيوعاً بكثير: formatOutput = true يعيد ترتيب المسافات، أو وجود BOM، أو تسلل نهايات أسطر Windows إلى القالب.

$doc = new DOMDocument();
$doc->preserveWhiteSpace = true;
$doc->formatOutput = false;          // لا تجعلها true أبداً
$doc->loadXML($invoiceXml);

$xpath = new DOMXPath($doc);
$xpath->registerNamespace('ext', 'urn:oasis:names:specification:ubl:schema:xsd:CommonExtensionComponents-2');
$xpath->registerNamespace('cac', 'urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2');
$xpath->registerNamespace('cbc', 'urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2');

$remove = array_merge(
    iterator_to_array($xpath->query('//ext:UBLExtensions')),
    iterator_to_array($xpath->query('//cac:Signature')),
    iterator_to_array($xpath->query("//cac:AdditionalDocumentReference[cbc:ID='QR']"))
);

foreach ($remove as $node) {
    $node->parentNode->removeChild($node);
}

$canonical   = $doc->C14N(false, false);
$invoiceHash = base64_encode(hash('sha256', $canonical, true));
// هذه القيمة هي PIH للفاتورة التالية.
// أما PIH الفاتورة الأولى في السلسلة فلا يُحسب أصلاً — إنه ثابت
// الهيئة الابتدائي، وهو base64 للنص السداسي عشري:
// NWZlY2ViNjZmZmM4NmYzOGQ5NTI3ODZjNmQ2OTZjNzljMmRiYzIzOWRkNGU5MWI0NjcyOWQ3M2EyN2ZiNTdlOQ==

التوقيع

تستخدم الهيئة ECDSA على منحنى secp256k1 مع SHA-256، داخل توقيع XAdES مضمّن. اختيار secp256k1 يفاجئ الكثيرين لأنه منحنى Bitcoin وليس NIST P-256 المعتاد في البنى المؤسسية. قبل أن تعد بموعد تسليم، تأكد أن خادمك يدعمه فعلاً:

openssl ecparam -list_curves | grep secp256k1

أغلب نسخ OpenSSL الشائعة تتضمنه، لكن بعض صور Docker المصغّرة أو المشددة أمنياً تحذفه، واكتشاف ذلك يوم الإطلاق كارثة. افحصه داخل صورة الحاوية، لا على جهازك فقط.

الفخ الثاني الشهير هو digest كتلة SignedProperties. يجب أن تحسب الـ hash على الكتلة المُقنّنة تماماً كما ستظهر في المستند النهائي، بما في ذلك مسافات الإزاحة. التنفيذ المرجعي لدى الهيئة يُخرج هذه الكتلة بمسافات محددة، فإن قام مُولّد الـ XML لديك بتوحيدها، حُسب الـ digest على bytes مختلفة عن التي يحسبها المدقق، وستحصل على خطأ توقيع لا يخبرك بشيء تقريباً. إذا كنت تصحّح توقيعاً مرفوضاً وكان hash جسم الفاتورة صحيحاً بالفعل، فابدأ من هنا: أخرج الـ bytes التي حسبت عليها الـ digest وقارنها بالـ bytes الموجودة في المستند النهائي.

رمز QR

الفاتورة المبسطة في المرحلة الثانية تحمل بنية TLV من تسعة حقول، مُرمَّزة base64، ثم تُحوَّل إلى صورة QR. الحقول من 1 إلى 5 هي حقول المرحلة الأولى، والجديد هو 6 إلى 9: hash الفاتورة، وتوقيع ECDSA، والمفتاح العام للختم، وتوقيع جهة إصدار الشهادات على ذلك المفتاح العام في حالة الفواتير المبسطة.

function tlv(int $tag, string $value): string
{
    return chr($tag) . chr(strlen($value)) . $value;
}

// انتبه: الطول byte واحد، ومواصفة TLV لدى الهيئة لا تعرّف أي
// ترميز طول ممتد، فلا يجوز أن يتجاوز أي حقل 255 byte. عملياً لا
// يقترب أي حقل من هذا السقف: الحقل 6 يبلغ 44 byte، والحقول 7 إلى 9
// بين 64 و90 byte. الحقل الذي قد يفاجئك هو 1، اسم بائع عربي طويل
// بترميز UTF-8. تحقق بـ strlen() ولا تقتطع القيمة أبداً.
$qr = base64_encode(
      tlv(1, $sellerName)
    . tlv(2, $vatNumber)
    . tlv(3, $timestampUtcIso8601)
    . tlv(4, $totalWithVat)
    . tlv(5, $vatTotal)
    . tlv(6, $invoiceHash)
    . tlv(7, $ecdsaSignature)
    . tlv(8, $publicKeyDer)
    . tlv(9, $certificateSignature)
);

تفصيلتان يخطئ فيهما الجميع: التاريخ والوقت يجب أن يكونا بتوقيت UTC وبصيغة ISO 8601، والمبالغ نصوص بمنزلتين عشريتين — لا أرقاماً عائمة، ولا أرقاماً عربية-هندية.

6. كيف أحصل على CSID وأسجّل الوحدة في منصة فاتورة؟

التسجيل مصافحة من أربع خطوات لكل وحدة EGS. والوحدة هي جهاز أو نسخة واحدة تصدر الفواتير — جهاز نقطة بيع في فرع، أو خادم، أو عقدة تطبيق. إن كان لديك تطبيق Laravel واحد يصدر كل الفواتير فهذه وحدة واحدة. وإن كان لديك اثنتا عشرة نقطة بيع في الفروع فهذه اثنتا عشرة وحدة.

الخطوة 1 — توليد CSR ومفتاح خاص

محلياً، على الجهاز الذي سيوقّع. الـ CSR يحمل حقولاً خاصة بالهيئة: الرقم الضريبي المكوّن من 15 خانة، ومكانه حقل UID داخل subjectAltName لا حقل المنشأة (O)، ورقم تسلسلي بصيغة 1-{اسم النظام}|2-{الإصدار}|3-{معرّف الجهاز}، ونوع الفاتورة كأربع خانات (1100 لوحدة تصدر النوعين)، والدولة SA، وقطاع النشاط، ومعرّف OID لقالب الشهادة تختلف قيمته باختلاف البيئة.

# مقتطف من openssl.cnf — اسم القالب يتغير بحسب البيئة
[req]
prompt             = no
distinguished_name = dn
req_extensions     = v3_req

[dn]
CN = my-egs-unit-01
OU = Riyadh Branch
O  = My Company LLC
C  = SA

[v3_req]
# TSTZATCA-Code-Signing  = بيئة المطورين (sandbox)
# PREZATCA-Code-Signing  = بيئة المحاكاة (simulation)
# ZATCA-Code-Signing     = بيئة الإنتاج
1.3.6.1.4.1.311.20.2 = ASN1:UTF8String:PREZATCA-Code-Signing
subjectAltName       = dirName:alt_names

[alt_names]
SN = 1-MyERP|2-1.0|3-8f1a2c30-0000-4a00-9f00-1c2d3e4f5a6b
UID = 300000000000003
title = 1100
registeredAddress = King Fahd Road, Riyadh
businessCategory = Retail
openssl ecparam -name secp256k1 -genkey -noout -out ec-private.pem
openssl req -new -sha256 -key ec-private.pem -config openssl.cnf -out egs.csr

الخطوة 2 — طلب Compliance CSID

ادخل إلى منصة فاتورة، اختر تسجيل حل جديد، فتمنحك OTP صالحاً لفترة قصيرة (نحو ساعة بحسب تجربتي — تعامل معه كقصير جداً). أرسل الـ CSR بترميز base64 مع ذلك الرمز:

POST {base}/compliance
OTP: 123456
Accept-Version: V2
Content-Type: application/json

{ "csr": "<base64 لملف egs.csr>" }

تعود لك ثلاث قيم: binarySecurityToken وهو Compliance CSID، وsecret، وrequestID. احفظ الثلاثة. ومن هنا فصاعداً تكون المصادقة HTTP Basic باسم مستخدم هو الـ token وكلمة مرور هي الـ secret.

الخطوة 3 — اجتياز فحوص المطابقة

قبل إصدار شهادة الإنتاج، يجب أن ترسل وحدتك مستندات اختبارية بنجاح إلى /compliance/invoices — ستة مستندات عملياً لوحدة مسجلة للنوعين: فاتورة ضريبية وإشعار دائن وإشعار مدين، ثم فاتورة مبسطة وإشعار دائن وإشعار مدين. كل واحدة موقّعة بشهادة المطابقة ومربوطة بالسلسلة بشكل صحيح.

هذه هي البوابة الحقيقية. كل ما قبلها إجراءات ورقية، وهنا يوقفك hash معطوب أو digest مشوّه.

الخطوة 4 — طلب Production CSID

POST {base}/production/csids
Accept-Version: V2
Authorization: Basic base64(complianceToken:secret)

{ "compliance_request_id": "1234567890123" }

تعود لك شهادة الإنتاج و secret خاص بها، وهما بيانات الاعتماد للإجازة والإبلاغ الحقيقيين. والتجديد يتم بـ PATCH على المسار نفسه مع CSR جديد و OTP جديد. يُتداول أن مدة صلاحية الشهادة خمس سنوات — لكن بدلاً من الوثوق بالرقم، اقرأ حقل notAfter من شهادتك الفعلية واضبط تنبيهاً قبل انتهائها بستين يوماً. انتهاء الشهادة يوقف الفوترة بالكامل.

البيئات الثلاث

البيئةالمسارقالب الشهادةالاستخدام
بوابة المطورين (sandbox)/e-invoicing/developer-portalTSTZATCA-Code-Signingالتطوير المبكر. متساهلة، وتقبل ما سترفضه بيئة الإنتاج.
المحاكاة (simulation)/e-invoicing/simulationPREZATCA-Code-Signingالتجربة النهائية الحقيقية. القواعد نفسها بلا أثر قانوني.
الإنتاج/e-invoicing/coreZATCA-Code-Signingالفواتير الحقيقية.

الثلاث تقع تحت بوابة gw-fatoora.zatca.gov.sa. ولا تتخطَّ بيئة المحاكاة. بوابة المطورين متسامحة بطريقة تمنحك ثقة كاذبة — رأيت فواتير تمر في بيئة المطورين بسلاسة ثم تُرفض من أول محاولة في المحاكاة. خصّص أسبوعاً كاملاً في المحاكاة ببيانات تشبه بيانات الإنتاج.

7. الربط داخل تطبيق Laravel قائم

البنية النظيفة هي خط معالجة يُعزل فيه استدعاء الواجهة البرمجية في آخر خطوة خلف queue. الخطوات إجمالاً:

  1. احفظ الفاتورة أولاً. قاعدة بياناتك هي مصدر الحقيقة، لا استجابة الهيئة.
  2. خصّص ICV و PIH داخل transaction بقفل، كما في المثال السابق.
  3. ابنِ UBL XML من قالب، بلا أي تنسيق جمالي.
  4. احسب الـ hash، ووقّع، وابنِ QR، ثم خزّن الـ XML الموقّع على القرص أو في object storage، واحفظ الـ hash لأنك ستحتاجه كـ PIH للفاتورة التالية.
  5. أطلق job يرسل إلى مسار الإجازة أو الإبلاغ حسب نوع الفاتورة.
  6. سجّل الاستجابة حرفياً — الحالة، والتحذيرات، والأخطاء، والـ XML المُجاز إن وُجد — بجانب سجل الفاتورة.
// إجازة فاتورة ضريبية (B2B)
$response = Http::withBasicAuth($csid->token, $csid->secret)
    ->withHeaders([
        'Accept-Version'   => 'V2',
        'Accept-Language'  => 'en',
        'Clearance-Status' => '1',
    ])
    ->timeout(30)
    // أعد المحاولة على أعطال الشبكة وأخطاء البوابة فقط. استدعاء
    // retry(3, 2000) المجرد يعيد المحاولة على 4xx أيضاً، فيرسل
    // فاتورة مرفوضة ثلاث مرات ثم يرمي استثناءً، فلا تصل إلى
    // الخطوة السادسة ولا تسجّل ما قالته الهيئة فعلاً.
    ->retry(3, 2000, function (Throwable $e) {
        return $e instanceof ConnectionException
            || ($e instanceof RequestException
                && $e->response->serverError());
    }, throw: false)
    ->post($base . '/invoices/clearance/single', [
        'invoiceHash' => $invoice->hash,
        'uuid'        => $invoice->uuid,
        'invoice'     => base64_encode($signedXml),
    ]);

وهذه الدالة الشرطية في retry() ليست زينة. فدالة retry() في Laravel ترمي استثناءً داخلياً بين المحاولات، ما يعني أن السلوك الافتراضي يعامل استجابة 400 NOT_CLEARED — وهي حكم مدروس لا عطل عابر — معاملة انقطاع الاتصال. وthrow: false لا تقل أهمية: بدونها يرمي استنفاد المحاولات استثناء RequestException، فلا يُسند $response أصلاً، ولا تُنفَّذ خطوة «سجّل الاستجابة حرفياً» على الفواتير التي تحتاج دليلها أكثر من غيرها.

أما الفاتورة المبسطة فالمسار /invoices/reporting/single مع حذف ترويسة Clearance-Status، وشكل الجسم نفسه.

ملاحظات عملية من التكرار: احتفظ بمنطق التوقيع داخل service class مستقل عن إطار العمل وبلا اعتماد على Eloquent، لأنك ستحتاج اختباره بـ unit tests مقابل نماذج الفواتير المنشورة من الهيئة، وهذا صعب من خلال model. استخدم queue مخصصاً بتزامن منخفض حتى تُرسل الفواتير بترتيب الـ ICV. وسجّل جسم كل طلب واستجابة، لأن رسالة الخطأ وحدها بعد ستة أسابيع لن تخبرك بما أرسلته. هذه هي مبادئ الانضباط نفسها التي أدافع عنها في مقالي عن أفضل ممارسات تصميم واجهات API: مفاتيح idempotency، وحفظ الاستجابات حرفياً، وفصل واضح بين «سجّلناها» و«قبلوها».

وأمران يستحقان ميزانية مستقلة. الأول: المفتاح الخاص بيانات اعتماد توقيع قانوني، ومتطلبات مقاومة العبث تعني ألا يوجد مسار تصدير متاح للمستخدم، وألا يقبع المفتاح في مستودع الكود. شفّره في التخزين، وشدّد صلاحيات الملفات، وأبعده عن النسخ الاحتياطية التي تغادر المملكة. والثاني: المملكة لديها متطلبات بشأن مكان حفظ أرشيف الفواتير، ومدد حفظ السجلات الضريبية تمتد عموماً إلى ست سنوات لمعظم السجلات وأطول للأصول الرأسمالية والعقارات. هذا قرار استضافة لا قرار برمجة، وتحقق من المدد السارية مع مستشارك الضريبي قبل اختيار المنطقة. وقد غطيت جانب حماية المفاتيح بصورة أعم في قائمة فحص أمان الموقع.

8. ماذا يحدث إذا فشلت فواتيري في التحقق؟

ثلاث نتائج، والفرق بينها قانوني لا تقني فقط.

الاستجابةالمعنىهل الفاتورة صحيحة؟ماذا تفعل
200 — CLEARED / REPORTEDقُبلت بلا ملاحظاتنعماحفظ الـ XML المُجاز وسلّمه للمشتري
202 — قُبلت مع تحذيراتمقبولة لكن الهيئة رصدت ملاحظات غير حاجبةنعمصحيحة قانوناً، لكن عالج التحذيرات — تتحول إلى أخطاء في إصدارات المواصفة اللاحقة
400 — NOT_CLEARED / NOT_REPORTEDمرفوضة لمخالفة قواعد العمل أو الصيغةلاالمستند ليس فاتورة نظامية. صحّح وأعد الإرسال
401شهادة غير صالحة أو منتهيةلا ينطبقافحص انتهاء الشهادة وترميز Basic auth
5xx أو انتهاء المهلةمشكلة في البوابة لا لديكغير معروفأعد المحاولة تصاعدياً، ولا تفترض الرفض أبداً

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

وحالة 400 هي الخطرة في الفواتير الضريبية. إن فشلت الإجازة فليس لديك فاتورة نظامية تسلّمها للمشتري، ولا يجوز إرسال النسخة غير المُجازة وتسوية الأمر لاحقاً. نظامك يحتاج حالة ظاهرة اسمها «بانتظار الإجازة» وتنبيهاً تشغيلياً، لا job فاشلاً بصمت.

أما الغرامات: نظام ضريبة القيمة المضافة السعودي ينص على غرامات لعدم إصدار الفواتير أو حفظها بالشكل الصحيح، ويُتداول نطاق يبدأ من نحو 5,000 SAR ويصل إلى 50,000 SAR بحسب المخالفة وتكرارها، وقد اتبعت الهيئة تاريخياً نهج الإنذار والتصحيح في المخالفة الأولى. لست مستشاراً ضريبياً، وجدول الغرامات عُدّل أكثر من مرة، فتعامل مع هذه الأرقام كترتيب حجم واحصل على الوضع الساري من مختص ضريبي سعودي. لكن الخلاصة الهندسية ثابتة: الربط المكسور ليس عطلاً يكلفك تذكرة دعم، بل عطلاً يكلفك غرامة وعميلاً لا يستطيع استرداد ضريبته.

ابنِ لوحة متابعة للمطابقة من اليوم الأول: كل فاتورة، وحالة إرسالها، ورمز الاستجابة، وزر واحد لإعادة الإرسال. ليست رفاهية. هي الفرق بين اكتشاف خلل منهجي خلال عشرين دقيقة واكتشافه في الإقرار الضريبي الربعي.

9. كم يستغرق الربط على تطبيق Laravel قائم؟

بافتراض تطبيق Laravel مبني بشكل معقول، يصدر فواتير بالفعل، ونموذج الفاتورة لديه نظيف، والمبالغ مخزّنة كـ decimal لا كـ float، تقديري الصادق هو من أربعة إلى ثمانية أسابيع عمل مركّز — أي من 22 إلى 38 يوم عمل، وهذا توزيعها:

  • الاستكشاف وتدقيق البيانات — من 3 إلى 5 أيام. هنا تكتشف أن الإشعارات الدائنة نُفّذت كفواتير بقيم سالبة، أو أن أربعة فروع تتشارك تسلسلاً واحداً.
  • تعديلات نموذج البيانات — عدّادات ICV، وحفظ PIH، وعمود UUID، وأرشفة الـ XML والاستجابات — من 3 إلى 5 أيام.
  • توليد UBL والتوقيع — من 5 إلى 10 أيام، وهي الكتلة الأكبر.
  • التسجيل وفحوص المطابقة في بيئتي المطورين والمحاكاة — من 5 إلى 8 أيام، شاملة انتظار صلاحيات المنصة ورموز OTP.
  • أدوات الإدارة والمراقبة وإعادة المحاولة — من 3 إلى 5 أيام.
  • التسجيل في الإنتاج والتشغيل المتوازي — من 3 إلى 5 أيام.

أضف كثيراً إن انطبق أي مما يلي: فروع أو وحدات EGS متعددة، أو مجموعة ضريبية، أو فوترة بعملات متعددة، أو ترقيم فواتير قائم به فجوات، أو إشعارات دائنة ومدينة لا تشير إلى فاتورتها الأصلية، أو قاعدة بيانات قديمة يتوزع فيها مفهوم «الفاتورة» على ثلاثة جداول. وفي منصة SaaS متعددة المستأجرين حيث يحتاج كل مستأجر شهادته وعدّاده، يتحول الأمر من مهمة ربط إلى خاصية منصة، ويتضاعف الجدول الزمني تقريباً.

والمتغير الذي لا يقدّره أحد بشكل صحيح هو صلاحية الدخول إلى المنصة. الحصول على تفويض الشخص المناسب لدى العميل على منصة فاتورة، ببيانات الاعتماد الضريبية الصحيحة، ليولّد OTP في اللحظة التي يحتاجها فيها مطورك — يستغرق عادة وقتاً أطول من كتابة كود التوقيع نفسه. ابدأه في الأسبوع الأول.

10. التكلفة

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

النطاقالمدى (SAR)المدى (USD)الافتراضات
المرحلة الأولى فقط (QR + العربية + الإصدار المنظم)4,000 – 9,0001,050 – 2,400نموذج فاتورة نظيف، منشأة واحدة
المرحلة الثانية، وحدة EGS واحدة، تطبيق Laravel نظيف22,000 – 45,0005,900 – 12,000رقم ضريبي واحد، فرع واحد، النوعان، decimal لا float
المرحلة الثانية بفروع أو كيانات متعددة50,000 – 110,00013,300 – 29,300عدة وحدات EGS، شهادة وعدّاد لكل وحدة، احتمال مجموعة ضريبية
المرحلة الثانية داخل منصة SaaS متعددة المستأجرين90,000 – 200,000+24,000 – 53,300+تجربة تسجيل لكل مستأجر، دورة حياة شهادات، مراقبة على مستوى المستأجر
وسيط معتمد من الهيئة (سنوياً)3,000 – 25,000 سنوياً + رسم لكل فاتورة800 – 6,700 سنوياًيضاف إليه عمل الربط معه، عادة من 5 إلى 15 يوماً
الصيانة وتحديثات المواصفة500 – 2,000 شهرياً135 – 535 شهرياًتجديد الشهادات، ترقية إصدار المواصفة، معالجة التحذيرات

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

11. أبني داخلياً، أم أشترك مع مزوّد معتمد، أم أوظّف مختصاً؟

أنا أكسب رزقي من بناء هذا، ومع ذلك سأخبرك متى لا يجب أن توظّفني.

اشترك مع مزوّد حل معتمد من الهيئة إذا كانت فوترتك نمطية، وحجمك متوسطاً، ولا يوجد في نموذج منتجك شيء غير معتاد. تدفع اشتراكاً، ويتحمل هو تحديثات المواصفة، وتتكامل مع واجهة برمجية أرحم بكثير من واجهة الهيئة المباشرة. وهذا هو الجواب الصحيح أكثر مما يحب المطورون الاعتراف.

ابنِ داخلياً أو وظّف مختصاً حين يكون منطق فوترتك مخصصاً فعلاً — أسواق إلكترونية بتسويات مقسّمة، أو اشتراكات بحسابات تناسبية، أو خليط من B2B و B2C، أو هويات ضريبية لكل مستأجر — أو حين تتجاوز رسوم الفاتورة الواحدة عند حجمك تكلفة البناء خلال ثمانية عشر شهراً. وكل حالة يجبرك فيها المزوّد العام على تشويه نموذج نطاقك هي حالة بناء.

وإن قررت البناء فلست مضطراً للبدء من ملف فارغ. حتى أغسطس 2026 توجد على Packagist عدة حزم PHP مستخدمة فعلياً. حزمة salla/zatca هي الأكثر تحميلاً بفارق كبير — نحو 470,000 تثبيت مقابل قرابة 23,000 لكل من البديلتين — ويجدر تصحيح سمعتها بوصفها «حزمة QR»: فهي إلى جانب توليد QR تتضمن صنف InvoiceSign للتوقيع بـ XAdES وأدوات لتوليد الـ CSR أثناء التسجيل، أي أنها تمتد إلى المرحلة الثانية ولا تتوقف عند الأولى. لكنها لا تبني مستند UBL نيابة عنك ولا تغلّف مساري الإجازة والإبلاغ. أما saleh7/php-zatca-xml وsevaske/zatca-api فأوسع نطاقاً، إذ تغطيان توليد UBL والتوقيع بـ XAdES واستدعاءات الواجهة البرمجية. جميعها مجتمعية وغير رسمية. اقرأ الشيفرة قبل أن تعتمد على أي منها، وتحقق من آخر تحديث لها مقابل إصدار المواصفة الحالي، لأن تنفيذ توقيع قديم أسوأ من لا شيء.

ولا تسلّم هذا لعامّي WordPress ولا لفريق معرضه صفحات هبوط. منظومة التوقيع لا ترحم، ورسائل الخطأ مقتضبة، ودورة التصحيح تتطلب قراءة مواصفة فنية لا إجابة على Stack Overflow. إن كنت تحتاج شخصاً نفّذ هذا فعلاً، فهذه بالضبط الحالة التي أصفها في صفحة توظيف مطور Laravel.

وملاحظة عملية أخيرة للتجار في السعودية والخليج: نادراً ما يكون ربط ZATCA هو الربط التنظيمي الوحيد على خارطة الطريق. يأتي عادة في الربع نفسه مع بوابات الدفع، والاثنان يتفاعلان: الاسترداد يولّد إشعاراً دائناً، والإشعار الدائن مستند خاضع لمتطلبات ZATCA. إن كان هذا حالك، فاقرأ دليلي عن ربط بوابات الدفع الخليجية بجانب هذا المقال، ورتّبهما معاً بدل أن تدفع ثمن مرحلتي استكشاف منفصلتين.

12. الأخطاء الأغلى ثمناً

  • تخزين المبالغ كـ float. إن كانت مجاميعك مخزّنة كأرقام عائمة، فسيرفض التحقق الحسابي لدى الهيئة فواتير بفروق هللة واحدة لا تراها في الواجهة. عالج هذا قبل أي شيء آخر؛ إنه migration لا patch.
  • عدّاد قابل للتصفير أو به فجوات. يجب ألا يتراجع ICV ولا يتكرر، عبر عمليات النشر واستعادة النسخ الاحتياطية وتحديث بيئة staging. استعادة نسخة إنتاج على staging ثم السماح لها بإرسال فواتير كسرت أكثر من سلسلة.
  • التوقيع داخل دورة الطلب. توقيع ECDSA سريع، أما استدعاء الهيئة فلا. ضعه في queue.
  • تخطي بيئة المحاكاة. سبق شرحه. لا تفعل.
  • عدم حفظ الـ XML المُجاز. في الفواتير الضريبية، المستند العائد من الهيئة هو النظامي. إن احتفظت بنسختك فقط فأرشيفك غير مطابق.
  • شهادة واحدة لكل شيء. إن كان لديك أكثر من جهاز يصدر فواتير فكل واحد وحدة EGS بشهادتها وعدّادها. مشاركة شهادة واحدة بين الفروع تمر في بيئة الاختبار وتسقط في التدقيق.
  • اعتبار العربية اختيارية. كانت إلزامية في المرحلة الأولى ولا تزال. اسم البائع بالعربية، والعربية على المستند المطبوع.
  • غياب المراقبة. عامل queue يموت بصمت هو حادثة مطابقة بفتيل مؤجّل.

13. خطة تنفيذ واقعية

إن كنت تبدأ اليوم وأمامك موعد موجة بعد ستة أشهر، فهذا التسلسل الذي أنفّذه:

  1. الأسبوع 1: تأمين صلاحية الدخول إلى منصة فاتورة واختبارها. تدقيق نموذج الفاتورة. قرار البناء أو الاشتراك مع مزوّد معتمد.
  2. الأسبوعان 2–3: migration نموذج البيانات — decimal، وعدّادات ICV، و PIH، و UUID، وأرشيف XML. يُنشر على الإنتاج خلف feature flag دون توليد أي شيء بعد.
  3. الأسابيع 4–6: توليد UBL، والـ hash، والتوقيع، و QR. مع اختبارات وحدة مقابل نماذج المستندات المنشورة من الهيئة قبل أي استدعاء شبكي.
  4. الأسبوع 7: التسجيل في بيئة المطورين وإرسال المستندات الستة.
  5. الأسبوعان 8–9: المحاكاة ببيانات تشبه الإنتاج. هنا تظهر الأخطاء الحقيقية.
  6. الأسبوع 10: التسجيل في الإنتاج وتشغيل متوازٍ — ولّد ووقّع وأرسل كل فاتورة حقيقية، مع إبقاء المسار القديم ظاهراً داخلياً للمقارنة.
  7. الأسبوعان 11–12: التحويل الكامل. مراقبة يومية. تصفية قائمة التحذيرات.

هذا يترك لك ثلاثة أشهر احتياطية أمام إشعار مدته ستة أشهر، وهو تقريباً الاحتياطي الذي تحتاجه، لأن ما يؤخر هذا المشروع لا يكون الكود إلا نادراً.

14. الخطوة التالية

إن كنت تدير متجراً أو نظام ERP مبنياً على Laravel في السعودية وأمامك موعد موجة، فالخطوة المفيدة هي مراجعة فنية قصيرة لما لديك بالفعل: نموذج الفاتورة، والترقيم، ومعالجة الإشعارات الدائنة، وعدد وحدات EGS التي تحتاجها حقاً. تستغرق مني هذه المراجعة ساعة أو ساعتين، وهي الفرق بين مشروع ينتهي في ثمانية أسابيع وآخر يتجاوز الستة عشر.

يمكنك الاطلاع على نوعية ما أنفّذه في معرض أعمالي، وسأعطيك رأياً صريحاً في ما إذا كان الأفضل أن تبني هذا أو تشتريه — بما في ذلك أن أنصحك بشرائه إن كان هذا هو القرار الصحيح.

أرسل لي تفاصيل نظامك وسأعود إليك خلال 24 ساعة باستشارة مجانية وعرض سعر ثابت. بلا اشتراك شهري ملزم، وبلا مفاجآت بالساعة، وببيان واضح لما يشمله النطاق وما لا يشمله.

أسئله شائعه

هل متجري المبني خصيصاً ملزم بالربط مع ZATCA؟
إذا كنت مكلفاً مقيماً ومسجلاً في ضريبة القيمة المضافة في السعودية ونظامك يصدر فواتير ضريبية، فنعم. الهيئة تنظّم الفاتورة لا فئة البرنامج، لذا يُعد متجر Laravel المخصص وحدة EGS تماماً كأي برنامج محاسبة تجاري. وموعد إلزامك يحدده رقم الموجة التي وضعتك فيها الهيئة، وتُبلَّغ قبله بستة أشهر على الأقل.
ما الفرق بين المرحلة الأولى والمرحلة الثانية؟
المرحلة الأولى تشترط فقط إصدار الفاتورة إلكترونياً بصيغة منظمة وباللغة العربية، مع رمز QR من خمسة حقول للفواتير المبسطة. أما المرحلة الثانية فتضيف صيغة UBL 2.1، وختماً تشفيرياً موقّعاً بشهادة صادرة من الهيئة، ورمز QR من تسعة حقول، وربط الفواتير في سلسلة عبر ICV و PIH، واتصالاً مباشراً بواجهة برمجية للإجازة أو الإبلاغ.
كيف أحصل على CSID وأسجّل في منصة فاتورة؟
أربع خطوات لكل وحدة EGS. ولّد مفتاحاً خاصاً على منحنى secp256k1 و CSR يحمل رقمك الضريبي المكوّن من 15 خانة في حقل UID داخل subjectAltName، مع الرقم التسلسلي للجهاز. احصل على OTP من منصة فاتورة وأرسل الـ CSR إلى مسار المطابقة لتحصل على Compliance CSID. أرسل ستة مستندات اختبارية موقّعة إلى مسار فحص الفواتير. ثم بادل رقم الطلب بشهادة الإنتاج.
كم يستغرق الربط على تطبيق Laravel قائم؟
من أربعة إلى ثمانية أسابيع عمل مركّز — أي من 22 إلى 38 يوم عمل — إذا كان التطبيق يصدر فواتير بالفعل، ونموذج الفاتورة نظيف، والمبالغ مخزّنة كـ decimal لا كـ float. أما تعدد الفروع أو المجموعات الضريبية أو العملات، أو قاعدة بيانات قديمة يتوزع فيها مفهوم الفاتورة على جداول عدة، فقد يضاعف المدة. وأكثر تأخير يُستهان به هو الحصول على صلاحية دخول معتمدة إلى منصة فاتورة لدى العميل.
ماذا يحدث إذا فشلت فواتيري في التحقق؟
استجابة 400 تعني NOT_CLEARED أو NOT_REPORTED، والمستند ليس فاتورة نظامية، فعليك تصحيحه وإعادة إرساله لا إعادة المحاولة عمياء. واستجابة 202 تعني القبول مع تحذيرات، وهي صحيحة قانوناً لكن يجب معالجة الملاحظات قبل أن تتحول إلى أخطاء حاجبة. أما 5xx فهي مشكلة في البوابة، فأعد المحاولة تصاعدياً ولا تعتبرها رفضاً.
ما القيمة الصحيحة لـ PIH في أول فاتورة؟
هي الثابت NWZlY2ViNjZmZmM4NmYzOGQ5NTI3ODZjNmQ2OTZjNzljMmRiYzIzOWRkNGU5MWI0NjcyOWQ3M2EyN2ZiNTdlOQ==، وهو base64 للنص السداسي عشري المكوّن من 64 حرفاً صغيراً لناتج SHA-256 على الحرف 0، وليس base64 لـ bytes الملخّص الخام. لو رمّزت الـ bytes الخام لحصلت على X+zrZv/IbzjZUnhsbWlsecLbwjndTpG0ZynXOif7V+k=، وهي قيمة خاطئة ترسب في فحص المطابقة. وكل PIH لاحق هو base64 للـ bytes الخام؛ الاستثناء هو القيمة الابتدائية وحدها.
هل أستخدم حزمة PHP جاهزة بدل البناء من الصفر؟
جزئياً. حتى أغسطس 2026 تشمل الخيارات المستخدمة على Packagist حزمة salla/zatca وهي الأكثر تحميلاً بفارق كبير، وتغطي توليد QR إضافة إلى التوقيع بـ XAdES وتوليد الـ CSR لكنها لا تبني مستند UBL، ثم حزمتي saleh7/php-zatca-xml و sevaske/zatca-api اللتين تضيفان توليد UBL واستدعاءات الإجازة والإبلاغ. جميعها مجتمعية وغير رسمية، لذا اقرأ الشيفرة وتحقق من تاريخ آخر تحديث مقابل إصدار المواصفة الحالي قبل الاعتماد عليها.
هل أحتاج شهادة منفصلة لكل فرع؟
كل جهاز أو نسخة تصدر فواتير تُعد وحدة EGS مستقلة، وكل وحدة تحتاج CSR خاصاً بها، وشهادة إنتاج خاصة بها، وعدّاد ICV غير قابل للتصفير خاصاً بها. تطبيق Laravel واحد يصدر كل الفواتير هو وحدة واحدة، واثنتا عشرة نقطة بيع في الفروع هي اثنتا عشرة وحدة. ومشاركة شهادة واحدة بين الفروع قد تمر في بيئة الاختبار لكنها تسقط في التدقيق.
كلمات مفتاحية: ZATCALaravelE-InvoicingSaudi ArabiaComplianceAPI IntegrationEcommercePHP

مستعد لتطبيق ما قرأته؟

استشاره مجانية 30 دقيقة، رد خلال 24 ساعة، وعرض سعر مكتوب.

تواصل واتساب