شحن العدادات

اختر باقة لمحفظتك وأكّد إجماليها مع العميل ثم اطلب الشحن. تستمر العملية بعد قبولها. احتفظ بمعرّفها حتى تعرف هل نجحت، وإن فشلت فهل أُعيد المال.

اختيار الباقة

وقّع GET /v1/wallets/{walletId}/meter-packages. تحتاج إلى meter_charges:read، وهي مشمولة أيضًا في meter_charges:create. الردّ غير موقّع. أعد nextCursor كما هي في cursor؛ وتحمل الصفحة الأخيرة nextCursor: null.

{
  "items": [
    {
      "id": "5f3b8c21-7a0d-4e96-b1c4-2d8e6f9a0b37",
      "name": "باقة 100 دينار",
      "gecolPackageName": "PKG_100",
      "logo": "https://cdn.anis.ly/meter/packages/100.png",
      "unitPrice": {
        "amount": "97.500",
        "currency": "LYD"
      },
      "businessPrice": {
        "amount": "97.500",
        "currency": "LYD"
      },
      "personalPrice": {
        "amount": "100.000",
        "currency": "LYD"
      }
    }
  ],
  "nextCursor": null
}

تحدّد unitPrice ما تدفعه هذه المحفظة. أرسل قيمة Money هذه في expectedTotal. أما personalPrice فسعر التجزئة المعروض، ولا تظهر businessPrice إلا حين تنطبق شروط عرضها. الحقل name اسم واحد محفوظ للباقة، وليس كائنًا بلغتين. لا تحسب إجمالي الدفع من أسعار العرض الأخرى.

طلب الشحن

احفظ قيمة UUID جديدة مع المحتوى كما سترسله قبل الاستدعاء. أرسلها في Idempotency-Key؛ فهي نفسها operationId في القراءة والإشعارات والمراجع. وقّع بملف الإنشاء: بصمة المحتوى ورقم استخدام واحد جديد ومفتاح منع التكرار. تحتاج إلى meter_charges:create.

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

POST /v1/wallets/{walletId}/meter-charges
Idempotency-Key: 3c1e9b7a-0d42-4f5e-8a61-2b9c7d0e5f13
{
  "packageId": "5f3b8c21-7a0d-4e96-b1c4-2d8e6f9a0b37",
  "meterNumber": "0268000710262",
  "expectedTotal": {
    "amount": "97.500",
    "currency": "LYD"
  },
  "externalReference": "POS-77-INV-1093"
}

يعني الردّ الموقّع 202 قبول العملية أو استمرار التحقق من نتيجتها، ولا يحمل رمز شحن. قد تعيد العملية المقبولة الردّ التالي مع Location: /v1/meter-charges/{operationId}:

{
  "operationId": "3c1e9b7a-0d42-4f5e-8a61-2b9c7d0e5f13",
  "operationType": "meterCharge",
  "status": "processing"
}

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

قراءة النتيجة والرموز

تحتاج قراءة GET /v1/meter-charges/{operationId} إلى صلاحية القراءة أو الإنشاء. وقّعها كقراءة وتحقّق من توقيع الردّ. تظهر عملية تطبيق آخر كما لو كانت غير موجودة. تحمل القراءة أثناء المعالجة بيانات العملية الأساسية بلا إيصال. وعند النجاح تظهر:

{
  "operationId": "3c1e9b7a-0d42-4f5e-8a61-2b9c7d0e5f13",
  "status": "succeeded",
  "walletId": "71825266-7182-4182-8182-718252667182",
  "meterNumber": "0268000710262",
  "package": {
    "id": "5f3b8c21-5f3b-4c21-8f3b-5f3b8c215f3b",
    "name": "باقة 100 دينار",
    "gecolPackageName": "PKG_100"
  },
  "total": {
    "amount": "97.500",
    "currency": "LYD"
  },
  "externalReference": "POS-77-INV-1093",
  "createdAt": "2026-10-20T08:12:44Z",
  "completedAt": "2026-10-20T08:13:02Z",
  "receipt": {
    "receiptNumber": "260020103744",
    "creditedAmount": "88.640",
    "tokens": [
      {
        "sequence": 1,
        "label": "KCT1",
        "code": "1234 5678 9012 3456 7890",
        "description": "Key change token 1"
      },
      {
        "sequence": 2,
        "label": "KCT2",
        "code": "2345 6789 0123 4567 8901",
        "description": "Key change token 2"
      },
      {
        "sequence": 3,
        "label": "STS",
        "code": "5821 0034 7719 2265 0418",
        "description": "Credit token"
      }
    ],
    "taxes": [
      {
        "description": "ضريبة الاستهلاك",
        "amount": "5.000"
      },
      {
        "description": "رسوم الخدمة",
        "amount": "3.860"
      }
    ]
  },
  "personalPrice": {
    "amount": "100.000",
    "currency": "LYD"
  }
}

اعرض كل الرموز بترتيب sequence التصاعدي: رموز تغيير المفتاح أولًا، ثم رمز الرصيد. احتفظ بقيم label وcode وdescription، بما فيها الأصفار الأولى والمسافات؛ ولا تعرض الرمز الأخير وحده. لا يعمل الرمز إلا على العداد الذي شُحن. تمثّل receiptNumber وcreditedAmount وtaxes بيانات شركة الكهرباء، وقد تختلف عن إجمالي المحفظة. تمرّ نصوص مبالغ الإيصال كما وردت؛ ولا يُضمن أن تتبع عدد الخانات العشرية في Money.

قد تعيد القراءة أيضًا رفضًا سُجّل لعملية الإنشاء بهذا المعرّف، مع Idempotency-Replayed: true: الحالة 409 (insufficient_balance أو price_changed أو package_unavailable) أو الحالة 422 (meter_rejected). تقع هذه الرفوض قبل تحرّك المال: لم يُخصم شيء. وعند القراءة يعني idempotency_conflict (409) أنه لا توجد عملية شحن عداد لتطبيقك بهذا المعرّف؛ فلا تستخدم Idempotency-Key جديدًا إلا لعملية جديدة فعلًا.

البحث عن عملياتي

وقّع GET /v1/wallets/{walletId}/meter-charges بصلاحية meter_charges:read أو meter_charges:create. الردّ غير موقّع. تظهر عمليات تطبيقك وحده في المحفظة المصرّح بها، من الأحدث إلى الأقدم. استخدم معًا ما تحتاجه من المرشّحات الاختيارية status (processing أو succeeded أو failed) وmeterNumber (مطابقة تامة، من 1 إلى 64 رقمًا لاتينيًا مع الاحتفاظ بالأصفار الأولى) وfrom وto (بتوقيت UTC) وcursor وpageSize. لا تتجاوز الفترة 31 يومًا؛ وعند ترك التاريخين تُعرض آخر 31 يومًا. حجم الصفحة الافتراضي 50 وحدّه الأقصى 100. مرّر nextCursor كما هو مع المرشّحات وحجم الصفحة نفسيهما حتى يصبح null. النتيجة الفارغة تحمل items: [] وnextCursor: null. عند validation_failed (422) صحّح المرشّحات أو المؤشّر؛ ولا تكرر عملية الشراء لاستعادة القائمة. الإيصال ورموز الشحن متاحة في قراءة الشحن الموقّعة فقط.

{
  "items": [
    {
      "operationId": "3c1e9b7a-0d42-4f5e-8a61-2b9c7d0e5f13",
      "status": "succeeded",
      "meterNumber": "0268000710262",
      "package": {
        "id": "5f3b8c21-7a0d-4e96-b1c4-2d8e6f9a0b37",
        "name": "باقة 100 دينار",
        "gecolPackageName": "PKG_100"
      },
      "total": {
        "amount": "97.500",
        "currency": "LYD"
      },
      "externalReference": "POS-77-INV-1093",
      "createdAt": "2026-10-20T08:12:44Z",
      "completedAt": "2026-10-20T08:13:02Z"
    }
  ],
  "nextCursor": null
}

الفشل وإعادة المال

تتضمن كل حالة total وpersonalPrice. لا يظهر completedAt إلا عند النجاح، وتبقى أوصاف ضرائب شركة الكهرباء بالعربية كما وردت.

قد تعرض القراءة status: "failed" بينما لا تزال إعادة المال جارية. راجع refund.status قبل إبلاغ العميل بأن المال عاد.

{
  "operationId": "3c1e9b7a-0d42-4f5e-8a61-2b9c7d0e5f13",
  "status": "failed",
  "walletId": "71825266-7182-4182-8182-718252667182",
  "meterNumber": "0268000710262",
  "package": {
    "id": "5f3b8c21-5f3b-4c21-8f3b-5f3b8c215f3b",
    "name": "باقة 100 دينار",
    "gecolPackageName": "PKG_100"
  },
  "total": {
    "amount": "97.500",
    "currency": "LYD"
  },
  "externalReference": "POS-77-INV-1093",
  "createdAt": "2026-10-20T08:12:44Z",
  "failure": {
    "code": "meter_not_whitelisted",
    "message": {
      "ar": "العداد غير مسجل",
      "en": "Meter not enrolled"
    }
  },
  "refund": {
    "status": "refunded",
    "amount": {
      "amount": "97.500",
      "currency": "LYD"
    }
  },
  "personalPrice": {
    "amount": "100.000",
    "currency": "LYD"
  }
}
refund.status المعنى
refunded عاد المال
notCharged رفضت المحفظة الدفع، ولم يُخصم شيء
pending لم يعد المال بعد؛ وقد أُبلغ موظفو أنيس

يحمل failure.message رسالتي العرض بالعربية والإنجليزية. ابنِ منطقك على failure.code. القيم التالية جزء من قراءة الشحن، وليست رموز مشاكل HTTP.

failure.code المعنى
meter_number_invalid رقم العداد غير صحيح
meter_not_whitelisted العداد غير مسجّل
meter_not_linked العداد غير مربوط
meter_removed العداد محذوف
meter_inactive العداد غير نشط
meter_account_blocked الحساب موقوف
meter_data_incomplete بيانات العداد غير مكتملة
meter_debt_exceeds_package مديونية العداد أكبر من قيمة الباقة
package_unavailable الباقة غير متاحة
package_limit_reached بُلغ حدّ الباقة
token_not_written لم يُكتب الرمز
insufficient_balance الرصيد غير كافٍ
gecol_unavailable لم يصل ردّ شركة الكهرباء في الوقت المحدد
charge_failed تعذّر الشحن

لا يُرسل إشعار meterCharge.failed إلا بعد عودة المال أو ثبوت عدم خصمه. لذلك قد ترى المال قيد الإعادة في القراءة قبل وصول إشعار الفشل. وتحمل إشعارات النجاح محتوى القراءة الكامل، بما فيه كل الرموز.

الرفض قبل تحرك المال

الرمز HTTP ما تفعله
meter_rejected 422 راجع رقم العداد
package_unavailable 409 اقرأ الباقات الحالية
price_changed 409 أكّد سعر الدفع الحالي مجددًا
insufficient_balance 409 وفّر رصيدًا كافيًا
spending_limit_exceeded 429 انتظر إعادة ضبط الحدّ حسب Retry-After
idempotency_conflict 409 استعد المحتوى الأصلي إذا تغيّر؛ واقرأ العملية أولًا فقد تكون قُبلت وخُصم مبلغها، ولا تستخدم معرّفًا جديدًا إلا لعملية جديدة فعلًا

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

لا يعني idempotency_conflict ولا replay_detected أن الطلب الأول فشل: فقد يكون قُبل وخُصم مبلغه، فلا تفترض أبدًا أنه لم يُخصم شيء. اقرأ العملية الأصلية عبر GET /v1/meter-charges/{operationId} قبل أي محاولة جديدة. لتكرار القصد نفسه بعد replay_detected احتفظ بـ Idempotency-Key وأرسل nonce وتوقيعًا جديدين. وبعد idempotency_conflict أعد استخدام المفتاح للقصد المطابق فقط، واستخدم Idempotency-Key جديدًا لعملية جديدة فعلًا. أما insufficient_balance وprice_changed ورفض المنتج غير المتاح فتقع قبل تحرّك المال: لم يُخصم شيء.

استقبل النتائج عبر الإشعارات وتحقّق من كل إشعار قبل معالجته.

الباقات · الإنشاء · القراءة · البحث عن عملياتي

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

{
  "operationId": "3c1e9b7a-0d42-4f5e-8a61-2b9c7d0e5f13",
  "status": "failed",
  "walletId": "71825266-7182-4182-8182-718252667182",
  "meterNumber": "0268000710262",
  "package": {
    "id": "5f3b8c21-5f3b-4c21-8f3b-5f3b8c215f3b",
    "name": "باقة 100 دينار",
    "gecolPackageName": "PKG_100"
  },
  "total": {
    "amount": "97.500",
    "currency": "LYD"
  },
  "externalReference": "POS-77-INV-1093",
  "createdAt": "2026-10-20T08:12:44Z",
  "failure": {
    "code": "meter_not_whitelisted",
    "message": {
      "ar": "العداد غير مسجل",
      "en": "Meter not enrolled"
    }
  },
  "refund": {
    "status": "pending",
    "amount": {
      "amount": "97.500",
      "currency": "LYD"
    }
  },
  "personalPrice": {
    "amount": "100.000",
    "currency": "LYD"
  }
}