طلبات النقاط
الخلفية
طلب النقاط هو هدية نقاط من عميل إلى آخر، أو نقاط يشتريها العميل
لنفسه. يختار العميل عرضًا، واختياريًا مناسبة، ومستلمًا، ثم يدفع بالنقاط
أو بالبطاقة. وعلى Shukah يفعل التطبيق ذلك عبر POST /maeda/point-order و
POST /maeda/point-order/get-description واستدعاءَي دفع على /maeda/order/checkout-payment-*.
وفي Locate route لكل واحد من هذه الأربعة، إضافةً إلى route جديد يقرأ طلبًا واحدًا. وثلاثة من الأربعة
adapt: يحتاج Create وrouteا الدفع إلى تغييرات في التطبيق. أما get-description فهو drop-in:
لا يتغير سوى الـ base URL والمسار، لكن في TL;DR الخاص به بنودًا للمراقبة (المعرّفات والـ token).
وستة أمور تستحق العناية:
- الطلب الذي يستلمه التطبيق له مفاتيح مختلفة: فالمعرّف UUID اسمه
correlation_id، والسعر هوmoney_amount. وتسمّي routes الدفع الطلب بـcorrelation_idفي المسار. - النقاط المصروفة على جانب لا تُخصم من الجانب الآخر (خلال ثواني التشغيل؛ ولم يُفحص هل يصل لاحقًا). فعلى dev في 2026-10-02 لم يخفض طلبٌ مدفوع بالنقاط إلا رصيد الجانب الذي أنشأه، لذا يجب أن يقرأ التطبيق الرصيد والسجل من الجانب الذي يُنشئ فيه الطلبات.
- يقبل Create فقط
payment_typeبالقيمةpointأوcheckout. وقد يرسل التطبيق أيضًاpayfortأوhyperpay، وتجيب Locate عن كليهما بـ400. - يقع الدفع بالنقاط وrouteا الدفع خلف flagين على الخادم. وكان كلاهما مفعّلًا على dev في 2026-10-01. أما production فغير مؤكَّد.
- ليس لـ Create مفتاح idempotency، وبعد
504لا يستطيع التطبيق إيجاد الطلب الذي ربما أنشأه. - عدة استدعاءات أخرى للهدايا في التطبيق ليس لها route في Locate: قائمة الهدايا المرسَلة، واستدعاء token الدفع، واستدعاء STC Pay، واستدعاءات PayFort.
الميزة
أين تحدث الاستدعاءات في التطبيق
في نسخة التطبيق اللحظية بتاريخ 2026-02-26 يقع تدفق الهدية في نموذج إرسال الهدية وفي شاشة دفع الهدية. وتحتاج
routes Locate الخمسة كلها إلى token، وعلى dev اليوم يجب أن يكون token من Shukah: فهي تستخدم فحص الـ token نفسه
الذي تستخدمه routes النقاط الأخرى، والذي أعطى JWT من Locate 401 في 2026-10-02 (انظر
Which tokens work). وفي 2026-10-01 عمل الـ token كلاهما على
route الرصيد.
| الخطوة | الاستدعاء | ماذا يفعل التطبيق |
|---|---|---|
| 1. كتابة الرسالة | Description | بينما يملأ العميل النموذج، يطلب التطبيق الرسالة التي سيراها المستلم. وقد رصدت وثائق خدمة النقاط أنه يستدعيه مرتين لطلب واحد: مرة بالعرض، ومرة أخرى بالعرض والمناسبة. |
| 2. إيجاد المستلم | البحث عن عميل | عند الإرسال، يبحث التطبيق عن المستلم برقم الهاتف ويأخذ userId بوصفه to_client_id. |
| 3. إنشاء الطلب | Create | يعيد التطبيق أولًا تحميل الإعدادات العامة. ويرسل payment_type بالقيمة point عندما يدفع العميل بالنقاط، وإلا قيمة payment من تلك الإعدادات: payfort أو hyperpay أو checkout. |
| 4أ. الدفع بالنقاط | لا شيء | يكون الطلب مدفوعًا بالفعل عند عودة create. فيعيد التطبيق تحميل السجل ويفتح تبويب الهدايا المرسَلة (Sent gifts) ويعرض "Gift sent successfully". |
| 4ب. بطاقة محفوظة | الدفع بالبطاقة | يرسل التطبيق معرّف البطاقة المحفوظة وCVV. وتبدأ الإجابة خطوة 3-D Secure. |
| 4ج. بطاقة جديدة أو Apple Pay أو STC Pay | الدفع بالجلسة | يطلب التطبيق جلسة دفع ويعطيها لـ Checkout SDK. أما payment_type الذي يرسله (applePay أو stcpay أو card في النسخة اللحظية) فتتجاهله Locate. وقد أرسل التطبيق 2.31.5 القيمة applePay هنا في 2026-10-02، وأرسل STC Pay إلى route checkout-payment-stcpay الخاص بـ Shukah نفسها، وليس له route في Locate. |
| 5. بعد الدفع بالبطاقة | لا شيء | عند نجاح الـ SDK يعيد التطبيق تحميل السجل ويفتح تبويب الهدايا المرسَلة. |
| لا تستخدمه أي شاشة | قراءة الطلب (Get order) | لدى التطبيق استدعاء لطلب واحد من Shukah، لكن لا شاشة تستخدمه. |
وعندما لا تقول إعدادات العميل checkout، يدفع التطبيق عبر PayFort أو Hyperpay بدلًا من ذلك، باستدعاءات
ليس لها route في Locate. وينطبق الأمر نفسه على استدعاء Frames-token في التطبيق، واستدعاء STC Pay
لديه، واستدعاء CVV-token إلى خدمة الدفع (انظر «التفاصيل»).
دورة الحياة
يبدأ الطلب الجديد بالحالة initial_order أو، عند الدفع بالنقاط، بالحالة successful. والحالات
هي كلمات Shukah نفسها، محفوظة كما هي.
status | payment_status | المعنى | كيف تُبلَغ |
|---|---|---|---|
initial_order | null | أُنشئ ولم يُدفع. | Create مع checkout. ويبقى الطلب هنا حتى تُخبر البوابة خدمة النقاط بالنتيجة. والطلب الذي لا يدفعه أحد يُترك كما هو. |
successful | null | دُفع بالنقاط. خُصم من المرسِل وأُضيف للمستلم. | Create مع point. |
successful | CAPTURED | دُفع بالبطاقة. أُضيف للمستلم. | تُبلّغ البوابة عن دفعة ملتقَطة (captured)، وعادةً خلال ثوانٍ من استدعاء الدفع. |
payment_failed | FAILED | رُفضت البطاقة. وهذا نهائي: فالـ routeان للدفع يجيبان الآن بـ 409، لذا على العميل إنشاء طلب جديد. | تُبلّغ البوابة عن دفعة مرفوضة. |
paid | AUTHORIZED | لا يحدث إلا في الطلبات المنسوخة من Shukah. ولا تضبطه Locate أبدًا. | لا يُبلَغ من routes Locate. |
لا يوجد route للتحديث (refresh). فبعد استدعاء الدفع يراقب التطبيق قراءة الطلب (Get order) أو سجل النقاط حتى تتغير الحالة.
من يستطيع فعل ماذا
| الاستدعاء | من | الجميع الآخرون |
|---|---|---|
| Create | المستدعي هو الدافع دائمًا. ويؤخذ الدافع من الـ token، لا من الـ body أبدًا. | — |
| قراءة الطلب (Get order) | الدافع أو المستلم. | 404 point order not found، وهي الإجابة نفسها لطلب مفقود. |
| الدفع بالبطاقة، الدفع بالجلسة | الدافع فقط. | 404، وللمستلم أيضًا. |
| Description | أي عميل. والمُهدي في الجملة هو المستدعي. | — |
لا مفتاح idempotency، وما يجب فعله بعد 504
ليس لـ Create مفتاح idempotency. فكل استدعاء ينشئ طلبًا جديدًا بـ correlation_id جديد. وإعادة المحاولة بعد
انتهاء المهلة قد تنشئ إذن طلبًا ثانيًا.
و504 تعني أن النتيجة مجهولة. وتقول وثائق order-v2 نفسها إن على التطبيق قراءة الطلب بدل
إعادة المحاولة عشوائيًا. وما تسمح به الشيفرة (قراءة للشيفرة، لا ما تنص عليه الوثائق):
- القراءات، وdescription، والدفع بالجلسة. آمنة لإعادة المحاولة. فاستدعاء الدفع بالجلسة المكرر لا ينشئ إلا جلسة أخرى.
- الدفع بالبطاقة. يعرف التطبيق
correlation_id. اقرأ الطلب أولًا ولا تستدعِ الدفع بالبطاقة مرة أخرى حتى يصبح الطلبsuccessfulأوpayment_failed. فالبوابة لا تجعل مرجع الدفع فريدًا، لذا لا يُثار التقاط ثانٍ إلا كتنبيه (alert) ولا يُسترد. - Create مع
checkout. لم يحصل التطبيق علىcorrelation_id، ولا يوجد route لسرد الطلبات ولا مفتاح idempotency، لذا لا يستطيع إيجاد الطلب. والطلب المكرر غير المدفوع غير ضار: فالطلبات التي لا تُدفع أبدًا تُترك كما هي. - Create مع
point. الشيء نفسه، لكن التكرار يخصم من المرسِل مرتين إذا كان الاستدعاء الأول قد اكتمل. افحص الرصيد والسجل قبل إعادة المحاولة.
وكيف ينبغي للتطبيق إيجاد الطلب بعد 504 في create: غير موثّق بعد.
الـ Flags
مفتاحان في خدمة النقاط يحددان هل يعمل الدفع أصلًا. وقد فُحص كلاهما على dev في 2026-10-01 باستدعاءات لا تكتب شيئًا.
| الـ Flag | ما يتحكم فيه | Dev، فُحص 2026-10-01 | Production | ما يراه التطبيق عند تعطيله |
|---|---|---|---|---|
POINT_ORDER_POINT_ENABLED | Create مع payment_type بالقيمة point. ولا يتأثر checkout. | مفعّل. أجاب body دفع بالنقاط إلى نفسك بـ 400 a point order cannot be paid with points to yourself. ومع تعطيل الـ flag سيكون 403. | غير مؤكَّد. | 403 {"success":false,"error":{"code":"FORBIDDEN","message":"paying a point order with points is disabled"}}، قبل أي بحث أو كتابة. |
POINT_ORDER_CARD_ENABLED | الـ routeان للدفع كليهما. | مفعّل. أجاب pay/session على معرّف طلب عشوائي بـ 404 point order not found. ومع تعطيل الـ flag سيكون 403. | غير مؤكَّد. | 403 {"success":false,"error":{"code":"FORBIDDEN","message":"card payment for point orders is disabled"}}، قبل أي قراءة: حتى معرّف طلب مجهول يحصل على 403. |
تقول وثائق أقدم (2026-09-21 و09-22 و09-25) إن الـ flagين غير مضبوطين في كل بيئة. وتقول وثيقة بتاريخ 2026-09-17 إن شرط الدفع بالنقاط تحقق على dev فقط، وإن production يبقى غير مضبوط حتى طرحه. وهذه الأقوال أقدم من الفحص، لذا ففي dev يسود الفحص. وبالنسبة إلى production: غير موثّق بعد. وتُضبط القيم في بيئة خدمة النقاط وتُدمج في الـ image الخاص بها، لذا يحتاج التغيير إلى build جديد للـ image.
المهل (Timeouts)
ينتظر order-v2 خدمة النقاط 10 ثوانٍ، وينتظر 20 ثانية على routeَي الدفع. والحد الخاص بالتطبيق
لـ order-v2 هو 20 ثانية، لذا قد يستسلم التطبيق على routes الدفع في اللحظة نفسها تقريبًا التي يُصدر فيها
order-v2 504. وتواصل routes الدفع إلى خدمة الدفع، التي لها افتراضيها الخاص البالغ 10 ثوانٍ.
POST /api/v2/client/point-orders
POST /maeda/point-order✅ تم الاستبدال🚩 POINT_ORDER_POINT_ENABLED- 🔴 لا يوجد point_order_id: يبني التطبيق pointOrderId = String(undefined) = "undefined"؛ واسم amount صار money_amount.
- 🔴 وتغيب أيضًا: client_id وto_client_id وpoint_occasion_id وoccasion وdescription وpayment_point_order_id.
- 🔴 تحتاج routes الدفع إلى correlation_id بصيغة UUID في المسار؛ احتفظ به من create وإلا أعطى الدفع 404.
- 🔴 لم تُخصم النقاط المصروفة على جانب من الجانب الآخر خلال ثوانٍ (ولم يُفحص ما بعدها): اقرأ الرصيد والسجل من الجانب الذي يُنشئ الطلبات.
- 🔴 القيمتان payment_type بـ payfort أو hyperpay (يرسل التطبيق قيمة الإعدادات) تعطيان 400؛ ولا تعمل إلا point أو checkout.
- 🔴 يجب أن يكون point_offer_id وpoint_occasion_id معرّفَي hex من 24 حرفًا من قوائم Locate؛ والأعداد الصحيحة من Shukah تعطي 400.
- 🟡 تجيب Shukah بـ 201 وLocate بـ 200؛ وتضيف Locate الحقول correlation_id وorder_no وpayment_status وorder_type.
- 🟡 payment_type بقيمة point إلى معرّفك أنت يعطي 400؛ وإهداء النفس عبر checkout مسموح (وتسمح Shukah بكليهما).
- 🟡 يحتاج point إلى POINT_ORDER_POINT_ENABLED (403 عند التعطيل)؛ وعلى dev لا يفتح أي 403 خطأً عامًا.
- 🟡 لا يوجد idempotency key؛ وبعد 504 لا يعرف التطبيق معرّف الطلب أبدًا.
flag في خدمة النقاط لـ payment_type "point" (403 FORBIDDEN عند التعطيل). مفعّل على dev، وفُحص في 2026-10-01؛ وproduction غير مؤكَّد.
تقبل payment_type بالقيمة point أو checkout فقط (لا payfort ولا hyperpay) وتُرجع طلبًا بشكل مختلف (correlation_id وmoney_amount وغيرهما).
الطلب (Request)
| الحقل | الموضع | النوع | مطلوب | القيود | الوصف |
|---|---|---|---|---|---|
Authorization | header | string | نعم | Bearer <token>: token من Shukah على dev اليوم. أما JWT من Locate فيحصل على 401 عند فحص الـ token نفسه الذي تستخدمه routes النقاط الأخرى (2026-10-02): انظر Which tokens work. | token الدافع. |
Content-Type | header | string | نعم | application/json. | الـ body هو JSON. |
point_offer_id | body | string | نعم | 24 حرف hex، تُفحص ولا تُحوَّل أبدًا. والعدد الصحيح من Shukah أو رقم أو "3" تعطي 400. | id العرض من قائمة العروض. |
to_client_id | body | integer | نعم | عدد صحيح موجب. ويُقبل رقم JSON أو سلسلة رقمية. و" 7 " تصبح 7 وtrue تصبح 1. أما "" وnull والمفتاح المفقود و"abc" و"12.5" فتعطي 400. | معرّف عميل المستلم، كما يُرجعه البحث عن عميل. |
payment_type | body | string | نعم | point أو checkout بالضبط. وcard وpayfort وhyperpay تعطي 400. | point: الدفع بنقاط الدافع في هذا الاستدعاء. checkout: إنشاء طلب غير مدفوع ليُدفع بالبطاقة. |
point_occasion_id | body | string أو null | لا | 24 حرف hex. والمفتاح المفقود وnull و0 و"0" و"" كلها تعني بلا مناسبة. وأي قيمة أخرى غير hex (" " و5) تعطي 400. | id المناسبة من قائمة المناسبات. |
occasion | body | string أو null | لا | تُحذف المسافات الزائدة، من 1 إلى 255 حرفًا. و"" والمسافات وnull تعني لا شيء. وغير السلسلة أو 256 حرفًا تعطي 400. | نص حر، لمناسبة مخصصة. |
| أي مفتاح آخر | body | any | لا | يُسقَط. | — |
الـ body الذي ليس كائن JSON يفشل بـ 400 مع details بالقيمة {"formErrors":["Expected object, received null"],"fieldErrors":{}}.
الاستجابة (Response)
200 مع كائن مجرد (وليس 201). وله تسعة مفاتيح. وتعود المفاتيح التسعة نفسها من
قراءة الطلب (Get order).
| الحقل | النوع | يقبل null | الصيغة / القيود | الوصف |
|---|---|---|---|---|
correlation_id | string | لا | UUID v4، من 36 حرفًا. | معرّف الطلب. ويوضع في مسار routes get والدفع. |
order_no | integer | نعم | القيمة الأولى 100000. وnull فقط في الطلبات المنسوخة من Shukah. | رقم الطلب المقروء للبشر. |
status | string | لا | initial_order أو paid أو payment_failed أو successful. وطلب checkout الجديد هو initial_order، والطلب المدفوع بالنقاط هو successful. | حالة الطلب. |
payment_status | string | نعم | CAPTURED أو FAILED أو AUTHORIZED أو null. وnull عند الإنشاء. | حالة البوابة. |
point_amount | number | لا | قيمة point_amount للعرض. | النقاط التي يحصل عليها المستلم. |
purchased_point_amount | number | نعم | قيمة العرض. وnull عندما لا يكون للعرض سعر بالنقاط. | النقاط التي تُخصم من الدافع عند الدفع بالنقاط. |
money_amount | string | لا | خانتان عشريتان بالضبط، مثل "40.00". | الريال (SAR) الذي تُخصم به البطاقة. |
payment_type | string | لا | point أو checkout. | صدى للطلب. |
order_type | string | لا | gift عندما يُعثر على صف مناسبة، وإلا transfer. ولا يحدده النص الحر أبدًا. | نوع الطلب. |
{
"correlation_id": "2755e9a8-a6b5-4244-9f3d-777efe89ef80",
"order_no": 100010,
"status": "initial_order",
"payment_status": null,
"point_amount": 100,
"purchased_point_amount": 110,
"money_amount": "40.00",
"payment_type": "checkout",
"order_type": "transfer"
}
التُقط على dev، 2026-10-01 (العميل 356259): طلب checkout للعرض الأول، أُرسل إلى
معرّف العميل نفسه، دون مناسبة. ولم يُدفع قط ويبقى على dev، لأنه لا يوجد route يحذف
طلبًا.
checkout وpoint. طلب checkout يكتب صف طلب واحدًا ولا شيء غيره: فلا نقاط تتحرك
ولا يُرسل شيء إلى Shukah أو خدمة الدفع. أما طلب point فيُسوّى في الاستدعاء نفسه. إذ
يخصم من الدافع purchased_point_amount، ويضيف للمستلم point_amount، ويحفظ الطلب
successful. وكل ذلك يحدث معًا أو لا يحدث إطلاقًا. وفي العروض الحية يُخصم من الدافع
أكثر مما يحصل عليه المستلم، مثل 110 نقاط مقابل 100.
الهدايا الذاتية. قد يساوي to_client_id المستدعي في checkout: فشراء نقاط لنفسك هو
الحالة الشائعة على Shukah (814 من 1,228 طلبًا). أما point فترفضه بـ 400.
الأخطاء
تعمل الفحوص بهذا الترتيب لـ point: الـ flag، فالدفع لنفسه، فالعرض، فالمستلم، فالمناسبة،
فـ purchased_point_amount، فملف الدافع، ثم الخصم. ولـ checkout: العرض، فالمستلم، فالمناسبة،
ثم يُحفظ الطلب.
| HTTP | Body | متى |
|---|---|---|
400 | {"success":false,"error":{"code":"VALIDATION_ERROR","message":"Invalid point-order body","details":{"formErrors":[],"fieldErrors":{"payment_type":["Invalid enum value. Expected 'point' | 'checkout', received 'card'"]}}}} | فشل الـ body في الفحص. والمثال هو payment_type: "card"، من تشغيل محلي لـ schema الخدمة، ولم يُلتقط مباشرة. |
400 | {"success":false,"error":{"code":"VALIDATION_ERROR","message":"a point order cannot be paid with points to yourself"}} | payment_type هو point وto_client_id هو المستدعي. التُقط على dev، 2026-10-01. |
400 | الشكل نفسه، message بالقيمة to_client_id <id> does not exist. | المستلم ليس عميلًا معروفًا. لم يُلتقط. |
400 | الشكل نفسه، message بالقيمة point occasion <id> not found or inactive. | معرّف المناسبة مجهول أو غير نشط. لم يُلتقط. |
400 | الشكل نفسه، message بالقيمة point offer <id> cannot be paid with points: it has no purchased_point_amount. | point على عرض بلا سعر بالنقاط. لم يُلتقط. |
400 | الشكل نفسه، message بالقيمة client <id> does not exist. | ليس للدافع ملف (لا يُبلَغ إلا مع point). لم يُلتقط. |
403 | {"success":false,"error":{"code":"FORBIDDEN","message":"paying a point order with points is disabled"}} | payment_type هو point والـ flag معطّل. يُفحص قبل أي بحث. لم يُلتقط، لأن الـ flag كان مفعّلًا. |
404 | {"success":false,"error":{"code":"NOT_FOUND","message":"point offer <id> not found or inactive"}} | العرض مجهول أو غير نشط أو محذوف. لم يُلتقط. |
422 | {"success":false,"error":{"code":"INSUFFICIENT_POINTS","message":"insufficient points for client <id>"}} | point، والرصيد أقل من purchased_point_amount. ولا يُنشأ طلب. لم يُلتقط. |
401 | {"success":false,"error":{"code":"UNAUTHORIZED","message":"Missing or invalid authorization header"}} | لا يوجد header باسم Authorization، أو لا يبدأ بـ Bearer . التُقط على dev، 2026-10-01، دون token. |
401 | الشكل نفسه، مع message بالقيمة Invalid or expired token أو auth service unreachable: <error text> أو auth response missing numeric client id. | رفضت خدمة المصادقة الـ token، أو تعذّر الوصول إليها (فعطل خدمة المصادقة يبدو كـ token سيئ)، أو أجابت دون معرّف عميل. لم يُلتقط على هذا الـ route. وأعطت routes النقاط الأخرى Invalid or expired token لـ JWT من Locate في 2026-10-02. |
500 | {"success":false,"error":{"code":"INTERNAL_ERROR","message":"Internal server error"}} | أي فشل غير متوقع في خدمة النقاط، مثل خطأ في قاعدة البيانات. وتكون الرسالة AUTH_SERVICE_URL not set عندما تكون الخدمة مضبوطة خطأً. لم يُلتقط. |
502 | {"success":false,"error":{"code":"POINT_SERVICE_UNAVAILABLE","message":"Point service unavailable: <error text>"}} | تعذّر على order-v2 الوصول إلى خدمة النقاط. وهذا الـ body صادر عن order-v2، لا عن خدمة النقاط. لم يُلتقط. |
504 | {"success":false,"error":{"code":"POINT_SERVICE_TIMEOUT","message":"Point service did not respond in time; the request outcome is unknown"}} | لم تجب خدمة النقاط خلال 10 ثوانٍ. من order-v2. قد يكون الطلب موجودًا بالفعل: انظر «لا مفتاح idempotency» أعلاه. لم يُلتقط. |
كل خطأ صادر عن خدمة النقاط له الغلاف (envelope) {"success":false,"error":{"code":"…","message":"…"}}.
جرّبه
ينشئ هذا الاستدعاء طلبًا على dev لا يمكن حذفه. استخدم checkout: فـ point يحرّك النقاط.
export ORDER_BASE_URL=https://dev.v2.api.order.locate.sa
export SHUKAH_TOKEN='<shukah-token>' # a Locate JWT gets 401 here today; see "Which tokens work" on Points wallet
export OFFER_ID='<id-from-the-offers-list>'
export TO_CLIENT_ID='<recipient-client-id>'
curl -s -X POST "$ORDER_BASE_URL/api/v2/client/point-orders" -H "Authorization: Bearer $SHUKAH_TOKEN" -H "Content-Type: application/json" -d "{\"point_offer_id\":\"$OFFER_ID\",\"to_client_id\":\"$TO_CLIENT_ID\",\"payment_type\":\"checkout\"}"
المتوقع: 200 والـ body ذو المفاتيح التسعة المبيّن تحت «الاستجابة»، مع status بالقيمة initial_order (التُقط
على dev، 2026-10-02، بـ token من Shukah). أما JWT من Locate فيحصل على 401 عند فحص الـ token (قراءة شيفرة: لم
يُستدعَ create بالـ JWT). وبلا token تحصل على 401 المبيّن تحت «الأخطاء».
الفروق عن Shukah
استُدعي الجانبان على dev في 2026-10-02 (العميل 356259، بـ token من Shukah) بالعرض والمستلم وpayment_type
نفسها: مرة بـ checkout ومرة بـ point، إلى عميل dev آخر. وأجابت Shukah بـ 201 مع صف طلبها (الـ body
في صفحة المقارنة). وأجابت Locate بـ 200. وهذا طلب point
على Locate:
{
"correlation_id": "add67555-e1e4-4fac-ae71-9675a98efffe",
"order_no": 100012,
"status": "successful",
"payment_status": null,
"point_amount": 100,
"purchased_point_amount": 110,
"money_amount": "40.00",
"payment_type": "point",
"order_type": "transfer"
}
التُقط على dev، 2026-10-02. ولطلب checkout (order_no 100011) المفاتيح التسعة نفسها، مع status
بالقيمة initial_order وpayment_type بالقيمة checkout. ويبقى الطلبان على dev.
| الجانب | Shukah | Locate | الأثر على التطبيق |
|---|---|---|---|
| URL | POST $SHUKAH_BASE_URL/maeda/point-order | POST $ORDER_BASE_URL/api/v2/client/point-orders | غيّر base URL والمسار. |
| الـ Token | token معتم (opaque) من Shukah. | token من Shukah فقط على dev اليوم: ونجح مع create في 2026-10-02. أما JWT من Locate فيحصل على 401 عند فحص الـ token في خدمة النقاط، كما في routes النقاط الأخرى (2026-10-02؛ ولم يُجرَّب مع create). | يجب أن يواصل التطبيق إرسال token من Shukah هنا. وللبيئات الأخرى: غير موثّق بعد. |
| حالة HTTP | 201. | 200 (التُقط على dev، 2026-10-02). | لا شيء إذا قبل التطبيق أي 2xx. ويجب تغيير الشيفرة التي تتحقق من 201 بالضبط. |
payment_type | يرسل التطبيق point، أو قيمة payment من الإعدادات العامة: payfort أو hyperpay أو checkout. | point أو checkout فقط. وpayfort وhyperpay تعطيان 400. | إذا قالت الإعدادات payfort أو hyperpay، يفشل create. وهل تُسقَط تلك البوابات لطلبات النقاط: غير موثّق بعد. وعلى Shukah استخدم payfort في 1,126 من 1,225 طلبًا (وثيقة خدمة النقاط بتاريخ 2026-07-29). |
| المعرّفات في الـ body | أعداد صحيحة كنص. | point_offer_id وpoint_occasion_id سلسلتان من 24 حرفًا. وto_client_id دون تغيير. | أرسل المعرّفات من قوائم Locate. والعدد الصحيح من Shukah يعطي 400. |
| مفاتيح الاستجابة | اثنا عشر مفتاحًا: point_order_id وpayment_point_order_id وclient_id وto_client_id وpoint_amount وpurchased_point_amount وamount وstatus وpayment_type وpoint_occasion_id وoccasion وdescription (create، التُقط على dev، 2026-10-02؛ وهي مفاتيح صفوف طلبات Shukah نفسها في قائمة الهدايا المرسَلة). ويعرّف التطبيق occasion_id بدل point_occasion_id، فـ occasionId لديه دائمًا undefined. | تسعة مفاتيح: correlation_id وorder_no وstatus وpayment_status وpoint_amount وpurchased_point_amount وmoney_amount وpayment_type وorder_type. | لا يوجد على الجانبين إلا point_amount وpurchased_point_amount وstatus وpayment_type. واسم amount على Locate هو money_amount، وتغيب point_order_id وclient_id وto_client_id وpoint_occasion_id وoccasion وdescription وpayment_point_order_id. ويقرأ التطبيق point_order_id وamount وpayment_point_order_id ويمرّر معرّف الطلب إلى كل استدعاء دفع، لذا يجب إعادة بناء كائن الطلب من المفاتيح الجديدة. |
| معرّف الطلب | point_order_id: رقم (1256 و1257 من create، 2026-10-02)، يُعاد إرساله كسلسلة ("1252") في أجسام الدفع من التطبيق 2.31.5 (2026-10-02). | UUID، correlation_id. | يأخذه route الدفع وroute القراءة في المسار. ويجيب create دون point_order_id، فيصبح pointOrderId في التطبيق النص "undefined" ويحصل كل استدعاء دفع على 404. |
| هدية لنفسه | مسموحة. | مسموحة لـ checkout، و400 لـ point. | لا شيء لـ checkout. |
| كلمات الحالة | initial_order وpayment_failed وsuccessful وpaid (كما يعرّفها التطبيق). | الكلمات نفسها. وطلب Locate لا يبلغ إلا initial_order وsuccessful وpayment_failed. | لا شيء. |
| Idempotency | لا شيء. | لا شيء. | الخطر نفسه على الجانبين. |
| body الخطأ | غير موثّق بعد. | غلاف خدمة النقاط الوارد تحت «الأخطاء». | يجب تغيير أي شيء يطابق نص الخطأ. |
| رؤية Shukah للطلب | لا ينطبق. | طلب point يضع تحديثين في طابور لـ Shukah، لكن لا شيء يرسل ذلك الطابور على k8s اليوم. وطلب checkout لا يضع شيئًا. | لا ترى Shukah طلبات Locate إلى أن يوجد مُفرِّغ (drainer). ويقرأ تبويب الهدايا المرسَلة من Shukah. |
الرصيد بعد طلب point | خفّض الطلب 1257 رصيد Shukah بمقدار 110 (من 514853.64 إلى 514743.64). ولم يتحرك رصيد Locate. | خفّض الطلب 100012، بعد ثوانٍ، رصيد Locate بمقدار 110 (من 514405.6788 إلى 514295.6788). ولم يتحرك رصيد Shukah. على dev، 2026-10-02. | كاسر (breaking). السجلان (ledgers) غير متزامنين عند الصرف، على الأقل ليس خلال ثوانٍ؛ ولم يُفحص هل يصل الصرف لاحقًا. فإذا أنشأ التطبيق الطلبات على Locate وقرأ الرصيد أو السجل من Shukah (أو العكس)، عرض رصيدًا خاطئًا. |
GET /api/v2/client/point-orders/:correlation_id
route جديد. تقول وثائق خدمة النقاط فقط "Read-through, no settle". ولدى التطبيق استدعاء لطلب
Shukah واحد (GET /maeda/point-order/{id})، لكن لا شاشة تستخدمه، لذا فليس لهذا الـ route
نظير في Shukah ضمن تدفق التطبيق. وأي استدعاء من Shukah يستبدله، إن وُجد: غير موثّق بعد.
الطلب (Request)
| الحقل | الموضع | النوع | مطلوب | القيود | الوصف |
|---|---|---|---|---|---|
Authorization | header | string | نعم | Bearer <token>: token من Shukah على dev اليوم. أما JWT من Locate فيحصل على 401 عند فحص الـ token نفسه الذي تستخدمه routes النقاط الأخرى (2026-10-02): انظر Which tokens work. | token الدافع أو المستلم. |
correlation_id | path | string | نعم | أي سلسلة غير فارغة؛ ولا فحص للصيغة. وGET …/point-orders/description تُقرأ بوصفها المعرّف description. | الـ correlation_id الذي أرجعه create. |
| أي مفتاح query | query | — | لا | يمرّره order-v2 وتتجاهله خدمة النقاط. | — |
الاستجابة (Response)
200 مع المفاتيح التسعة نفسها كما في استجابة create. وبالنسبة إلى طلب منسوخ من Shukah، يكون order_no
هو null وتحمل status وpayment_status قيم Shukah نفسها.
{
"correlation_id": "2755e9a8-a6b5-4244-9f3d-777efe89ef80",
"order_no": 100010,
"status": "initial_order",
"payment_status": null,
"point_amount": 100,
"purchased_point_amount": 110,
"money_amount": "40.00",
"payment_type": "checkout",
"order_type": "transfer"
}
التُقط على dev، 2026-10-01 (العميل 356259): الطلب الذي أنشأته عينة create، وقُرئ مباشرة بعدها.
الاستدعاء للقراءة فقط. فلا يسوّي طلبًا ولا يحدّثه: وتتغير الحالة فقط عندما تُبلّغ البوابة بنتيجة. ولا توجد طريقة لسرد الطلبات، لذا لا يستطيع التطبيق قراءة إلا طلب يملك معرّفه بالفعل.
الأخطاء
| HTTP | Body | متى |
|---|---|---|
404 | {"success":false,"error":{"code":"NOT_FOUND","message":"point order not found"}} | لا يوجد طلب بهذا المعرّف للمستدعي بوصفه دافعًا أو مستلمًا. والطلب المفقود وطلب شخص آخر يعطيان الإجابة نفسها. لم يُلتقط. |
401 | {"success":false,"error":{"code":"UNAUTHORIZED","message":"Missing or invalid authorization header"}} | لا يوجد header باسم Authorization، أو لا يبدأ بـ Bearer . التُقط على dev، 2026-10-01، دون token. |
401 | الشكل نفسه، مع message بالقيمة Invalid or expired token أو auth service unreachable: <error text> أو auth response missing numeric client id. | رفضت خدمة المصادقة الـ token، أو تعذّر الوصول إليها (فعطل خدمة المصادقة يبدو كـ token سيئ)، أو أجابت دون معرّف عميل. لم يُلتقط على هذا الـ route. وأعطت routes النقاط الأخرى Invalid or expired token لـ JWT من Locate في 2026-10-02. |
500 | {"success":false,"error":{"code":"INTERNAL_ERROR","message":"Internal server error"}} | أي فشل غير متوقع في خدمة النقاط، مثل خطأ في قاعدة البيانات. وتكون الرسالة AUTH_SERVICE_URL not set عندما تكون الخدمة مضبوطة خطأً. لم يُلتقط. |
502 | {"success":false,"error":{"code":"POINT_SERVICE_UNAVAILABLE","message":"Point service unavailable: <error text>"}} | تعذّر على order-v2 الوصول إلى خدمة النقاط. وهذا الـ body صادر عن order-v2، لا عن خدمة النقاط. لم يُلتقط. |
504 | {"success":false,"error":{"code":"POINT_SERVICE_TIMEOUT","message":"Point service did not respond in time; the request outcome is unknown"}} | لم تجب خدمة النقاط خلال 10 ثوانٍ. وهذا الـ body صادر عن order-v2. لم يُلتقط. |
جرّبه
export ORDER_BASE_URL=https://dev.v2.api.order.locate.sa
export SHUKAH_TOKEN='<shukah-token>' # a Locate JWT gets 401 here today; see "Which tokens work" on Points wallet
export CORRELATION_ID='<correlation_id-from-create>'
curl -s "$ORDER_BASE_URL/api/v2/client/point-orders/$CORRELATION_ID" -H "Authorization: Bearer $SHUKAH_TOKEN"
المتوقع: 200 والـ body ذو المفاتيح التسعة المبيّن تحت «الاستجابة». والمعرّف الذي ليس لك يعطي 404 المبيّن
تحت «الأخطاء». استخدم token من Shukah على dev اليوم، كما في كل route للنقاط.
POST /api/v2/client/point-orders/description
POST /maeda/point-order/get-description✅ تم الاستبدال- 🟡 تُجيب Shukah بـ {"description":""} وLocate بجملة كاملة؛ للعرض فقط لذا لا يكسر شيئًا.
- 🟡 يجب أن تكون المعرّفات معرّفات hex من 24 حرفًا من Locate؛ وتعطي "1" و"1" من Shukah الرد 400 VALIDATION_ERROR في الحقلين.
- 🟡 الجملة بالإنجليزية دائمًا وتستخدم اسم المستدعي وهاتفه المقنَّع؛ وهي بيانات شخصية فلا تُسجَّل في السجلات.
- 🟡 لم يُجرَّب JWT على هذا الـ route؛ ويرفضه الفحص نفسه على كل route نقاط آخر.
الطلب (Request)
| الحقل | الموضع | النوع | مطلوب | القيود | الوصف |
|---|---|---|---|---|---|
Authorization | header | string | نعم | Bearer <token>: token من Shukah على dev اليوم. أما JWT من Locate فيحصل على 401 عند فحص الـ token نفسه الذي تستخدمه routes النقاط الأخرى (2026-10-02): انظر Which tokens work. | token المُهدي. |
Content-Type | header | string | نعم | application/json. | الـ body هو JSON. |
point_offer_id | body | string | نعم | 24 حرف hex. والعدد الصحيح من Shukah أو الرقم يعطي 400. | id العرض. |
point_occasion_id | body | string أو null | لا | 24 حرف hex. والمفتاح المفقود وnull و"" و"0" و0 تعني لا شيء. وأي قيمة أخرى غير hex تعطي 400. | id المناسبة. |
occasion | body | string أو null | لا | تُحذف المسافات الزائدة، من 1 إلى 255 حرفًا. و"" والمسافات وnull تعني لا شيء. النص الحر يغلب: فعندما لا يكون فارغًا، لا يُبحث عن معرّف المناسبة أصلًا. | نص حر، لمناسبة مخصصة. |
لا يوجد حقل للمستلم. فالجملة هي منظور المستلم، مبنية من المستدعي.
الاستجابة (Response)
200 مع مفتاح واحد:
| الحقل | النوع | يقبل null | الصيغة / القيود | الوصف |
|---|---|---|---|---|
description | string | لا | You have received a gift {occasion} of {points} points from {giver name} ({masked phone}). انظر القواعد أدناه. | النص الذي يعرضه التطبيق بينما يكتب العميل الهدية. |
{
"description": "You have received a gift Birthday of 100 points from Test User (*****0000)"
}
التُقط على dev، 2026-10-01 (العميل 356259)، مع العرض الأول ومناسبة Birthday؛ واستُبدل
اسم المُهدي وأرقام هاتفه بـ Test User و*****0000.
قواعد الجملة:
- النص إنجليزي دائمًا. ولا يوجد مُدخل للغة.
occasionهو النص الحر عندما لا يكون فارغًا بعد حذف المسافات الزائدة. وإلا فهو التسمية الإنجليزية للمناسبة عند إعطاءpoint_occasion_id. وإلا فهو فارغ.- عند غياب المناسبة تحتفظ الجملة بمسافة مزدوجة:
a gift of 400 points. وتحتفظ بها خدمة النقاط عن قصد، كما تفعل Shukah، حتى يستطيع التطبيق تبديل الـ hosts دون تغيير أي نص. لا تحذفها. pointsهيpoint_amountللعرض، أي ما يحصل عليه المستلم، لاpurchased_point_amount.- اسم المُهدي هو الاسم الأول واسم العائلة للمستدعي مفصولين بمسافة، أو
Locate userعندما يكون كلاهما فارغًا. - الهاتف المقنَّع هو
*****يتبعها آخر أربعة أرقام من هاتف المستدعي، أو*****فقط عندما لا توجد أرقام.
{
"description": "You have received a gift of 400 points from Test User (*****0000)"
}
من وثيقة خدمة النقاط locate-point-service@2c44ce3: docs/integrations/2026-07-16-client-facing-point-apis.md
(آخر تغيير 2026-09-22، الأسطر 397-404، وقورنت هناك بـ Shukah على dev في 2026-09-21)، مع استبدال
الاسم والأرقام. وليست التقاطًا من Locate: فلم يُجرَ أي استدعاء بلا مناسبة في 2026-10-01.
تحتوي الجملة الاسم الحقيقي للمستدعي وآخر أربعة أرقام من الهاتف. تعامل معها كبيانات شخصية عند تسجيلها في log.
الأخطاء
| HTTP | Body | متى |
|---|---|---|
400 | {"success":false,"error":{"code":"VALIDATION_ERROR","message":"Invalid description body","details":{"formErrors":[],"fieldErrors":{"point_offer_id":["Required"]}}}} | فشل الـ body في الفحص. والمثال هو {}، من تشغيل محلي لـ schema الخدمة، ولم يُلتقط مباشرة. |
400 | الشكل نفسه، message بالقيمة point occasion <id> not found or inactive. | لا يوجد نص حر، ومعرّف المناسبة مجهول أو غير نشط. لم يُلتقط. |
400 | الشكل نفسه، message بالقيمة client <id> does not exist. | ليس للمستدعي ملف. لم يُلتقط. |
404 | {"success":false,"error":{"code":"NOT_FOUND","message":"point offer <id> not found or inactive"}} | العرض مجهول أو غير نشط أو محذوف. لم يُلتقط. |
401 | {"success":false,"error":{"code":"UNAUTHORIZED","message":"Missing or invalid authorization header"}} | لا يوجد header باسم Authorization، أو لا يبدأ بـ Bearer . التُقط على dev، 2026-10-01، دون token. |
401 | الشكل نفسه، مع message بالقيمة Invalid or expired token أو auth service unreachable: <error text> أو auth response missing numeric client id. | رفضت خدمة المصادقة الـ token، أو تعذّر الوصول إليها (فعطل خدمة المصادقة يبدو كـ token سيئ)، أو أجابت دون معرّف عميل. لم يُلتقط على هذا الـ route. وأعطت routes النقاط الأخرى Invalid or expired token لـ JWT من Locate في 2026-10-02. |
500 | {"success":false,"error":{"code":"INTERNAL_ERROR","message":"Internal server error"}} | أي فشل غير متوقع في خدمة النقاط، مثل خطأ في قاعدة البيانات. وتكون الرسالة AUTH_SERVICE_URL not set عندما تكون الخدمة مضبوطة خطأً. لم يُلتقط. |
502 | {"success":false,"error":{"code":"POINT_SERVICE_UNAVAILABLE","message":"Point service unavailable: <error text>"}} | تعذّر على order-v2 الوصول إلى خدمة النقاط. وهذا الـ body صادر عن order-v2، لا عن خدمة النقاط. لم يُلتقط. |
504 | {"success":false,"error":{"code":"POINT_SERVICE_TIMEOUT","message":"Point service did not respond in time; the request outcome is unknown"}} | لم تجب خدمة النقاط خلال 10 ثوانٍ. وهذا الـ body صادر عن order-v2. لم يُلتقط. |
جرّبه
export ORDER_BASE_URL=https://dev.v2.api.order.locate.sa
export SHUKAH_TOKEN='<shukah-token>' # a Locate JWT gets 401 here today; see "Which tokens work" on Points wallet
export OFFER_ID='<id-from-the-offers-list>'
export OCCASION_ID='<id-from-the-occasions-list>'
curl -s -X POST "$ORDER_BASE_URL/api/v2/client/point-orders/description" -H "Authorization: Bearer $SHUKAH_TOKEN" -H "Content-Type: application/json" -d "{\"occasion\":\"\",\"point_offer_id\":\"$OFFER_ID\",\"point_occasion_id\":\"$OCCASION_ID\"}"
المتوقع: 200 وdescription المبيّن تحت «الاستجابة»، مع اسم المستدعي نفسه وأرقام هاتفه.
وبلا token تحصل على 401 المبيّن تحت «الأخطاء». استخدم token من Shukah على dev اليوم: فقد حصل على 200 في
2026-10-02، أما JWT من Locate فيحصل على 401 على routes النقاط الأخرى.
الفروق عن Shukah
| الجانب | Shukah | Locate | الأثر على التطبيق |
|---|---|---|---|
| URL | POST $SHUKAH_BASE_URL/maeda/point-order/get-description | POST $ORDER_BASE_URL/api/v2/client/point-orders/description | غيّر base URL والمسار. |
| الـ Token | token معتم (opaque) من Shukah. | token من Shukah (200 على dev، 2026-10-02). ولم يُجرَّب JWT من Locate على هذا الـ route؛ والفحص نفسه يعطيه 401 على routes النقاط الأخرى. | يجب أن يواصل التطبيق إرسال token من Shukah هنا. |
| المعرّفات في الـ body | أعداد صحيحة كنص، مثل "1". | سلاسل من 24 حرفًا. والعدد الصحيح من Shukah يعطي 400. | أرسل المعرّفات من قوائم Locate. |
| الإجابة على dev | {"description":""} لكل body جُرّب في 2026-10-01 (معرّفات كنص وكأرقام، ومع نص حر)، ومرة أخرى في 2026-10-02. | الجملة الكاملة (التُقطت على dev، 2026-10-01). | مع Shukah ستكون خانة الرسالة فارغة اليوم. وتقول وثيقة خدمة النقاط إن الجانبين تطابقا في 2026-09-21؛ أما في 2026-10-01 فلم يتطابقا. ولماذا تُرجع Shukah "" الآن: غير موثّق بعد. |
| الجملة | الصياغة نفسها، مع المسافة المزدوجة عند غياب المناسبة (بحسب وثيقة خدمة النقاط، 2026-09-21). | الصياغة نفسها والمسافة المزدوجة نفسها. | لا شيء إذا لم تكن الإجابة فارغة. |
| body الخطأ | غير موثّق بعد. | غلاف خدمة النقاط الوارد تحت «الأخطاء». | يجب تغيير أي شيء يطابق نص الخطأ. |
POST /api/v2/client/point-orders/:correlation_id/pay/card
POST /maeda/order/checkout-payment-standalone✅ تم الاستبدال🚩 POINT_ORDER_CARD_ENABLED- 🔴 ينتقل معرّف الطلب من point_order_id في body (سلسلة "1252") إلى المسار بصيغة correlation_id من نوع UUID.
- 🔴 يحتفظ التطبيق بـ pointOrderId فقط؛ ومن create في Locate تكون قيمته "undefined" فيعطي الاستدعاء 404 "point order not found".
- 🟡 الـ body {id, cvv} نفسه؛ ويُتجاهل point_order_id وpayment_type (pay-point-order-card.ts:7-15).
- 🟡 تتحول cvv بقيمة null إلى النص "null"؛ وإجابة 3DS هي body الخاص بـ payment-api بينما يتوقع نوع التطبيق 3ds.downgraded.
- 🟡 يحتاج إلى POINT_ORDER_CARD_ENABLED (403 FORBIDDEN عند التعطيل)؛ وتجيب Shukah dev بـ 422 "not configured properly".
flag في خدمة النقاط لـ routes الدفع (403 FORBIDDEN عند التعطيل). مفعّل على dev، وفُحص في 2026-10-01؛ وproduction غير مؤكَّد.
لطلبات النقاط فقط: الـ body نفسه (id وcvv)، لكن الطلب يُسمّى في المسار بـ correlation_id لا point_order_id.
يدفع هذا الـ route طلب checkout ببطاقة حفظها العميل. والانتقال من
checkout-payment-standalone في Shukah هو تغيير في URL ومعرّف طلب جديد، لا body جديد. وroute Shukah
نفسه يُستخدم أيضًا لطلبات المطاعم؛ وتغطي هذه الصفحة استخدامه للهدايا فقط.
الطلب (Request)
| الحقل | الموضع | النوع | مطلوب | القيود | الوصف |
|---|---|---|---|---|---|
Authorization | header | string | نعم | Bearer <token>: token من Shukah على dev اليوم. أما JWT من Locate فيحصل على 401 عند فحص الـ token نفسه الذي تستخدمه routes النقاط الأخرى (2026-10-02): انظر Which tokens work. ويُمرَّر الـ token الخام إلى خدمة الدفع. وهل تقبل خدمة الدفع JWT من Locate: غير موثّق بعد. | token الدافع. والمستلم يحصل على 404. |
Content-Type | header | string | نعم | application/json. | الـ body هو JSON. |
correlation_id | path | string | نعم | تُحذف المسافات الزائدة. ومسافات فقط تعطي 400 correlation_id is required. | correlation_id الخاص بالطلب. |
id | body | string | نعم | تُحذف المسافات الزائدة، حرف واحد على الأقل. وغير السلسلة تعطي 400. | معرّف البطاقة المحفوظة (src_…). وهو card.tokenName في التطبيق. |
cvv | body | string | لا | سلسلة أو رقم، تُحذف المسافات الزائدة، من 1 إلى 8 محارف. و"" تُعدّ مفقودة. وnull تصبح النص "null". وتحتاج إليه البوابة لبطاقات MADA. ويُمرَّر ولا يُخزَّن. | رمز أمان البطاقة. |
point_order_id وpayment_type وأي مفتاح آخر | body | any | لا | يُقبل ويُتجاهل. فالطلب يُسمّى بالمسار. | يُبقى حتى يظل body Shukah القديم يعمل. |
الاستجابة (Response)
200 مع body خدمة الدفع، دون تغيير. ولم يُلتقط مباشرة: فالاستدعاء يذهب إلى بوابة الدفع
وينشئ دفعة. ويبدأ التطبيق 3-D Secure من هذا الكائن. ولا تقرأ خدمة النقاط
شيئًا منه، ويبقى الطلب initial_order حتى تُبلّغ البوابة بنتيجة.
{
"id": "pay_2c6e…",
"status": "Pending",
"reference": "lp-<correlation_id>",
"3ds": {"enrolled": "Y", "is_redirect": true},
"_links": {"redirect": {"href": "https://…/sessions-interceptor/sid_…"}}
}
من وثيقة خدمة النقاط locate-point-service@2c44ce3: docs/integrations/2026-07-16-client-facing-point-apis.md
(القسم 3.6، آخر تغيير 2026-09-22)، مع اختصار القيم الطويلة في الوثيقة نفسها. لم يُلتقط
مباشرة، لأن الاستدعاء ينشئ دفعة في البوابة والخطة تمنع ذلك.
عادت إحدى الاستجابات من 902 وهي Declined بالفعل دون _links.redirect. وعلى التطبيق التعامل مع
هذه الحالة.
الأخطاء
تعمل الفحوص بهذا الترتيب: الـ flag، فالطلب لهذا الدافع، فنوع الدفع، فنوع الطلب،
فـ successful، فـ payment_failed، فملف الدافع، ثم المبلغ.
| HTTP | Body | متى |
|---|---|---|
400 | {"success":false,"error":{"code":"VALIDATION_ERROR","message":"Invalid pay-by-card body","details":{…}}} | فشل الـ body في الفحص، مثل غياب id. لم يُلتقط. |
400 | الشكل نفسه، message بالقيمة this point order is not paid by card. | payment_type للطلب ليس checkout. لم يُلتقط. |
400 | الشكل نفسه، message بالقيمة this point order is not payable by card. | نُسخ الطلب من Shukah، أو لا معرّف إضافة (credit id) وراءه. لم يُلتقط. |
400 | الشكل نفسه، message بالقيمة client <id> does not exist أو point order <correlation_id> has a non-positive money_amount. | ليس للدافع ملف، أو المبلغ صفر أو سالب. لم يُلتقط. |
403 | {"success":false,"error":{"code":"FORBIDDEN","message":"card payment for point orders is disabled"}} | POINT_ORDER_CARD_ENABLED معطّل. يُفحص أولًا، قبل أي قراءة. لم يُلتقط، لأن الـ flag كان مفعّلًا. |
404 | {"success":false,"error":{"code":"NOT_FOUND","message":"point order not found"}} | لا يوجد طلب كهذا للمستدعي بوصفه دافعًا. والمستلم يحصل على هذا أيضًا. لم يُلتقط. |
409 | {"success":false,"error":{"code":"CONFLICT","message":"point order is already paid"}} | الطلب successful. لم يُلتقط. |
409 | الشكل نفسه، message بالقيمة point order payment failed; create a new order. | الطلب payment_failed. والرفض نهائي. لم يُلتقط. |
| حالة خدمة الدفع | body خدمة الدفع نفسه، دون تغيير. مثل 422 مع source_id_invalid لبطاقة محفوظة ميتة، أو 400 لـ CVV مفقود في MADA. | أجابت خدمة الدفع بـ 4xx. لا توجد دفعة ويبقى الطلب قابلًا للدفع. لم يُلتقط. |
500 | {"success":false,"error":{"code":"INTERNAL_ERROR","message":"Internal server error"}}، أو message بالقيمة PAYMENT_API_URL not set. | تعذّر الوصول إلى خدمة الدفع، أو لم تجب خلال 10 ثوانٍ، أو أجابت 5xx: ولا يُسجَّل شيء. لم يُلتقط. |
401 | {"success":false,"error":{"code":"UNAUTHORIZED","message":"Missing or invalid authorization header"}} | لا يوجد header باسم Authorization، أو لا يبدأ بـ Bearer . التُقط على dev، 2026-10-01، دون token. |
401 | الشكل نفسه، مع message بالقيمة Invalid or expired token أو auth service unreachable: <error text> أو auth response missing numeric client id. | رفضت خدمة المصادقة الـ token، أو تعذّر الوصول إليها (فعطل خدمة المصادقة يبدو كـ token سيئ)، أو أجابت دون معرّف عميل. لم يُلتقط على هذا الـ route. وأعطت routes النقاط الأخرى Invalid or expired token لـ JWT من Locate في 2026-10-02. |
502 | {"success":false,"error":{"code":"POINT_SERVICE_UNAVAILABLE","message":"Point service unavailable: <error text>"}} | تعذّر على order-v2 الوصول إلى خدمة النقاط. وهذا الـ body صادر عن order-v2، لا عن خدمة النقاط. لم يُلتقط. |
504 | {"success":false,"error":{"code":"POINT_SERVICE_TIMEOUT","message":"Point service did not respond in time; the request outcome is unknown"}} | لم تجب خدمة النقاط خلال 20 ثانية. من order-v2. قد تكون الدفعة موجودة بالفعل في البوابة: اقرأ الطلب ولا تستدعِ مرة أخرى. لم يُلتقط. |
جرّبه
يذهب هذا الاستدعاء إلى بوابة الدفع وقد يخصم من بطاقة. شغّله على dev فقط، ببطاقة اختبار وبطلب أنشأته بنفسك.
export ORDER_BASE_URL=https://dev.v2.api.order.locate.sa
export SHUKAH_TOKEN='<shukah-token>' # a Locate JWT gets 401 here today; see "Which tokens work" on Points wallet
export CORRELATION_ID='<correlation_id-from-create>'
export CARD_ID='<saved-card-id>'
curl -s -X POST "$ORDER_BASE_URL/api/v2/client/point-orders/$CORRELATION_ID/pay/card" -H "Authorization: Bearer $SHUKAH_TOKEN" -H "Content-Type: application/json" -d "{\"id\":\"$CARD_ID\"}"
المتوقع: 200 وكائن الدفعة المبيّن تحت «الاستجابة». ويبقى الطلب initial_order حتى
تُبلّغ البوابة بالنتيجة. استخدم token من Shukah على dev اليوم، كما في كل route للنقاط.
الفروق عن Shukah
| الجانب | Shukah | Locate | الأثر على التطبيق |
|---|---|---|---|
| URL | POST $SHUKAH_BASE_URL/maeda/order/checkout-payment-standalone | POST $ORDER_BASE_URL/api/v2/client/point-orders/{correlation_id}/pay/card | غيّر base URL والمسار، وضع فيه correlation_id الخاص بالطلب. |
| الـ Body | {id, cvv, point_order_id}. | {id, cvv}. ويُقبل point_order_id وpayment_type ويُتجاهلان. | لا شيء: الأسماء نفسها. |
| معرّف الطلب | point_order_id في الـ body، وهو سلسلة مثل "1252" (أجسام الدفع من التطبيق 2.31.5، التُقطت على dev، 2026-10-02). | UUID، correlation_id، في المسار. | يحتفظ التطبيق بـ pointOrderId فقط. وعليه أن يحتفظ بالمعرّف الجديد من create. |
| الـ Token | token معتم (opaque) من Shukah. | token من Shukah فقط على dev اليوم: فـ JWT من Locate يحصل على 401 عند الفحص نفسه الذي تستخدمه routes النقاط الأخرى (2026-10-02). ويذهب الـ token الخام أيضًا إلى خدمة الدفع. | يجب أن يواصل التطبيق إرسال token من Shukah هنا. |
| Shukah على dev | 422 {"name":"Unprocessable Content","message":"Checkout payment is not configured properly.",…} لمدفوعات طلبات النقاط (2026-10-02). | لم يُستدعَ مباشرة. | لا تستطيع Shukah على dev قبول دفعة نقاط اليوم، لذا لم يمكن مقارنة الجانبين مباشرة. |
| الـ Flag | لا شيء. | POINT_ORDER_CARD_ENABLED: مفعّل على dev (فُحص 2026-10-01)، وproduction غير مؤكَّد. | إذا كان الـ flag معطّلًا، فكل استدعاء 403. |
| أخطاء الدفع | 4xx الخاص بخدمة الدفع نفسها. | تُمرَّر دون تغيير بالحالة والـ body نفسيهما. | لا شيء: فالتطبيق يتعامل مع هذا الشكل بالفعل. |
| الاستجابة | نوع التطبيق فيه 3ds.downgraded. | عينة الوثيقة فيها 3ds.is_redirect، وقد تعود دفعة مرفوضة دون _links.redirect، الذي يشترطه نوع التطبيق. | افحص بدء 3-D Secure مقابل استجابة حقيقية. |
| مدفوعات الهدايا الأخرى | يستخدم التطبيق أيضًا checkout-payment وcheckout-payment-stcpay واستدعاءات PayFort واستدعاء CVV-token. | لا route لها. ولا يوجد سوى هذا والدفع بالجلسة. | انظر «التفاصيل» للاستدعاءات التي لم تُستبدل. |
| body الخطأ | غير موثّق بعد. | غلاف خدمة النقاط الوارد تحت «الأخطاء». | يجب تغيير أي شيء يطابق نص الخطأ. |
POST /api/v2/client/point-orders/:correlation_id/pay/session
POST /maeda/order/checkout-payment-flow✅ تم الاستبدال🚩 POINT_ORDER_CARD_ENABLED- 🔴 ينتقل معرّف الطلب من point_order_id في body (سلسلة "1253") إلى المسار بصيغة correlation_id من نوع UUID.
- 🔴 يحتفظ التطبيق بـ pointOrderId فقط؛ ومن create في Locate تكون قيمته "undefined" فيعطي الاستدعاء 404.
- 🟡 يُتجاهل payment_type بقيمة "applePay" وpoint_order_id؛ وتعرض جلسة Checkout المحافظ الإلكترونية.
- 🟡 الـ body اختياري ({rememberMe})؛ والسلسلة "true" أو "false" تُحوَّل، أما الرقم فيعطي 400.
- 🟡 يحتاج إلى POINT_ORDER_CARD_ENABLED؛ وتجيب Shukah dev بـ 422 "Checkout payment is not configured properly."
flag في خدمة النقاط لـ routes الدفع (403 FORBIDDEN عند التعطيل). مفعّل على dev، وفُحص في 2026-10-01؛ وproduction غير مؤكَّد.
لطلبات النقاط فقط: الـ body نفسه (rememberMe)؛ ويُتجاهل point_order_id وpayment_type ويُسمّى الطلب بـ correlation_id في المسار.
يبدأ هذا الـ route جلسة دفع لطلب checkout. ويستخدمه Checkout SDK لبطاقة جديدة وApple Pay
وGoogle Pay وSTC Pay. ولا يخصم شيئًا بنفسه. وroute Shukah نفسه يُستخدم أيضًا
لطلبات المطاعم؛ وتغطي هذه الصفحة استخدامه للهدايا فقط.
الطلب (Request)
| الحقل | الموضع | النوع | مطلوب | القيود | الوصف |
|---|---|---|---|---|---|
Authorization | header | string | نعم | Bearer <token>: token من Shukah على dev اليوم. أما JWT من Locate فيحصل على 401 عند فحص الـ token نفسه الذي تستخدمه routes النقاط الأخرى (2026-10-02): انظر Which tokens work. | token الدافع. والمستلم يحصل على 404. |
Content-Type | header | string | لا | application/json. والـ body الفارغ أو المفقود صالح ويعني {}. | الـ body هو JSON. |
correlation_id | path | string | نعم | تُحذف المسافات الزائدة. ومسافات فقط تعطي 400 correlation_id is required. | correlation_id الخاص بالطلب. |
rememberMe | body | boolean | لا | true أو false أو النص "true" أو "false". الافتراضي false. والرقم أو null أو أي نص آخر يعطي 400. | حفظ البطاقة، مسجَّلة باسم الدافع، بعد دفعة ناجحة. |
point_order_id وpayment_type وأي مفتاح آخر | body | any | لا | يُقبل ويُتجاهل. | يُبقى حتى يظل body Shukah القديم يعمل. |
الاستجابة (Response)
200 مع جلسة خدمة الدفع، دون تغيير. ولم تُلتقط مباشرة: فالاستدعاء يذهب إلى
بوابة الدفع. ويبدأ Checkout SDK من هذا الكائن. ويمكن التخلي عن الجلسة وإنشاء أخرى
على الطلب نفسه؛ وقد شوهدت تسع على مرجع واحد، ولا واحدة منها خطأ.
{
"id": "ps_3J5G…",
"payment_session_secret": "<redacted-secret>",
"payment_session_token": "<redacted-token>",
"_links": {"self": {"href": "…"}}
}
من وثيقة خدمة النقاط locate-point-service@2c44ce3: docs/plans/2026-09-21-point-order-manual-test-guide.md
(الأسطر 336-343، آخر تغيير 2026-09-22)، مع استبدال السر (secret) والـ token. ولم تُلتقط
مباشرة، لأن الاستدعاء يفتح جلسة دفع في البوابة والخطة تمنع ذلك.
الأخطاء
الحراس أنفسهم الخاصون بـ الدفع بالبطاقة، بالترتيب نفسه. وتُسرد الصفوف كاملة.
| HTTP | Body | متى |
|---|---|---|
400 | {"success":false,"error":{"code":"VALIDATION_ERROR","message":"Invalid pay-by-session body","details":{…}}} | rememberMe ليست قيمة تشبه boolean. لم يُلتقط. |
400 | الشكل نفسه، message بالقيمة this point order is not paid by card أو this point order is not payable by card. | الطلب ليس طلب checkout أنشأته Locate. لم يُلتقط. |
400 | الشكل نفسه، message بالقيمة client <id> does not exist أو point order <correlation_id> has a non-positive money_amount. | ليس للدافع ملف، أو المبلغ صفر أو سالب. لم يُلتقط. |
403 | {"success":false,"error":{"code":"FORBIDDEN","message":"card payment for point orders is disabled"}} | POINT_ORDER_CARD_ENABLED معطّل. يُفحص أولًا، قبل أي قراءة. لم يُلتقط، لأن الـ flag كان مفعّلًا. |
404 | {"success":false,"error":{"code":"NOT_FOUND","message":"point order not found"}} | لا يوجد طلب كهذا للمستدعي بوصفه دافعًا. التُقط على dev، 2026-10-01، بمعرّف طلب عشوائي (وبهذه الطريقة فُحص الـ flag). |
409 | {"success":false,"error":{"code":"CONFLICT","message":"point order is already paid"}}، أو message بالقيمة point order payment failed; create a new order. | الطلب successful، أو هو payment_failed. لم يُلتقط. |
| حالة خدمة الدفع | body خدمة الدفع نفسه، دون تغيير. | أجابت خدمة الدفع بـ 4xx. لم يُلتقط. |
500 | {"success":false,"error":{"code":"INTERNAL_ERROR","message":"Internal server error"}}، أو message بالقيمة PAYMENT_API_URL not set. | تعذّر الوصول إلى خدمة الدفع، أو لم تجب خلال 10 ثوانٍ، أو أجابت 5xx. لم يُلتقط. |
401 | {"success":false,"error":{"code":"UNAUTHORIZED","message":"Missing or invalid authorization header"}} | لا يوجد header باسم Authorization، أو لا يبدأ بـ Bearer . التُقط على dev، 2026-10-01، دون token. |
401 | الشكل نفسه، مع message بالقيمة Invalid or expired token أو auth service unreachable: <error text> أو auth response missing numeric client id. | رفضت خدمة المصادقة الـ token، أو تعذّر الوصول إليها (فعطل خدمة المصادقة يبدو كـ token سيئ)، أو أجابت دون معرّف عميل. لم يُلتقط على هذا الـ route. وأعطت routes النقاط الأخرى Invalid or expired token لـ JWT من Locate في 2026-10-02. |
502 | {"success":false,"error":{"code":"POINT_SERVICE_UNAVAILABLE","message":"Point service unavailable: <error text>"}} | تعذّر على order-v2 الوصول إلى خدمة النقاط. وهذا الـ body صادر عن order-v2، لا عن خدمة النقاط. لم يُلتقط. |
504 | {"success":false,"error":{"code":"POINT_SERVICE_TIMEOUT","message":"Point service did not respond in time; the request outcome is unknown"}} | لم تجب خدمة النقاط خلال 20 ثانية. من order-v2. و504 هنا آمن لإعادة المحاولة: فالاستدعاء المكرر لا ينشئ إلا جلسة أخرى. لم يُلتقط. |
جرّبه
يفتح هذا الاستدعاء جلسة دفع في البوابة. شغّله على dev فقط، بطلب أنشأته بنفسك.
export ORDER_BASE_URL=https://dev.v2.api.order.locate.sa
export SHUKAH_TOKEN='<shukah-token>' # a Locate JWT gets 401 here today; see "Which tokens work" on Points wallet
export CORRELATION_ID='<correlation_id-from-create>'
curl -s -X POST "$ORDER_BASE_URL/api/v2/client/point-orders/$CORRELATION_ID/pay/session" -H "Authorization: Bearer $SHUKAH_TOKEN" -H "Content-Type: application/json" -d '{"rememberMe":false}'
المتوقع: 200 وكائن الجلسة المبيّن تحت «الاستجابة». وcorrelation_id عشوائي يعطي 404
المبيّن تحت «الأخطاء» (مع تفعيل الـ flag). استخدم token من Shukah على dev اليوم، كما في كل route للنقاط.
الفروق عن Shukah
| الجانب | Shukah | Locate | الأثر على التطبيق |
|---|---|---|---|
| URL | POST $SHUKAH_BASE_URL/maeda/order/checkout-payment-flow | POST $ORDER_BASE_URL/api/v2/client/point-orders/{correlation_id}/pay/session | غيّر base URL والمسار، وضع فيه correlation_id الخاص بالطلب. |
| الـ Body | {point_order_id, payment_type, rememberMe}. | {rememberMe}. ويُقبل point_order_id وpayment_type ويُتجاهلان. | لا شيء: الأسماء نفسها. |
payment_type | أرسل التطبيق 2.31.5 القيمة applePay (التُقط على dev، 2026-10-02). ويسمح نوع النسخة اللحظية أيضًا بـ stcpay وcard، لكن التطبيق 2.31.5 يرسل STC Pay إلى route checkout-payment-stcpay الخاص بـ Shukah نفسها. | يُتجاهل. وتعرض جلسة Checkout المحافظ (wallets). | لا تُختار المحفظة على الخادم. افحص أن الـ SDK يعرض المحفظة الصحيحة. وليس لـ STC Pay route خاص في Locate. |
| معرّف الطلب | سلسلة في الـ body: {"point_order_id":"1253",…} في التقاط التطبيق 2.31.5 (2026-10-02). | UUID، في المسار. | على التطبيق الاحتفاظ بـ correlation_id من create. |
| Shukah على dev | استدعاها التطبيق 2.31.5 على dev في 2026-10-02 وحصل على 422 Checkout payment is not configured properly. | لم يُستدعَ مباشرة. | لا تستطيع Shukah على dev قبول دفعة نقاط اليوم، لذا لم يمكن مقارنة الجانبين مباشرة. |
| الـ Flag | لا شيء. | POINT_ORDER_CARD_ENABLED: مفعّل على dev (فُحص 2026-10-01)، وproduction غير مؤكَّد. | إذا كان الـ flag معطّلًا، فكل استدعاء 403. |
| الاستجابة | غير موثّق بعد (لم يُلتقط). | جلسة خدمة الدفع، دون تغيير. | نوع التطبيق فيه id وpayment_session_secret وpayment_session_token و_links.self، وهي المفاتيح نفسها في عينة الوثيقة. |
| body الخطأ | غير موثّق بعد. | غلاف خدمة النقاط الوارد تحت «الأخطاء». | يجب تغيير أي شيء يطابق نص الخطأ. |
تنبيه
- للطلب مفاتيح جديدة (
correlation_idوmoney_amount) وتأخذ routes الدفعcorrelation_idفي المسار، لذا يجب على التطبيق الاحتفاظ به من create. - ليس لـ Create مفتاح idempotency: فبعد
504لا يعرف التطبيق معرّف الطلب أبدًا، وقد تنشئ إعادة المحاولة طلبًا ثانيًا أو، معpoint، تخصم مرتين. - يحتاج الدفع بالنقاط وrouteا الدفع إلى flagين. وكلاهما مفعّل على dev في 2026-10-01؛ وproduction غير مؤكَّد، والـ flag المعطّل يعطي
403.
التفاصيل (للـ backend)
فُحص dev فقط، عبر order-v2 على https://dev.v2.api.order.locate.sa، والبادئة /api/v2/client.
الـ hosts في UAT وproduction: غير موثّق بعد. في هذا القسم تعني OV2: المستودع
locate-order-v2-api@d9e4e5f6 وتعني PS: المستودع locate-point-service@2c44ce3. وكيف يعمل الـ proxy وفحص
الـ token وغلاف الأخطاء موصوف مرة واحدة، تحت
كيف تعمل الاستدعاءات الممرَّرة.
الـ Routes والـ proxy
| route التطبيق (order-v2) | route المصدر (خدمة النقاط) | ما يُمرَّر | الحد |
|---|---|---|---|
POST /api/v2/client/point-orders | POST /api/client/point-orders | Authorization والـ body كما استُلم | 10 ث |
GET /api/v2/client/point-orders/:correlation_id | GET /api/client/point-orders/<id>، مع تمرير المعرّف عبر encodeURIComponent | Authorization والـ query كله (تتجاهله خدمة النقاط) | 10 ث |
POST /api/v2/client/point-orders/description | POST /api/client/point-orders/description | Authorization والـ body | 10 ث |
POST /api/v2/client/point-orders/:correlation_id/pay/card | POST /api/client/point-orders/<id>/pay/card | Authorization والـ body | 20 ث |
POST /api/v2/client/point-orders/:correlation_id/pay/session | POST /api/client/point-orders/<id>/pay/session | Authorization والـ body | 20 ث |
(OV2: src/modules/client/point/point.controller.ts:10,110-201،
PS: services/api/src/routes/client.routes.ts:23-29.) يُركَّب route الحرفي POST point-orders/description
قبل GET point-orders/:correlation_id، لذا فإن GET …/point-orders/description الشاردة
تُقرأ بوصفها معرّف الطلب description وتجيب 404 point order not found
(PS: services/api/src/routes/client.routes.ts:24-27،
PS: services/api/src/routes/client.routes.test.ts:43-75). ويحتفظ route الـ description بالافتراضي
10 ثوانٍ، لأنه يركّب نصًا فقط (OV2: src/modules/client/point/point.controller.ts:126-131).
وتحتفظ POST بحالة المصدر: فـ 200 تبقى 200، لا 201 الافتراضية في Nest (شوهد في إعادة إنتاج محلية
لنمط الـ proxy؛ OV2: src/modules/client/point/point.service.ts:64). أما routes خدمة النقاط الداخلية، بما فيها
الـ webhook و…/refresh، فلا تُمرَّر أبدًا
(OV2: docs/plans/2026-09-16-point-order-proxy-plan.md:21،
OV2: docs/locate-point-client-api-reference.md:46).
Create
- يفحص الـ handler الـ body بـ schema، ثم تشغّل
createPointOrderالفحوص بالترتيب الوارد تحت «الأخطاء» (PS: services/api/src/handlers/create-point-order.ts:10-47،PS: packages/core/src/points/create-point-order.ts:66-186). المعرّفان كلاهما سلسلتا hex من 24 حرفًا ولا يُحوَّلان أبدًا (PS: services/api/src/lib/object-id.ts:12-14). ويجب أن يوجد المستلم في مجموعةclientsالمشتركة (PS: packages/core/src/points/client-lookup.ts:20-35). checkout: إدراج صف طلب واحد وزيادة عدّاد لـorder_no. ولا صف في السجل (ledger) ولا صف في outbox (PS: packages/core/src/points/create-point-order.ts:162-172). ويبقى الطلب غير مدفوع ويُترك كما هو إن هُجر (PS: docs/plans/2026-09-10-point-order-write-path-design.md:692).point: معاملة واحدة تكتب مصروفات الدافع، وكسب المستلم، والطلب (successful، وpayment_statusبالقيمةnull)، وصفّي outbox موجهين إلى Shukah (mirror_reduce_pointوmirror_add_point). وتُنفَّذ المصروفات أولًا، فيكون حارس السحب على المكشوف (overdraft guard) هو فحص الرصيد، ولا يُترك شيء عند الفشل (PS: packages/core/src/points/create-point-order.ts:193-258،PS: packages/core/src/points/apply-point-txn.ts:71-86). ولا شيء يفرّغ الـ outbox على k8s اليوم، لذا لا ترى Shukah هذين الطرفين إلى أن يوجد مُفرِّغ (drainer) (PS: CLAUDE.md:202-205،PS: .env.example:95-97،PS: docs/plans/2026-09-10-point-order-write-path-design.md:811). وتتفق الاستدعاءات الحية في 2026-10-02 مع ذلك: فطلبpointعلى Locate لم يغيّر رصيد Shukah، وطلب على Shukah لم يغيّر رصيد Locate، خلال ثواني التشغيل. ولم يُفحص هل يلحق أيٌّ من الجانبين لاحقًا.- الهامش (Markup). على dev يدفع الدافع 110 مقابل 100، ثم 240 و360 و480 و600. ومثال وثيقة التصميم "10 %
markup" هو العرض الأول، وتحمل البقية 20 %
(
PS: docs/plans/2026-08-03-point-order-flow-design.md:31). - ما يخزّنه الطلب.
offer_snapshotللمبالغ، وتسمية المناسبة من صف المناسبة، والوصف الإنجليزي من جهة المرسِل، وثلاثة معرّفات ثابتة (expense_correlation_idوearn_correlation_idوtransfer_id) (PS: packages/core/src/points/create-point-order.ts:119-160). ويبدأorder_noمن 100000 من عدّاد، وcorrelation_idهو UUID v4 (PS: packages/core/src/points/order-no.ts:12,20-28،PS: packages/core/src/points/correlation-id.ts:8-10). - النص الحر. يدخل
occasionفي الوصف المخزَّن، وفيpointيدخل في طرفي السجل. ولا يُخزَّن على الطلب كحقل. وبالنسبة إلى طلبcheckoutتضيف النقاطَ له لاحقًا خطّافُ البوابة (gateway hook)، يُبنى نص المستلم من التسمية الإنجليزية لصف المناسبة، لا من النص الحر للعميل. وهذه النقطة الأخيرة مقروءة من الشيفرة ولا تنص عليها أي وثيقة (PS: packages/core/src/points/create-point-order.ts:119-125,190،PS: packages/core/src/points/settle-point-order.ts:231). order_typeهوgiftعندما يُعثر على صف مناسبة (بعدده الصحيح في Shukah أو بمعرّفه الخاص) وtransferخلاف ذلك (PS: packages/core/src/points/derive-order-type.ts:16-34). وتُظهر عينة وثيقة خدمة النقاطgiftلـ body فيه مناسبة. أما استدعاءcheckoutالحي فلم تكن فيه مناسبة، لذا هوtransfer.- لا مفتاح idempotency. كل استدعاء يخصّص
correlation_idوorder_noجديدين (PS: packages/core/src/points/create-point-order.ts:109-113,165؛PS: docs/integrations/2026-07-16-client-facing-point-apis.md:237-238).
التسوية وخطّاف البوابة
الحالات هي قيم mt_point_order في Shukah محفوظة كما هي
(PS: docs/plans/2026-07-29-point-orders-verbatim-shukah-values.md:19-34،
PS: packages/core/src/db/models/point-order.model.ts:25,30-31). والطلبات المنشأة في Locate لا تبلغ إلا
initial_order وsuccessful وpayment_failed
(PS: packages/core/src/points/settle-point-order.ts:183-185). وتُبلّغ البوابة بالنتائج إلى
POST /internal/point-orders/webhooks/checkout، وهو ليس route للتطبيق ويحتاج إلى مفتاح خدمة
(PS: services/api/src/handlers/checkout-webhook.ts:9-41).
- الدفعة الملتقَطة (captured) التي يطابق مبلغها وعملتها تضيف للمستلم طرف كسب واحدًا وتكتب صف
outbox واحدًا، وتضبط
successfulوCAPTURED. ولا تُكتب مصروفات للدافع (PS: packages/core/src/points/settle-point-order.ts:117-119,195-328). - الدفعة المرفوضة على
initial_orderتضبطpayment_failedوFAILED(PS: packages/core/src/points/settle-point-order.ts:33-42,125-145). - الالتقاط على طلب
payment_failedيبقى فاشلًا، ويُطلب استرداد (refund) (PS: packages/core/src/points/settle-point-order.ts:121-123,331-392). - عدم تطابق المبلغ أو العملة يترك الطلب
initial_order، ويعلّمه بأنه متوقف (halted) ويُثير تنبيهًا (alert). ولا يُسوّى تلقائيًا أبدًا (PS: packages/core/src/points/settle-point-order.ts:201-224). - التسوية آمنة للتكرار: فتُحجز الإضافة (credit) بنقل الطلب خارج
initial_order(PS: packages/core/src/points/settle-point-order.ts:50-55).
الـ routeان للدفع
- كلاهما يبدأ بـ
loadPayableOrder: الـ flag، ثم بحث عن الطلب يتضمن معرّف الدافع (فالطلب المفقود وطلب شخص آخر يعطيان404نفسه)، ثم نوع الدفع، وفحص التاجر (merchant)، وsuccessful، وpayment_failed، وملف الدافع، والمبلغ (PS: packages/core/src/points/pay-point-order.ts:42-102). - خدمة الدفع هي مستودع
payment-api؛ ولم يُقرأ لهذه الصفحة. - البطاقة. الاستدعاء الصادر هو
POST {PAYMENT_API_URL}/client/api/v1/checkout/lp-<correlation_id>/payments/standalone. الجلسة. هوPOST {PAYMENT_API_URL}/client/api/v1/checkout/payment/lp-<correlation_id>/session. ويرسل كلاهما الـ headerstoken(الـ token الخام للعميل) وx-from-application: LOCATE_CLIENTوx-transaction-type: POINT. وفي الـ bodyamountبالريال (SAR)، وcustomerId(الدافع)، وcustomerName، وcustomerPhone، وidوcvv(البطاقة) أوrememberMe(الجلسة). والدافع هو العميل (customer)، لا المستلم أبدًا. ومرجع البوابة هوlp-<correlation_id>(PS: packages/core/src/points/payment-api-client.ts:125-164،PS: packages/core/src/points/pay-point-order.ts:29-33,134-174). - الكتابة الوحيدة ملاحظة لتصحيح الأخطاء (
payment_providerوpayment_reference)، تُكتب بذل أقصى جهد (best effort). ولا تحدث هنا كتابة في السجل ولا تغيير في الحالة (PS: packages/core/src/points/pay-point-order.ts:104-130). - يُمرَّر
cvvولا تخزّنه خدمة النقاط ولا تسجّله في log أبدًا (PS: services/api/src/middleware/request-logger.ts:5). أما order-v2 فيسجّل أجسام الطلبات دون إخفاء، بما فيها هذا (انظر قسم الـ proxy). - أخطاء خدمة الدفع. يُمرَّر
4xxبحالته وbody الخاصين به. أما فشل النقل أو انتهاء المهلة أو5xxفلا يُلتقط ويصبحINTERNAL_ERROR500، دون تسجيل شيء (PS: services/api/src/handlers/pay-point-order-card.ts:38-44،PS: services/api/src/handlers/pay-point-order-session.ts:38-40،PS: packages/core/src/points/payment-api-client.ts:22-30,70-74,86-92,113-118،PS: docs/plans/2026-09-10-point-order-write-path-design.md:684). - سلسلة المهل: التطبيق 20 ث، وorder-v2 20 ث، وخدمة الدفع 10 ث افتراضيًا
(
PAYMENT_API_TIMEOUT_MS) (mobile/locate-client@e960c3a: src/shared/api/instances/orderApiV2.ts:16،PS: packages/core/src/points/payment-api-client.ts:7-20). - يطلب الدفع المكرر بالبطاقة من خدمة الدفع دفعة جديدة على المرجع نفسه. والبوابة
لا تجعل المرجع فريدًا، لذا لا يُثار التقاط ثانٍ إلا كتنبيه
(
PS: packages/core/src/points/settle-point-order.ts:169-181،PS: docs/plans/2026-09-21-point-order-manual-test-guide.md:446). - وهل تقبل خدمة الدفع JWT من Locate في header الـ
token: غير موثّق بعد.
الـ Flags
يُقرأ POINT_ORDER_POINT_ENABLED بالشكل process.env.POINT_ORDER_POINT_ENABLED !== 'true' و
POINT_ORDER_CARD_ENABLED بالطريقة نفسها، لذا لا يفعّلهما إلا النص true بالضبط
(PS: packages/core/src/points/create-point-order.ts:69-72،
PS: packages/core/src/points/pay-point-order.ts:45-47). وقيم الـ flags مدمجة في الـ image: إذ
ينسخ الـ build الملف /data/compose/point-api/.env إليه (PS: Jenkinsfile-k8s:25-28,115-122،
PS: Dockerfile:22-23). واستخدم الفحص في 2026-10-01 استدعاءين لا يكتبان شيئًا: body من نوع point بمعرّف
عرض كله أصفار والمستدعي بوصفه المستلم (فحص الـ flag وفحص الدفع لنفسه كلاهما يعملان
قبل أي بحث)، واستدعاء pay/session على UUID عشوائي (فحص الـ flag يعمل قبل البحث عن
الطلب، والـ body الفارغ صالح) (PS: packages/core/src/points/create-point-order.ts:69-75،
PS: packages/core/src/points/pay-point-order.ts:44-54،
PS: services/api/src/handlers/pay-point-order-session.ts:22-24). وادعاءات الوثائق عن حالة
الـ flags موجودة في PS: .env.example:74-80,91-94 وPS: CLAUDE.md:192-196 و
PS: docs/plans/2026-09-17-shukah-sync-idempotent-key.md:35-38 و
PS: docs/plans/2026-09-21-point-order-session-handoff.md:17 و
PS: docs/plans/2026-09-22-o16-objectid-identity-design.md:286 و
PS: docs/integrations/2026-09-25-payment-api-hook-forwarding.md:199,748-753.
Description
يركّب الـ route نصًا ولا يكتب شيئًا. فيبحث عن العرض (status: 'active'،
deleted_at: null)، ثم عن المناسبة فقط عند غياب النص الحر، ثم عن ملف المستدعي
(PS: services/api/src/services/point-settings.service.ts:44-79). والصياغة هي
composeGiftDescription، التي تحتفظ بمسافة Shukah المزدوجة عن قصد
(PS: services/api/src/services/point-settings.mappers.ts:60-78). أما نصوص السجل والطلب التي
تُبنى عند إنشاء طلب فتختزل تلك الفجوة إلى مسافة واحدة ولا تحمل المسافة المزدوجة:
فهذه عائلة مختلفة من الجمل
(PS: packages/core/src/points/order-reason.ts:10-28). ويتبع الاسم وقناع الهاتف
displayName وmaskPhone (PS: packages/core/src/points/order-reason.ts:10-18). وتسجّل
وثيقة خدمة النقاط أن الـ bodyين، بمناسبة وبدونها، تطابقا مع Shukah في 2026-09-21
(PS: docs/integrations/2026-07-16-client-facing-point-apis.md:393-404). ويقول دليل الاختبار اليدوي
بتاريخ 2026-09-21 إن route الـ proxy الخاص بها لم يُدمج بعد؛ وهو مدمج عند d9e4e5f6
(PS: docs/plans/2026-09-21-point-order-manual-test-guide.md:444،
OV2: src/modules/client/point/point.controller.ts:132-146).
استدعاءات التطبيق التي لا route لها في Locate
- قائمة الهدايا المرسَلة. يقرأ تبويب "Sent gifts" في التطبيق
GET /maeda/point-order(معpageوper-page، وheaders الـx-pagination-*). ولا خدمة النقاط ولا order-v2 لديها route لسرد طلبات النقاط، ويعلّمه سجل الترحيل (migration registry) بأنه لم يُستبدل. وتفتح شاشة دفع الهدية ذلك التبويب بعد الدفع، لذا لن يظهر فيه طلب أُنشئ في Locate (mobile/locate-client@e960c3a: src/entities/points/model/api/services/points.service.ts:170-188،mobile/locate-client@e960c3a: src/features/SendedPointGifts/model/hooks/useSendedPointGifts.ts:20،mobile/locate-client@e960c3a: src/entities/points/model/api/controllers/points.controller.ts:151-152). - استدعاءات الدفع الأخرى.
order/checkout-payment(الـ Frames token) وorder/checkout-payment-stcpay، ومساري PayFort وHyperpay، واستدعاءcvv/tokenإلى خدمة الدفع بمعرّف طلب Shukah لا نظير لها في Locate. وتوفّر Locate route لبطاقة محفوظة وroute لجلسة؛ أما STC Pay فمحفظة داخل الجلسة. واستدعاءcvv/tokenلا أثر له على طلب Locate. وأي استدعاءات Locate ينبغي للتطبيق استخدامها بدلًا منها: غير موثّق بعد (mobile/locate-client@e960c3a: src/widgets/PointGiftsCheckout/model/hooks/usePointGiftsCheckout.ts:80-120,288-352،PS: docs/plans/2026-09-10-point-order-write-path-design.md:382-398). - طلب واحد. لدى التطبيق
getPointGiftOrderByIdلـGET /maeda/point-order/{id}، لكن لا شاشة تستدعيها (mobile/locate-client@e960c3a: src/entities/points/model/api/services/points.service.ts:316-341،mobile/locate-client@e960c3a: src/entities/points/index.ts:21-22).
التطبيق
تعيد createPointGiftOrder تحميل الإعدادات العامة وترسل payment_type بالقيمة point لدفع
بالنقاط، أو قيمة payment من تلك الإعدادات لدفع إلكتروني
(mobile/locate-client@e960c3a: src/entities/points/model/api/rtk/pointsApi.ts:408-421،
mobile/locate-client@e960c3a: src/entities/points/model/api/dto/points.dto.ts:356-397). ويحوّل
الـ DTO الخاص بالطلب الاستجابة إلى طلب التطبيق: فيبني pointOrderId بـ String(point_order_id)، ويقرأ
amount وclient_id وto_client_id وpayment_point_order_id، ويحوّل status عبر جدول
كلمات Shukah الأربع
(mobile/locate-client@e960c3a: src/entities/points/model/api/dto/points.dto.ts:23-31,138-183). وكل
استدعاءات الدفع ترسل order.pointOrderId
(mobile/locate-client@e960c3a: src/widgets/PointGiftsCheckout/model/hooks/usePointGiftsCheckout.ts:51,80-120).
ويبحث الدفع (checkout) عن المستلم بالهاتف ويمرّر userId بوصفه to_client_id، ثم ينشئ
الطلب ويدفع؛ ويعيد معالج النجاح تحميل السجل ويستبدل الشاشة بتبويب الهدايا المرسَلة (Sent gifts)
(mobile/locate-client@e960c3a: src/widgets/PointGiftsCheckout/model/hooks/usePointGiftsCheckout.ts:221-232,370-401).
حيث تختلف الوثائق عن الشيفرة (القول للشيفرة)
- تقول وثائق 2026-09-21 و09-22 و09-25 إن الـ flagين غير مضبوطين في كل بيئة. ويُظهر فحص
2026-10-01 كليهما مفعّلًا على dev. وتقول وثيقة 2026-09-25 إن أحدًا لم يقرأ الـ flag على host يعمل
(
PS: docs/integrations/2026-09-25-payment-api-hook-forwarding.md:748-753). - يقول دليل الاختبار اليدوي بتاريخ 2026-09-21 إن route الـ proxy الخاص بـ description لم يُدمج وإن
order-v2 على dev يستدعي
localhost:4010. وكلاهما قديم (PS: docs/plans/2026-09-21-point-order-manual-test-guide.md:24-32,444). - تقول وثيقة مرجع order-v2 إن تعذّر الوصول إلى خدمة المصادقة يعطي
500. أما خدمة النقاط فتجيب401(OV2: docs/locate-point-client-api-reference.md:201،PS: services/api/src/services/client-auth.service.ts:26-28). - تصف وثيقة التكامل create بأنه "new capability, not a Shukah read-parity endpoint"، وتقول أيضًا إن
POST المعاد يطابق "Shukah's own order-creation endpoint"
(
PS: docs/integrations/2026-07-16-client-facing-point-apis.md:178-182,237-238). - 2026-10-02: قالت هذه الصفحة إن token من Shukah وJWT من Locate عملا كلاهما على هذه الـ routes. وهي تشترك
في فحص الـ token الخاص بخدمة النقاط، الذي أعطى JWT من Locate
401على routes النقاط الأخرى في 2026-10-02 (PS: services/api/src/services/client-auth.service.ts:22-29). وتستخدم استدعاءات «جرّبه» الآن token من Shukah. - 2026-10-02: قالت هذه الصفحة إن طلب Shukah يحمل
occasion_id، كما يعرّفه التطبيق. وصفوف طلبات Shukah فيهاpoint_occasion_id(قائمة الهدايا المرسَلة، التُقطت على dev، 2026-10-02)، لذا فـoccasionIdفي التطبيق دائمًاundefined(mobile/locate-client@e960c3a: src/entities/points/model/api/dto/points.dto.ts:138-183). - 2026-10-02: قالت هذه الصفحة إن استدعاءات الدفع ترسل
point_order_idكعدد صحيح. يرسل التطبيق 2.31.5 سلسلة ("1252"و"1253"؛ التقاطات الـ proxy على dev، 2026-10-02). وتتجاهله Locate في الحالين (PS: services/api/src/handlers/pay-point-order-card.ts:7-15،PS: services/api/src/handlers/pay-point-order-session.ts:9-16). - 2026-10-02: بنت هذه الصفحة جانب Shukah من create من أنواع التطبيق ووثائق خدمة النقاط، لأن create لم
يكن قد استُدعي. ثم استُدعي الجانبان على dev في 2026-10-02: تجيب Shukah بـ
201مع اثني عشر مفتاحًا، وLocate بـ200مع تسعة، وطلبpointعلى أي جانب لم يخفض إلا رصيد ذلك الجانب (الالتقاطاتpoint-order-create-card--shukahوpoint-order-create-card--locate-stوpoint-order-create-point--shukahوpoint-order-create-point--locate-st).
Sources
locate-order-v2-api@d9e4e5f6:src/modules/client/point/point.controller.ts:6-10,110-201،src/modules/client/point/point.service.ts:64,src/main.ts:38- وثائق
locate-order-v2-api@d9e4e5f6:docs/locate-point-client-api-reference.md:46,48,201،docs/plans/2026-09-16-point-order-proxy-plan.md:21 locate-point-service@2c44ce3:services/api/src/routes/client.routes.ts:23-29،services/api/src/routes/client.routes.test.ts:43-75،services/api/src/handlers/create-point-order.ts:10-47،services/api/src/handlers/get-point-order.ts:6-12،services/api/src/handlers/point-order-description.ts:8-38،services/api/src/handlers/pay-point-order-card.ts:7-46،services/api/src/handlers/pay-point-order-session.ts:7-43،services/api/src/handlers/checkout-webhook.ts:9-41،services/api/src/services/point-orders.service.ts:3-15،services/api/src/services/point-settings.service.ts:44-79،services/api/src/services/point-settings.mappers.ts:60-78,services/api/src/lib/object-id.ts:12-14،services/api/src/services/client-auth.service.ts:26-28،services/api/src/middleware/request-logger.ts:5،packages/core/src/points/create-point-order.ts:66-268،packages/core/src/points/pay-point-order.ts:29-174،packages/core/src/points/payment-api-client.ts:7-30,70-74,86-92,113-164،packages/core/src/points/settle-point-order.ts:33-55,117-145,169-392،packages/core/src/points/apply-point-txn.ts:71-86،packages/core/src/points/derive-order-type.ts:16-34,packages/core/src/points/order-no.ts:12,20-28،packages/core/src/points/correlation-id.ts:8-10,packages/core/src/points/client-lookup.ts:20-35،packages/core/src/points/order-reason.ts:10-28،packages/core/src/db/models/point-order.model.ts:25,30-31,.env.example:74-97,CLAUDE.md:192-205،Jenkinsfile-k8s:25-28,115-122,Dockerfile:22-23- وثائق
locate-point-service@2c44ce3:docs/integrations/2026-07-16-client-facing-point-apis.md:178-182,237-238,263-299,393-404(آخر تغيير 2026-09-22)،docs/plans/2026-09-21-point-order-manual-test-guide.md:24-32,336-343,444,446(آخر تغيير 2026-09-22)،docs/plans/2026-09-10-point-order-write-path-design.md:382-398,513-518,684,692,811،docs/plans/2026-08-03-point-order-flow-design.md:31,33،docs/plans/2026-07-29-point-orders-verbatim-shukah-values.md:19-34،docs/plans/2026-09-17-shukah-sync-idempotent-key.md:35-38،docs/plans/2026-09-21-point-order-session-handoff.md:17،docs/plans/2026-09-22-o16-objectid-identity-design.md:286،docs/integrations/2026-09-25-payment-api-hook-forwarding.md:199,748-753 - نسخة الجوال اللحظية
mobile/locate-client@e960c3a(الفرعdevelopment، 2026-02-26؛ شيفرة التطبيق نفسه، وليست من Locate):src/entities/points/model/api/services/points.service.ts:170-188,265-283,316-361،src/entities/points/model/api/dto/points.dto.ts:23-31,138-183,312-397،src/entities/points/model/api/rtk/pointsApi.ts:408-421،src/entities/points/model/api/controllers/points.controller.ts:151-152،src/entities/points/index.ts:21-22،src/widgets/PointGiftsCheckout/model/hooks/usePointGiftsCheckout.ts:51,80-120,221-232,288-352,370-401،src/widgets/SendPointGiftForm/ui/SendPointGiftForm/SendPointGiftForm.tsx:149-185،src/features/SendedPointGifts/model/hooks/useSendedPointGifts.ts:20،src/shared/api/instances/orderApiV2.ts:16 - التقاطات dev، 2026-10-02 (parity audit):
point-order-description--shukahوpoint-order-description--locate-stوpoint-order-description-shukahids--locate-stوpoint-order-list--shukah؛ وأجسام طلبات التطبيق 2.31.5 لـcheckout-payment-flowوcheckout-payment-stcpay(التقطها الـ proxy الخاص بالمستخدم). ولم تُستدعَ routes الدفع في 2026-10-02. - التقاطات dev، 2026-10-02 (استدعاءات المتابعة، بموافقة المستخدم):
point-order-create-card--shukahوpoint-order-create-card--locate-stوpoint-order-create-point--shukahوpoint-order-create-point--locate-st، والرصيدان قبل وبعد. - التقاطات dev، 2026-10-01:
locate-point-order-create-checkoutوlocate-point-order-getوlocate-point-order-descriptionوshukah-point-order-descriptionوlocate-flag-probe-pay-by-pointوlocate-flag-probe-pay-sessionوprobe-locate-point-order-createوprobe-locate-point-order-getوprobe-locate-point-order-descriptionوprobe-locate-point-order-pay-cardوprobe-locate-point-order-pay-sessionوlocate-point-offersوlocate-point-occasions