هذا الدليل موجّه للمطوّرين وفِرَق التكامل التقني الذين يبنون الربط مع منصة فاتورة بلغة Java ضمن الفاتورة الإلكترونية في السعودية. موضوعه الوحيد: كيف تكتب كود Java فعليًا يصادق على واجهات الهيئة، ويحسب تجزئة الفاتورة، ويرمّز حمولة UBL، ويرسل الطلب إلى المقاصة أو الإبلاغ، ثم يقرأ الاستجابة ويتعامل مع الأخطاء. كل مقطع هنا قابل للنسخ والتشغيل بعد ضبط بيانات الاعتماد الخاصة بمنشأتك.
نعتمد في كل الأمثلة على المكتبة القياسية في Java دون أي اعتماد خارجي. عميل HTTP هو java.net.http.HttpClient المضمّن منذ الإصدار الحادي عشر، والتجزئة عبر java.security.MessageDigest، والترميز عبر java.util.Base64. هذا يبقي الكود نظيفًا وسهل الدمج في أي مشروع Java أو Spring دون إضافة تبعيات ثقيلة.
نفترض أنك قرأت بالفعل الأدلة المفاهيمية الثلاثة التي يبني عليها هذا الدليل. إن لم تكن، فابدأ بدليل المصادقة (Authentication) في واجهة الفوترة لتفهم بنية ترويسة Basic، ثم دليل نقاط نهاية API (Endpoints) لمنصة فاتورة لتعرف الوجهات والمنافذ، ثم دليل إرسال الفاتورة (Invoice Submission) عبر API لتفهم تسلسل الإرسال. هذا الدليل يحوّل تلك المفاهيم إلى كود Java جاهز.
نقطة محورية نضعها أمامك من البداية: واجهات الفوترة لا تستخدم OAuth. لن تجد في كود Java هنا أي طلب لرمز وصول قبل الإرسال. تبني ترويسة المصادقة محليًا من شهادة الختم التشفيري (CSID) وسرّها في كل طلب. أبقِ هذه الحقيقة في ذهنك، فهي تشكّل بنية العميل الذي ستكتبه.
هيكل مشروع التكامل بلغة Java
قبل أي سطر، رتّب مكوّنات نظامك في ذهنك. تكامل واجهة الفوترة بلغة Java يتألف من أربع طبقات منطقية. طبقة بيانات الاعتماد تقرأ معرّف CSID والسرّ من مصدر آمن. طبقة الأدوات المساعدة تبني ترويسة Basic، وتحسب التجزئة، وترمّز UBL. طبقة العميل ترسل الطلب عبر HttpClient. طبقة معالجة الاستجابة تقرأ JSON وتتعامل مع الأخطاء وإعادة المحاولة.
هذا الفصل بين الطبقات ليس ترفًا تصميميًا. تجديد شهادة CSID لاحقًا يصبح تغيير قيمة في طبقة واحدة. تغيّر منفذ الهيئة يصبح تعديلًا في طبقة العميل وحدها. اختبار بناء التجزئة يصبح ممكنًا دون إرسال طلب فعلي. ابنِ كل طبقة في صنف Java مستقل من البداية.
انتبه إلى تسمية الشهادة بدقة في كل أسمائك البرمجية: CSID وليس CCSI. الاختصار يعني Cryptographic Stamp Identifier، أي معرّف الختم التشفيري. الخطأ في الترتيب شائع، ويربكك حين تبحث في وثائق الهيئة أو تراجع أسماء المتغيرات في كودك.
بناء ملف UBL والتوقيع
حساب التجزئة SHA-256
ترميز Base64 وبناء ترويسة Basic
POST للمقاصة أو الإبلاغ
قراءة الاستجابة وحفظ النتيجة
بناء ترويسة المصادقة بـ Base64
المصادقة الأساسية في بروتوكول HTTP بسيطة في جوهرها: تصل معرّف CSID بالسرّ بنقطتين رأسيتين، ترمّز السلسلة الناتجة بـ Base64، ثم تضعها في ترويسة Authorization مسبوقة بكلمة Basic. الجديد أن «اسم المستخدم» هو معرّف الشهادة، و«كلمة المرور» هي السرّ المرافق لها. الصيغة العامة على هذا النحو:
في Java تبني هذه القيمة بسطرين عبر java.util.Base64. لاحظ أن النقطتين الرأسيتين جزء من المدخل قبل الترميز لا بعده، وأن الترميز يجري على البايتات بترميز UTF-8 صراحة لتفادي اختلاف الترميز الافتراضي بين البيئات:
تفصيل دقيق يخطئ فيه كثيرون: قيمة معرّف الشهادة نفسها مُرمَّزة بـ Base64 أصلًا لأنها بيانات شهادة ثنائية. هذا يعني أنك ترمّز شيئًا مرمّزًا، أي طبقتا Base64. الطبقة الأولى للشهادة كما تسلّمتها من الهيئة، والطبقة الثانية لسلسلة «المعرّف:السرّ» كاملة كما تتطلب المصادقة الأساسية. لا تفكّ ترميز المعرّف قبل بنائه في الترويسة.
تستدعي هذه الدالة مرة واحدة عند تهيئة العميل، أو قبل كل طلب إن كنت تقرأ بيانات الاعتماد من مخزن أسرار يتغيّر. لا تطلب رمز وصول من أي نقطة نهاية قبل ذلك. تبني الترويسة محليًا وترسل الطلب مباشرة، وهذا جوهر الفرق العملي بين Basic وOAuth في تصميم عميل Java.
سبب تحديد ترميز UTF-8 صراحة في getBytes جدير بالتوضيح. لو تركت Java تختار الترميز الافتراضي للنظام، قد يختلف الناتج بين خادم لينكس وجهاز تطوير ويندوز، فينتج token مختلف يردّه الخادم برمز 401 على بيئة دون أخرى. تثبيت StandardCharsets.UTF_8 يلغي هذا المصدر الخفي للأخطاء التي يصعب تتبّعها لأنها تعمل محليًا وتفشل في الإنتاج.
أبقِ الصنف AuthHeader نهائيًا (final) بمنشئ خاص، فهو حاوية لدالة ساكنة لا حالة له. هذا النمط للأصناف المساعدة يمنع إنشاء نسخة منه بلا داعٍ، ويوضّح لمن يقرأ الكود أن وظيفته بناء قيمة لا حمل حالة. طبّق النمط نفسه على بقية الأصناف المساعدة مثل InvoiceHash وUblEncoder.
حساب تجزئة الفاتورة بـ SHA-256
كل فاتورة في المرحلة الثانية تحمل تجزئة (invoiceHash) محسوبة على مستند UBL الخاص بها بخوارزمية SHA-256. هذه التجزئة جزء من سلسلة التجزئة التي تربط كل فاتورة بسابقتها، وتُرسل إلى الهيئة ضمن الحمولة. في Java تحسبها عبر java.security.MessageDigest دون أي مكتبة خارجية.
الناتج المطلوب من الهيئة هو تجزئة SHA-256 مُرمَّزة بـ Base64، لا بصيغة hex. انتبه لهذه النقطة، فحساب التجزئة ثم تحويلها إلى hex بدل Base64 خطأ شائع يجعل الهيئة ترفض الفاتورة رغم صحة الحساب نفسه. المقطع التالي يحسب التجزئة على البايتات الخام لمستند UBL ويعيدها بصيغة Base64:
نقطة جوهرية في حساب التجزئة: احسبها على المستند بالصيغة نفسها التي سترسلها بالضبط، حرفًا بحرف. أي مسافة بيضاء زائدة، أو سطر جديد مختلف، أو ترتيب عناصر مغاير بعد حساب التجزئة، ينتج تجزئة لا تطابق المستند المرسل، فترفضه الهيئة. ثبّت تطبيع المستند قبل التجزئة، ثم لا تلمسه بعدها.
استثناء NoSuchAlgorithmException هنا نظري عمليًا، لأن SHA-256 مضمونة في كل بيئة Java قياسية. لكن التقاطه إلزامي لأن التوقيع يفرضه. حوّله إلى استثناء غير مفحوص واضح الرسالة بدل تجاهله، حتى لو كنت واثقًا أنه لن يقع.
كائن MessageDigest ليس آمنًا للاستخدام من خيوط متعددة، لأنه يحمل حالة داخلية تتراكم مع كل استدعاء digest. لهذا أنشأنا نسخة جديدة منه داخل الدالة في كل مرة بدل مشاركة نسخة واحدة. لو احتجت أداءً أعلى في إصدار آلاف الفواتير، استخدم ThreadLocal لكل خيط بدل نسخة مشتركة، لكن لا تشارك نسخة واحدة بين خيوط أبدًا.
تذكّر أن تجزئة الفاتورة الأولى في كل بيئة تأخذ قيمة بداية ثابتة، ثم تربط كل فاتورة لاحقة بتجزئة سابقتها لتكوّن سلسلة متصلة. هذا الترابط يجعل أي تلاعب بفاتورة وسطى مكتشفًا، لأنه يكسر السلسلة. كودك يحسب التجزئة، لكن إدارة تسلسلها وحفظ التجزئة السابقة مسؤولية طبقة التخزين في نظامك، فصمّمها لتحفظ تجزئة آخر فاتورة لكل بيئة.
invoiceHash: تجزئة الفاتورة SHA-256 (Base64)
uuid: المعرّف الفريد للفاتورة
invoice: الفاتورة بترميز Base64
ترميز مستند UBL بـ Base64
تتطلب واجهات المقاصة والإبلاغ أن ترسل مستند UBL مُرمَّزًا بـ Base64 داخل حمولة JSON، لا كنص XML خام. السبب أن XML يحوي محارف قد تكسر بنية JSON، فالترميز يضمن نقلًا آمنًا للمستند كاملًا. في Java الترميز سطر واحد عبر Base64 نفسه الذي استخدمناه للمصادقة:
هذا المستند المرمّز هو قيمة الحقل invoice في حمولة الطلب. إلى جانبه ترسل قيمة invoiceHash التي حسبتها في الخطوة السابقة، ومعرّف الفاتورة الفريد uuid. لاحظ أن التجزئة تُحسب على المستند الأصلي قبل ترميزه بـ Base64، لا على المستند المرمّز. هذا ترتيب يخطئ فيه كثيرون فينتج عدم تطابق.
لبناء جسم الطلب بصيغة JSON دون مكتبة خارجية، يكفي قالب نصي بسيط لأن الحقول معروفة ومحدودة. إن كان مشروعك يستخدم مكتبة JSON مثل Jackson فاستخدمها، لكن للوضوح هنا نبني السلسلة يدويًا مع تهريب القيم:
نص الكتلة (text block) المتوفّر منذ الإصدار الخامس عشر من Java يجعل بناء قالب JSON مقروءًا. إن كنت على إصدار أقدم، استخدم String.format مع تسلسل عادي. النتيجة واحدة: حمولة JSON تحمل المعرّف والتجزئة والمستند المرمّز جاهزة للإرسال.
إرسال الطلب عبر HttpClient
الآن نجمع الطبقات في طلب فعلي. عميل java.net.http.HttpClient هو المكوّن الأساسي. تبني HttpRequest يحمل المنفذ الكامل، وترويسة Authorization من الدالة basic، وترويسة Accept-Version الإلزامية بقيمة V2، وترويسة Content-Type بقيمة application/json، ثم جسم الطلب. المقطع التالي يرسل فاتورة B2B إلى منفذ المقاصة:
لاحظ ترويسة Clearance-Status بقيمة 1 الخاصة بطلبات المقاصة، وهي تطلب من الهيئة مقاصة الفاتورة فورًا قبل تسليمها للمشتري. أما طلبات الإبلاغ لفواتير B2C فتذهب إلى منفذ reporting/single دون هذه الترويسة، لأن الإبلاغ يجري خلال 24 ساعة بعد الإصدار لا قبل التسليم.
اجعل ترويستي Accept-Version وContent-Type جزءًا ثابتًا من بناء كل طلب، تمامًا كما هما هنا. نسيان Accept-Version في نقطة نهاية واحدة سبب شائع لأخطاء يصعب تتبّعها، لأن البوابة قد تردّ بإصدار افتراضي لا يطابق توقّعاتك. ثبّتها في طبقة العميل لا في كل استدعاء.
أعد استخدام نسخة HttpClient واحدة طوال عمر التطبيق. إنشاء عميل جديد لكل طلب يهدر موارد ويفتح اتصالات زائدة. العميل آمن للاستخدام من خيوط متعددة، فاحقنه كوحيد (singleton) أو bean في Spring. ضبطنا مهلة الاتصال على عشرين ثانية ومهلة الطلب الكامل على ستين ثانية، وهذه قيم معقولة لبوابة الهيئة.
تستحق الترويسات وقفة، فكل طلب يحمل ست ترويسات لكل منها دور. Authorization تحمل مصادقة Basic بشهادة CSID وهي إلزامية. Accept-Version بقيمة V2 تحدّد إصدار الواجهة وهي إلزامية أيضًا. Content-Type بقيمة application/json يخبر البوابة أن الحمولة JSON. Accept بالقيمة نفسها يطلب استجابة JSON. Accept-Language بقيمة ar يحدّد لغة رسائل الخطأ وهي اختيارية. Clearance-Status بقيمة 1 خاصة بطلبات المقاصة دون الإبلاغ.
الفرق بين منفذ المقاصة ومنفذ الإبلاغ ينعكس في الكود بمتغيّر واحد: عنوان الـ URI ووجود ترويسة Clearance-Status أو غيابها. لتجنّب تكرار صنف FatooraClient مرتين، اجعل العنوان والترويسات الخاصة معاملات تمرّرها للدالة، فيخدمك العميل نفسه لكلا المنفذين. هذا يبقي منطق المصادقة والإرسال موحّدًا، ويغيّر الوجهة فقط بحسب نوع الفاتورة.
قراءة الاستجابة وتحليل JSON
بعد الإرسال تصلك استجابة تحمل رمز حالة وجسمًا بصيغة JSON. الخطوة الأولى دائمًا فحص رمز الحالة قبل الغوص في الجسم. رمز 200 يعني نجاح المعالجة، ورمز 401 يعني فشل مصادقة، ورمز 400 يعني خطأ في بنية الحمولة، ورمز 5xx يعني مشكلة مؤقتة في الخادم تستدعي إعادة المحاولة.
استجابة نجاح المقاصة تحمل حالة المقاصة والفاتورة الموقّعة ونتائج التحقق. شكلها على هذا النحو:
لقراءة هذه القيم في Java دون مكتبة خارجية، يكفي استخراج بسيط للحقول المطلوبة. لمشروع جدّي استخدم Jackson أو Gson لتحليل JSON كاملًا في كائن، لكن للوضوح نعرض دالة فحص الحالة المعتمدة على رمز HTTP أولًا، ثم استخراج حالة المقاصة:
التمييز بين أنواع الاستثناءات هنا مقصود. AuthException وValidationException أخطاء دائمة لا تُحل بإعادة المحاولة، فمعالجتها تتطلب تصحيح بيانات الاعتماد أو بنية الفاتورة. RetryableException وحده يستدعي إعادة المحاولة، لأن سببه مؤقت في الخادم. هذا التصنيف يقود منطق إعادة المحاولة في الخطوة التالية.
ضبط Accept-Language على ar يجعل رسائل الخطأ في جسم الاستجابة بالعربية، وهذا مفيد لفِرَق الدعم. اقرأ جسم الاستجابة دائمًا عند الفشل، فهو يحمل الرسالة الدقيقة التي تشرح السبب، لا رمز الحالة وحده.
حقل validationResults يستحق قراءة دقيقة حتى عند النجاح. قد تنجح المقاصة برمز 200 لكن يحمل الحقل warningMessages غير فارغة، أي تحذيرات لا تمنع الاعتماد لكنها تشير إلى أمور تستحق المعالجة قبل أن تتحوّل إلى أخطاء. سجّل هذه التحذيرات وراجعها دوريًا، فتجاهلها اليوم قد يعني رفضًا غدًا حين تشدّد الهيئة قاعدة كانت تحذيرًا.
الحقل clearedInvoice في استجابة النجاح يحمل الفاتورة الموقّعة من الهيئة مُرمَّزة بـ Base64. هذه هي النسخة المعتمدة التي تحفظها وتسلّمها للمشتري، لا النسخة التي أرسلتها أنت. فكّ ترميزها بـ Base64.getDecoder للحصول على XML الموقّع، ثم خزّنه مرتبطًا بالفاتورة في نظامك. الخلط بين النسخة المرسلة والنسخة المعتمدة خطأ يفقدك التوقيع الرسمي للهيئة.
معالجة الاستثناءات وإعادة المحاولة
الشبكة ليست مضمونة، وبوابة الهيئة قد تردّ أحيانًا برمز 5xx أو تنقطع المهلة. عميل تكامل جدّي لا يستسلم عند أول فشل مؤقت، بل يعيد المحاولة بتباعد متزايد (exponential backoff). انتبه إلى قاعدة حاسمة: أعد المحاولة فقط على الأخطاء المؤقتة، لا على أخطاء المصادقة أو بنية الفاتورة، لأن إعادة إرسال فاتورة خاطئة لن تنجح مهما كرّرت.
المقطع التالي يغلّف الإرسال بمنطق إعادة محاولة يتعامل مع HttpTimeoutException ومع رموز الخادم 5xx، ويزيد فترة الانتظار بين المحاولات:
لاحظ أن الشرط status أقل من 500 يعيد الاستجابة فورًا. هذا يشمل رمز 200 الناجح ورمز 400 و401، لأن هذين خطآن دائمان يعالجهما المتصل لا تُحل بالتكرار. فقط رموز 5xx والمهلة تقود إلى محاولة جديدة. هذا التمييز يحميك من إغراق البوابة بطلبات خاطئة متكررة.
التباعد المتزايد (المحاولة الأولى بعد ثانيتين، ثم أربع، ثم ثمان) يمنح الخادم وقتًا للتعافي بدل قصفه بطلبات متلاحقة. لإنتاج جدّي، أضف عشوائية بسيطة (jitter) إلى فترة الانتظار لتجنّب تزامن محاولات عدة عملاء في اللحظة نفسها. الحد الأعلى ثلاث محاولات معقول، فما بعده غالبًا مشكلة تستدعي تدخّلًا لا انتظارًا.
| رمز الحالة | الإجراء |
|---|---|
| 2xx | احفظ النتيجة المعتمَدة |
| 400/401 | خطأ دائم: صحّح، لا تُعِد |
| 5xx / مهلة | أعد المحاولة بتراجع أسّي |
تجميع المسار الكامل من البداية للنهاية
وصلنا الآن إلى تركيب الطبقات في مسار واحد متكامل. هذا المقطع يأخذ مستند UBL، ويحسب تجزئته، ويرمّزه، ويبني الحمولة، ثم يرسلها بإعادة محاولة، ثم يعالج الاستجابة. إنه الصورة الكاملة لما شرحناه قطعة قطعة:
ترتيب الخطوات هنا غير قابل للتبديل. التجزئة تُحسب على المستند المطبّع الموقّع قبل ترميزه بـ Base64. الترميز يجري بعد التجزئة. الحمولة تُبنى من الناتجين معًا. الإرسال يأتي أخيرًا. أي خلط في هذا الترتيب، خصوصًا حساب التجزئة على مستند مرمّز أو غير مطبّع، ينتج عدم تطابق ترفضه الهيئة.
لاحظ قراءة بيانات الاعتماد من متغيرات البيئة عبر System.getenv لا من ثوابت في الكود. هذه ليست تفصيلة تجميلية، بل قاعدة أمان أساسية نعود إليها في القسم التالي. الفاتورة الموقّعة المعادة في الاستجابة هي ما تحفظه وتسلّمه للمشتري، لا المستند الذي أرسلته.
هذا المثال يعالج فاتورة واحدة، لكن نظام إنتاج يصدر فواتير كثيرة. لا تجعل main يعالج فاتورة ثم يخرج، بل اقرأ الفواتير الجاهزة من طابور عمل (queue) عالجها واحدة واحدة. عميل HttpClient الواحد يخدمها كلها لأنه آمن من خيوط متعددة. هذا التصميم يفصل توليد الفاتورة عن إرسالها، فإن تعطّلت البوابة لحظيًا بقيت الفواتير في الطابور حتى تتعافى، دون أن تفقد أيًا منها.
افصل أيضًا توليد UBL وتوقيعه عن هذا المسار. دور كود الإرسال أن يأخذ مستندًا موقّعًا جاهزًا ويوصله للهيئة، لا أن يبني الفاتورة. هذا الفصل يجعل كل طبقة قابلة للاختبار وحدها: تختبر بناء UBL بمستندات معروفة، وتختبر الإرسال بمستندات جاهزة، دون أن تخلط منطقين في صنف واحد يصعب صيانته.
تأمين بيانات الاعتماد في كود Java
بيانات الاعتماد في المصادقة الأساسية ثابتة لا تنتهي تلقائيًا قبل تجديد الشهادة، بخلاف رمز الوصول قصير العمر في OAuth. هذا يجعل تسريبها أخطر، لأن من يحصل عليها يستطيع انتحال هوية جهازك حتى تنتبه وتعيد إصدار الشهادة. التأمين الصارم في كود Java ليس اختياريًا.
القاعدة الأولى: لا تكتب معرّف CSID ولا السرّ كثوابت String في الكود، ولا تدفعهما إلى مستودع الشيفرة. هذا أكثر مصادر التسريب شيوعًا. اقرأهما وقت التشغيل من متغيرات بيئة محمية، أو من مخزن أسرار سحابي، أو من ملف إعداد خارج الكود غير متتبَّع في git.
القاعدة الثانية: لا تسجّل ترويسة Authorization في سجلّات النظام. كثير من أطر Java تسجّل الترويسات كاملة عند تصحيح الأخطاء، فتتسرّب بيانات الاعتماد إلى ملفات السجل دون قصد. أضِف قاعدة حجب صريحة لهذه الترويسة في إعداد التسجيل، وراجع ما يطبعه كودك قبل النشر.
القاعدة الثالثة: افصل بيانات اعتماد المحاكاة عن الإنتاج في مفتاحين مختلفين، لا في القيمة نفسها يبدّلها متغيّر. واجهات المحاكاة والإنتاج تستخدمان شهادتين منفصلتين تمامًا، وإرسال شهادة بيئة إلى عنوان البيئة الأخرى ينتج رفضًا برمز 401 رغم أن البيانات «صحيحة» في حد ذاتها. اقرن وجهة العنوان وبيانات الاعتماد في إعداد واحد.
القاعدة الرابعة: عند أي شبهة تسريب، أعِد إصدار الشهادة فورًا من الهيئة دون انتظار تأكيد. إعادة الإصدار تُبطل البيانات القديمة وتمنحك زوجًا جديدًا، فحتى لو كان السرّ القديم بيد طرف آخر صار بلا قيمة. صمّم كودك ليقرأ بيانات الاعتماد من مصدر مركزي قابل للتحديث، فيصبح التجديد تغيير قيمة في مكان واحد لا إعادة نشر للنظام كله.
كيف يساعدك قيود في التكامل مع منصة فاتورة
كل ما شرحناه في هذا الدليل، من بناء ترويسة Basic بـ Base64 إلى حساب تجزئة SHA-256 وترميز UBL والإرسال عبر HttpClient وإعادة المحاولة، يديره نظام قيود للفاتورة الإلكترونية نيابةً عنك. أنت لا تكتب صنف AuthHeader ولا تحسب التجزئة يدويًا، بل تصدر فاتورتك من واجهة قيود ويتولّى النظام الربط مع الهيئة في الخلفية.
تحديدًا، يقوم قيود بالآتي على مستوى التكامل التقني:
- يدير شهادة الختم التشفيري (CSID) آليًا، من طلب شهادة الامتثال عبر رمز التحقق، إلى توليد شهادة الإنتاج بعد اجتياز الامتثال.
- يبني ترويسة Authorization بصيغة Basic لكل طلب تلقائيًا، فلا تتعامل مع ترميز Base64 ولا مع السرّ مباشرة.
- يحسب تجزئة الفاتورة ويوقّعها تشفيريًا ويضمّن رمز QR، ويحفظ سلسلة التجزئة للتحقق من الامتثال.
- ينفّذ المقاصة الفورية لفواتير B2B والإبلاغ خلال 24 ساعة لفواتير B2C، مع معالجة الأخطاء وإعادة المحاولة داخليًا.
- يفصل بيئة المحاكاة عن بيئة الإنتاج، ويدير تجديد الشهادة عند اقتراب انتهائها.
بهذا تركّز فِرَق التطوير على منطق العمل في نظامها، بينما تبقى طبقة التكامل مع الهيئة مسؤولية قيود. هذا يلغي الحاجة لبناء وصيانة تكامل Java مباشر مع منصة فاتورة من الصفر، ويوفّر على فريقك صيانة الشهادة والترويسات والتوقيع مع كل تحديث للهيئة.
دع قيود يتولّى التكامل مع منصة فاتورة
لا تبنِ تكامل Java من الصفر. أصدر فواتيرك الإلكترونية المتوافقة مع المرحلة الثانية، ودع قيود يدير الشهادة والتجزئة والترميز والربط مع الهيئة في الخلفية.
الأسئلة الشائعة عن التكامل بلغة Java
هل أحتاج مكتبة خارجية للتكامل مع منصة فاتورة بلغة Java؟
لا للوظائف الأساسية. عميل java.net.http.HttpClient وتجزئة java.security.MessageDigest وترميز java.util.Base64 كلها في المكتبة القياسية منذ الإصدار الحادي عشر. قد تفضّل مكتبة JSON مثل Jackson لتحليل الاستجابة في كائن، لكن المصادقة والتجزئة والترميز والإرسال لا تحتاج أي تبعية خارجية.
كيف أبني ترويسة Authorization في Java بالضبط؟
تصل معرّف CSID بالسرّ بنقطتين رأسيتين، ثم ترمّز السلسلة الناتجة بـ Base64 على بايتات UTF-8 عبر Base64.getEncoder، ثم تسبقها بكلمة Basic. النقطتان جزء من المدخل قبل الترميز لا بعده. ابنِ القيمة مرة واحدة عند تهيئة العميل ولا تطلب رمز وصول من أي نقطة نهاية قبلها.
هل أحسب تجزئة الفاتورة بصيغة hex أم Base64؟
بصيغة Base64. تحسب التجزئة بخوارزمية SHA-256 عبر MessageDigest على بايتات مستند UBL المطبّع، ثم ترمّز البايتات الناتجة بـ Base64 لا بـ hex. حساب التجزئة صحيحًا ثم تحويلها إلى hex خطأ شائع يجعل الهيئة ترفض الفاتورة رغم سلامة الحساب.
متى أعيد محاولة إرسال الفاتورة في Java؟
على الأخطاء المؤقتة فقط: رموز الخادم 5xx واستثناء HttpTimeoutException. أعد المحاولة بتباعد متزايد (ثانيتان ثم أربع ثم ثمان) حتى ثلاث محاولات. لا تعيد المحاولة على رمز 401 (فشل مصادقة) ولا 400 (خطأ بنية)، لأنهما خطآن دائمان لا يُحلّان بالتكرار بل بتصحيح بيانات الاعتماد أو الفاتورة.
هل تستخدم واجهات الفوترة OAuth في كود Java؟
لا. واجهات إصدار الفواتير تستخدم المصادقة الأساسية (Basic) المبنية على شهادة CSID وسرّها، لا OAuth برموز وصول مؤقتة. في كود Java لا تطلب رمز وصول من أي نقطة نهاية قبل الإرسال، بل تبني ترويسة Authorization محليًا وترسل الطلب مباشرة. هذا يبسّط تصميم العميل ويلغي إدارة دورة حياة الرمز.
لماذا أقرأ معرّف CSID والسرّ من متغيرات البيئة لا من الكود؟
لأن بيانات الاعتماد الأساسية ثابتة لا تنتهي تلقائيًا، فتسريبها يتيح انتحال هوية جهازك مدة طويلة. كتابتها كثوابت String في الكود ودفعها إلى مستودع الشيفرة أكثر مصادر التسريب شيوعًا. اقرأها وقت التشغيل عبر System.getenv أو من مخزن أسرار مشفّر، ولا تسجّل ترويسة Authorization في السجلّات.