منع التكرار والمحاولات
انقطاع الشبكة بعد إرسال الطلب وقبل وصول الردّ هو أخطر سيناريو في الفوترة: لا تعرف إن كانت الفاتورة سُجّلت أم لا، وإعادة الإرسال الأعمى قد تُنتج فاتورة مزدوجة في النظام الضريبي — وهو خطأ لا يُصلَح إلا بإشعار دائن.
الحل: ترويسة Idempotency-Key
أرسل مع كل POST /v1/invoices مفتاحًا فريدًا ثابتًا لتلك الفاتورة في نظامك
(حتى 80 حرفًا):
Idempotency-Key: so-99813-attempt
# أو
Idempotency-Key: 8f14e45f-ea1c-4b7f-9c0d-2a3b4c5d6e7f
السلوك:
- الطلب الأول ينشئ الفاتورة ويعيد
201أو202. - أي طلب لاحق بنفس المفتاح لا ينشئ شيئًا ويعيد
200مع الفاتورة الأصلية والحقلidempotent_replay: true.
{
"data": { "id": "inv_5c8d1e9a2b7f3046", "status": "accepted", "...": "..." },
"idempotent_replay": true
}
SO-99813) أو UUID تولّده وتخزّنه مع الفاتورة
قبل أول محاولة إرسال.
الحماية الثانية: رقم الفاتورة
حتى لو نسيت المفتاح، لن تُسجَّل فاتورتان بنفس invoice_number في الوضع نفسه:
المحاولة الثانية تُرفض برمز 409:
{
"error": {
"type": "duplicate_invoice_number",
"message": "رقم الفاتورة مستخدم مسبقًا في هذا الوضع.",
"existing_invoice_id": "inv_5c8d1e9a2b7f3046"
}
}
استخدم existing_invoice_id لجلب الفاتورة القائمة بدل إنشاء واحدة جديدة.
نمط التعامل الموصى به
function sendInvoice(order) {
// 1) خزّن مفتاح منع التكرار في جدولك قبل أي طلب
if (!order.idempotency_key) {
order.idempotency_key = crypto.randomUUID();
db.save(order);
}
// 2) أرسل
const res = await post('/v1/invoices', body, {
'Idempotency-Key': order.idempotency_key
});
// 3) خزّن النتيجة أيًا كانت
order.fawtara_id = res.data.id;
order.status = res.data.status; // accepted | rejected | error
order.qr_content = res.data.qr_content;
db.save(order);
// 4) ما لم تُقبل، لا تُعلن النجاح لمستخدمك
if (order.status !== 'accepted') {
notifyOps(res.data.error);
}
}
إعادة المحاولة عند الأخطاء المؤقّتة
| الحالة | أعد المحاولة؟ | كيف |
|---|---|---|
429 rate_limited | نعم | انتظر قيمة Retry-After ثم أعد الطلب نفسه بنفس مفتاح منع التكرار. |
500 / 503 | نعم | تدرّج أُسّي: 1s، 2s، 4s، 8s — بحد أقصى 5 محاولات. |
status = error (jofotara_unreachable) | نعم | POST /v1/invoices/{id}/retry — لا تُنشئ فاتورة جديدة. |
status = rejected | لا | البيانات خاطئة. صحّحها وأنشئ فاتورة برقم جديد. |
422 | لا | أصلح الحقول المذكورة في error.fields أولًا. |
402 | لا | رقّ خطتك أو انتظر بداية الشهر. |
ملاحظة عن عدّاد ICV
البوابة تطلب عدّادًا تصاعديًا لا يتكرّر لكل مكلّف. نحن ندير هذا العدّاد نيابة عنك
داخل معاملة قاعدة بيانات، فلا تحتاج لإرساله أو تتبّعه.
لاحظ أن كل محاولة إنشاء ناجحة تستهلك رقمًا جديدًا — بينما
retry يُعيد إرسال نفس المستند بنفس الـ ICV، وهذا هو السلوك الصحيح.