البحث عن عميل
الخلفية
على Shukah كان التطبيق يبحث عن عميل برقم الهاتف عبر GET /maeda/client?contact_phone=…. وفي
خدمة المصادقة بحث مماثل عند GET /api/v1/clients. يُرجع الحقول الأربعة نفسها، لكنه
يحتاج إلى JWT من Locate ويُخفي جزءًا من اسم العائلة (masking).
الميزة
أرسل رقم هاتف واحصل على من يملكه: معرّف العميل وهاتفه واسمه الأول واسم عائلته المقنَّع. والإجابة مصفوفة بصف واحد، أو مصفوفة فارغة عندما لا يكون لأي عميل نشط هذا الهاتف.
في نسخة التطبيق اللحظية بتاريخ 2026-02-26، يستخدمه التطبيق فقط لإيجاد مستلم هدية نقاط، من نموذج الهدية ومرة أخرى عند إتمام شراء الهدية (checkout). ويقرأ الصف الأول فقط، وتتحول المصفوفة الفارغة إلى خطأ "User not found" في التطبيق.
يحتاج الاستدعاء إلى جلسة مسجَّلة الدخول، لكن الهاتف الذي تبحث عنه لا يلزم أن يكون هاتفك. فالـ token يحدد المستدعي فقط. أما الهاتف في query فيحدد من يعود.
GET /api/v1/clients
GET /maeda/client✅ تم الاستبدال- 🔴 يحصل token من Shukah على 401 "Unauthorization"؛ أرسل JWT من Locate (من exchange-token؛ واحتفظ بـ token من Shukah).
- 🟡 تقنّع Locate الحقل last_name ("User" تصبح "U**r")؛ وتقنيع Shukah غير معروف (اللقطة منقّحة).
- 🟡 الهاتف غير المعروف يعطي 200 [] على Locate (بحسب الكود)؛ ويحوّل التطبيق [] إلى "User not found" (user.service.ts:49-51).
- 🟡 body الـ 401 في Locate هو {status,message} وليس body Laravel؛ ويقرأ التطبيق message فقط.
الطلب (Request)
| الحقل | الموضع | النوع | مطلوب | القيود | الوصف |
|---|---|---|---|---|---|
Authorization | header | string | نعم | Bearer <locate-jwt>، أي token المستدعي. ويُرفض token Shukah بـ 401. | جلسة المستدعي. |
contact_phone | query | string | نعم | تُحذف المسافات. ويُطابَق مع الهاتف المخزَّن بوجود + في البداية أو بدونه. الفارغ أو المفقود يعطي 400. وعلامة + الحرفية في URL تُقرأ مسافة وتُحذف، لذا فإن +966… غير المرمَّزة (unencoded) تظل تطابق. | الهاتف المراد البحث عنه. ويمكن أن يعود لأي شخص، لا للمستدعي فقط. |
key وlanguage وأي مفتاح query آخر | query | string | لا | يُقبل ويُتجاهل. | يُبقى حتى تظل سلسلة query القديمة لـ Shukah تعمل. |
الاستجابة (Response)
200 مع مصفوفة من صفر أو صف واحد:
| الحقل | النوع | يقبل null | الصيغة / القيود | الوصف |
|---|---|---|---|---|
client_id | integer | لا | — | معرّف العميل الذي عُثر عليه. |
contact_phone | string | نعم | الهاتف كما خُزِّن، غير مقنَّع. | هاتف العميل. |
first_name | string | نعم | تُحذف المسافات الزائدة. والفارغ يصبح null. غير مقنَّع. | الاسم الأول. |
last_name | string | نعم | مقنَّع: يُحتفظ بالحرف الأول والأخير ويتحول كل حرف بينهما إلى * (User تصبح U**r). والاسم المكوَّن من حرف أو حرفين يُرجَع كما هو. والفارغ يصبح null. | اسم العائلة، مقنَّع. |
عندما لا يكون لأي عميل نشط وغير محذوف هذا الهاتف، تكون الإجابة 200 مع []. والعميل الموقوف
أو المحظور أو المعلّق أو المنتهي يحصل على [] أيضًا.
[
{
"client_id": 356259,
"contact_phone": "+966500000000",
"first_name": "Test",
"last_name": "User"
}
]
التُقط على dev، 2026-10-01 (العميل 356259)؛ واستُبدلت الحقول الشخصية. وقد استبدل الالتقاط last_name بـ User، فلا يظهر هنا الشكل المقنَّع للقيمة الحقيقية. وقاعدة التقنيع (mask) مصدرها الشيفرة واختبار الوحدة الخاص بها: فـ User ستصبح U**r.
الأخطاء
| HTTP | Body | متى |
|---|---|---|
400 | {"name":"Bad Request","message":"Phone number cannot be blank.","code":0,"status":400,"type":"yii\\web\\BadRequestHttpException"} | contact_phone مفقود أو فارغ. |
401 | {"status":401,"message":"Unauthorization"} | الـ token مفقود أو مشوَّه أو ليس JWT من Locate، أو عميل المستدعي غير موجود، أو عميل المستدعي غير نشط. |
التُقط body الخاص بـ 401 على dev في 2026-10-01 دون token. أما body الخاص بـ 400 فله الشكل نفسه
الذي التُقط لـ reset-otp (انظر تسجيل الدخول برمز OTP). ولم يُلتقط على هذا
الـ route، لأن جولة الالتقاط لم تُجرِ أي استدعاء بحث بـ token وهاتف فارغ.
جرّبه
export AUTH_BASE_URL=https://dev.authentication.locate.sa
export TOKEN='<locate-jwt>' # see "Getting a test token" on the module overview
export PHONE='<phone-to-look-up>' # the answer shows that client's first name and phone
curl -s -G "$AUTH_BASE_URL/api/v1/clients" --data-urlencode "contact_phone=$PHONE" -H "Authorization: Bearer $TOKEN"
المتوقع: 200 ومصفوفة بصف واحد، كما هو مبيّن تحت «الاستجابة». والهاتف الذي لا يملكه أحد يعطي [].
الفروق عن Shukah
لـ body الخاص بـ 200 المفاتيح الأربعة نفسها على الجانبين: ففي 2026-10-01 أرجعت الخدمتان client_id
وcontact_phone وfirst_name وlast_name للعميل 356259.
| الجانب | Shukah | Locate | الأثر على التطبيق |
|---|---|---|---|
| الـ Token | token من Shukah (استخدمه الالتقاط). | JWT من Locate فقط. وtoken من Shukah يحصل على 401 عند الحارس نفسه الخاص بـ verify-token. | استبدل token Shukah بالـ JWT قبل التحوّل، واحتفظ بـ token Shukah لـ order-v2. |
| URL | GET $SHUKAH_BASE_URL/maeda/client?contact_phone=…&key=maeda&language=en-GB (الاستدعاء الملتقط). | GET $AUTH_BASE_URL/api/v1/clients?contact_phone=…. تتجاهل Locate key وlanguage. | غيّر base URL والمسار. ويمكن للتطبيق مواصلة إرسال key وlanguage. |
last_name | غير موثّق بعد: فقد استُبدل last_name في التقاط dev، لذا لا يمكن رؤية التقنيع. | مقنَّع: يُحتفظ بالحرف الأول والأخير ويتحول ما بينهما إلى *. | ينسخ التطبيق last_name من الصف الأول، وLocate تعطيه مقنَّعًا. افحص كيف يستخدم تدفق الهدية الاسم. |
| هاتف غير معروف | غير موثّق بعد. | 200 مع []، وكذلك لعميل غير نشط. | يحوّل التطبيق المصفوفة الفارغة بالفعل إلى "User not found"، لذا لا يحتاج [] إلى تغيير. |
body الخاص بـ 401 | {"name":"Unauthorized","message":"Your request was made with invalid credentials.","code":0,"status":401,"type":"Illuminate\\Auth\\AuthenticationException"} (بلا token، التُقط على dev، 2026-10-01). | {"status":401,"message":"Unauthorization"} (بلا token، التُقط على dev، 2026-10-01). | يجب تغيير أي شيء يطابق نص الخطأ أو name أو type. وقيمة status هي 401 في الاثنين. |
تنبيه
- يعود
last_nameمقنَّعًا (UserتصبحU**r)، لكنfirst_nameوcontact_phoneيعودان كاملين. - الهاتف غير المعروف والعميل غير النشط كلاهما يحصل على
200مع[]، ويحوّلها التطبيق إلى خطأ "User not found"، فلا يستطيع التمييز بينهما. - الـ token والهاتف مستقلان: فأي مستدعٍ مسجَّل الدخول يستطيع البحث عن أي هاتف والحصول على الاسم الأول لذلك العميل وهاتفه.
التفاصيل (للـ backend)
فُحص dev فقط: الـ host https://dev.authentication.locate.sa والبادئة /api/v1. أما الـ host والبادئة في UAT
وproduction: غير موثّق بعد.
الـ Route والبحث
- يُسجَّل
GET /clientsقبل أي route بمعاملات (parameter route)، خلف الـ middleware المسمّىclient، لذا يحتاج إلى JWT من Locate لعميل نشط كما يفعل verify-token (src/routes/client.js:14-16،src/middlewares/client.js:8-42). - يقرأ الـ handler قيمة
contact_phoneويجيب بـ400عبر خطأ Yii عندما تكون فارغة (src/controllers/client/lookup/index.js:33-34). - البحث هو
findClientByPhone، نفسه في reset-otp: الهاتف المخزَّن مع+اختيارية، غير محذوف، بحالةACTIVE، بأحدثclient_idأولًا، دون cache (src/controllers/client/reset-otp/handler.js:24-30،src/shared/helpers/filter.js:1-4). ويُرجع أفضل تطابق واحد، لذا فللمصفوفة صف واحد على الأكثر (src/controllers/client/lookup/index.js:36-40). - علامة
+الحرفية في URL تُفكّ ترميزها إلى مسافة، ثم تحذفهاnormalizePhone، لذا فإن+966…غير المرمَّزة تظل تطابق (src/sync/lib/casts.js:40-41؛ قراءة شيفرة). - تذكر وثيقة التصميم تدفقات التاجر (merchant) التي تحمل JWT عميل من Locate بوصفها حالة الاستخدام
(
docs/plans/2026-08-04-client-lookup-by-phone-api-local.md:9-11). ولا تستدعيه نسخة التطبيق اللحظية إلا لتحديد مستلم هدية (انظر Sources). - الاستدعاء للقراءة فقط، دون cache خاص به ودون ترقيم صفحات (pagination) ودون آثار جانبية
(
docs/plans/2026-08-04-client-lookup-by-phone-api-local.md:25-28).
تحويل الصف والتقنيع
تبني toLookupRow كل صف (src/controllers/client/lookup/index.js:23-28):
client_idهوclient_idالمخزَّن. وcontact_phoneهو الهاتف المخزَّن، أوnull.first_nameتُحذف مسافاته الزائدة، والفارغ يصبحnull(src/controllers/client/lookup/index.js:17-21).- يمر
last_nameعبرmaskLastName: تُحذف المسافات الزائدة، وnullأو الفارغ يعطيnull، والطول اثنان أو أقل يبقى دون تغيير، وإلا فالحرف الأول ثمlength - 2نجمة ثم الحرف الأخير (src/controllers/client/lookup/index.js:8-15). ويثبّت اختبار الوحدةUserعلىU**r(src/controllers/client/__tests__/lookup.test.js:32). - تقنيع
first_nameأوcontact_phoneخارج النطاق في وثيقة التصميم (docs/plans/2026-08-04-client-lookup-by-phone-api-local.md:93). ولذلك يحصل أي مستدعٍ مسجَّل الدخول عليهما كاملين لأي هاتف، ولا يستطيع الاستدعاء التمييز بين هاتف غير معروف وآخر غير نشط.
غير موثّق بعد
- هل تقنّع Shukah
last_name: فقد استُبدلت القيمة في الالتقاط. - ماذا تجيب Shukah عن هاتف غير معروف أو هاتف فارغ: لم يُلتقط أي منهما.
- هل يقبل بحث Shukah أيضًا مرشّحات القوائم. تدرج وثيقة التصميم "Pagination /
status/pagelist filters from upstream" خارج النطاق بالنسبة إلى Locate (docs/plans/2026-08-04-client-lookup-by-phone-api-local.md:91)، ولم يستخدم الالتقاط إلاcontact_phoneوkeyوlanguage. - هل في أي بيئة هذا الـ route: لا يسجّله
origin/productionعند4f3bc96(2026-09-21) (ملفsrc/routes/client.jsفيه لا يستوردlookup)، وهل يعمل أي شيء من ذلك الـ ref غير معروف.
حيث تختلف الوثائق عن الشيفرة (القول للشيفرة)
- مثال التقنيع في وثيقة التصميم فيه 13 نجمة (
U*************)لـUser (500…000)) (docs/plans/2026-08-04-client-lookup-by-phone-api-local.md:30). أما الشيفرة واختبار الوحدة فتعطيان 12 (src/controllers/client/__tests__/lookup.test.js:33)، لأن القاعدة هيlength - 2. والفرق شكلي.
Sources
locate-authentication-service@6757aa4:src/routes/client.js:14-16،src/middlewares/client.js:8-42,src/controllers/client/lookup/index.js:8-41،src/controllers/client/__tests__/lookup.test.js:32-33،src/controllers/client/reset-otp/handler.js:24-30,src/shared/helpers/filter.js:1-4،src/sync/lib/casts.js:40-41,src/shared/errors/yii-bad-request-error.js:12-23،src/middlewares/error-handler.js:10,src/shared/constant/error-message.js:59- وثائق
locate-authentication-service@6757aa4:docs/plans/2026-08-04-client-lookup-by-phone-api-local.md:9-11,25-30,56-59,90-93(2026-08-04) - نسخة الجوال اللحظية
mobile/locate-client@e960c3a(الفرعdevelopment، 2026-02-26؛ شيفرة التطبيق نفسه، وليست من Locate):src/entities/user/model/api/services/user.service.ts:38-47,49-51,58-60،src/entities/user/model/api/dto/user.dto.ts:126-155،src/widgets/SendPointGiftForm/model/hooks/sendPointGiftForm.ts:64،src/widgets/PointGiftsCheckout/model/hooks/usePointGiftsCheckout.ts:376 - التقاطات dev، 2026-10-01:
locate-client-lookupوshukah-client-lookupوprobe-locate-client-lookupوprobe-shukah-client-lookupوprobe-locate-reset-otp - دليل ثانوي، ليس الـ sha المثبَّت:
locate-authentication-serviceorigin/productionعند4f3bc96(2026-09-21)،src/routes/client.js