Points wallet
Bối cảnh
Trên Shukah, app đọc số dư điểm bằng GET /maeda/client/points, bảng tỷ lệ tích điểm bằng
GET /maeda/point-rate, và đổi một gift-card code bằng GET /maeda/giftcard-code/use. Locate
có một route cho mỗi cái, và đợt kiểm tra tương đương xếp cả ba là drop-in: không cái nào cần
app đổi gì ngoài base URL và path. TL;DR của từng cái vẫn có các dòng cần lưu ý, vì method, token
hoặc dữ liệu vẫn có thể khác:
- Balance (drop-in).
GET /api/v2/client/pointstrên order-v2 chuyển lời gọi sang point service. Nó trả về cùng bốn key như Shukah. Cần lưu ý:pointscó thể có bốn chữ số thập phân, trên dev số dư và ngày hết hạn không giống của Shukah (2026-10-01 và 2026-10-02), và trên dev hiện nay nó chỉ chấp nhận token Shukah, không chấp nhận JWT Locate. - Tỷ lệ tích điểm và gift-card code (drop-in). order-v2 tự phục vụ hai cái này. Nó không chuyển
chúng sang point service. Cần lưu ý: bảng tỷ lệ tích điểm trên dev không phải của Shukah, và
gift-card code giờ là một
POSTvới code nằm trong body, một lời gọi app đã có sẵn. App chỉ gọi cả hai ở chế độ Merchant.
Tính năng
Wallet là màn hình nơi client xem số dư điểm, các giao dịch điểm gần nhất, một ô để nhập gift-card code, và FAQ về điểm. Trong snapshot app ngày 2026-02-26 các lời gọi xảy ra ở đây:
| Lời gọi | Ở đâu trong app |
|---|---|
Balance (#points) | Khi app tải thông tin của client (lúc khởi động và sau khi đăng nhập), và khi checkout nhà hàng mở phần chọn phương thức thanh toán (để xem client có thể trả bằng điểm không). Màn hình Wallet hiển thị số điểm, giá trị của chúng theo SAR và ngày hết hạn. |
Tỷ lệ tích điểm (#point-rates) | Trong thẻ "Earn Points" của giỏ hàng nhà hàng và giỏ hàng merchant, hiển thị tổng giỏ hàng tích được bao nhiêu. |
Gift-card code (#gift-card-code) | Khi client nhập một code vào ô trên màn hình Wallet. Khi thành công app hiển thị một thông báo với code và số điểm, rồi tải lại số dư và lịch sử. Khi có lỗi nó hiển thị thông báo với text lỗi. |
Balance và gift-card code cần token của chính client. Bảng tỷ lệ tích điểm là public. Với
balance, hiện nay trên dev chỉ có token Shukah chạy: một JWT Locate nhận 401 vào 2026-10-02, dù
cả hai token đều chạy vào 2026-10-01 (xem "Token nào chạy được" ở mục Chi tiết). Gift-card code dùng
cùng cách kiểm tra token (qua /api/v1/users), nên nó cũng cần token Shukah (đọc code; chưa gọi với
token, vì nó cộng điểm).
Chế độ Merchant. App chỉ gọi các route Locate cho tỷ lệ tích điểm và gift-card code khi
client settings ghi enableMerchantService: true. Nếu không app vẫn gọi Shukah cho cả hai.
App đọc setting đó từ Locate client-settings service khi nó khởi động. Lời gọi balance
không có biến thể Locate trong app hiện nay: nó luôn gọi Shukah, ở cả hai chế độ, nên app phải được
sửa để gọi GET /api/v2/client/points.
Hai số dư. Số dư mà route này trả về đến từ point service. Khi client trả
một order bằng điểm, order-v2 vẫn kiểm tra số dư trên Shukah (GET /maeda/client/points với
token của client), không phải cái này. Trong giai đoạn chạy song song hai số có thể khác nhau, nên màn hình Wallet
và checkout có thể hiển thị các con số khác nhau. Có chủ đích hay không: Chưa được tài liệu hóa.
Việc tiêu điểm không được đồng bộ. Hai ledger đã không theo nhau khi điểm được tiêu (trong vài giây của lần chạy; việc nó có tới sau đó hay không thì chưa được kiểm tra). Trên dev ngày 2026-10-02, một order quà tặng trả bằng điểm trên Shukah làm số dư Shukah giảm 110 và để nguyên số dư Locate; cùng order đó trên Locate, vài giây sau, chỉ làm giảm số dư Locate (xem Point orders). Nếu app tạo point order ở một bên, nó phải đọc số dư và lịch sử từ chính bên đó, nếu không màn hình Wallet hiển thị sai số.
GET /api/v2/client/points
GET /maeda/client/points✅ Đã thay thế- 🟡 JWT của Locate nhận 401 "Invalid or expired token" trên dev; hãy gửi token của Shukah.
- 🟡 Số dư "514405.6788" so với "514853.64" của Shukah (Locate thấp hơn 447.96); hết hạn 2026-11-28 so với 2026-11-30.
- 🟡 points có tới 4 chữ số thập phân; app dùng Number(points) || 0 nên parse được; màn hình phải làm tròn khi hiển thị.
- 🟡 Body 401 là {success,error:{code,message}} không có message ở cấp cao nhất; app sẽ hiển thị chuỗi JSON thô.
Request
| Field | Vị trí | Kiểu | Bắt buộc | Ràng buộc | Mô tả |
|---|---|---|---|---|---|
Authorization | header | string | có | Bearer <token>. Một token Shukah chạy được trên dev. Một JWT Locate nhận 401 Invalid or expired token (capture trên dev, 2026-10-02; nó chạy vào 2026-10-01): xem "Token nào chạy được" ở mục Chi tiết. | Token của chính client. Client id lấy từ nó, nên app không bao giờ gửi một id. |
| Bất kỳ query key nào | query | — | không | Không được đọc. order-v2 không forward query trên route này, nên language mà app thêm vào bị bỏ. | — |
Response
200 với một object trần (không có wrapper status hay data):
| Field | Kiểu | Nullable | Định dạng / ràng buộc | Mô tả |
|---|---|---|---|---|
id | string | không | Client id dạng text, ví dụ "356259". | Client id. |
user_id | string | không | Cùng giá trị với id. | Client id. |
points | string | không | Một số thập phân dạng text. Các số 0 ở cuối và dấu chấm ở cuối bị bỏ, và tối đa bốn chữ số thập phân được giữ: "514405.6788". "0" khi client không có tài khoản điểm. Có thể âm: inbound sync từ Shukah không kiểm tra overdraft. | Số dư hiện tại. |
pointsExpirationDate | string | có | YYYY-MM-DD, một ngày theo Asia/Riyadh. null trừ khi quy tắc hết hạn bên dưới áp dụng. | Ngày số dư hết hạn do không hoạt động. |
Các key đến theo thứ tự: id, user_id, points, pointsExpirationDate.
{
"id": "356259",
"user_id": "356259",
"points": "514405.6788",
"pointsExpirationDate": "2026-11-28"
}
Capture trên dev, 2026-10-01 (client 356259), với một token Shukah. Cùng lời gọi với một JWT Locate
trả về cùng body vào ngày đó. Vào 2026-10-02 token Shukah nhận cùng các giá trị, còn JWT Locate
nhận 401.
Định dạng points. Locate giữ điểm với bốn chữ số thập phân và không làm tròn chúng. Formatter
chỉ bỏ các số 0 ở cuối, nên "511712.9600" thành "511712.96" và "0.0000" thành "0".
Trên dev cùng client và cùng phút cho "514405.6788" trên Locate và "128712.41" trên Shukah vào
2026-10-01, và "514405.6788" và "514853.64" vào 2026-10-02. App đọc giá trị bằng
Number(points) || 0, nên cả hai định dạng đều parse được.
Quy tắc pointsExpirationDate. Ngày là null khi point settings nói hết hạn là never,
khi settings chưa được đặt, hoặc khi client không có dòng earn được tính nào. Nếu không thì nó là ngày
của dòng earn được tính mới nhất của client cộng với số ngày không hoạt động đã cấu hình, theo
Asia/Riyadh. Locate chỉ thực thi ngày đó khi expiry job của nó được bật (xem Chi tiết). Dev
trả về 2026-11-28.
Lỗi
| HTTP | Body | Khi nào |
|---|---|---|
401 | {"success":false,"error":{"code":"UNAUTHORIZED","message":"Missing or invalid authorization header"}} | Không có header Authorization, hoặc nó không bắt đầu bằng Bearer . Đã capture trên dev, 2026-10-01, khi không có token. |
401 | {"success":false,"error":{"code":"UNAUTHORIZED","message":"Invalid or expired token"}}, hoặc cùng shape với message là auth service unreachable: <error text> hoặc auth response missing numeric client id. | Auth service từ chối token, không kết nối được (sự cố auth trông giống token xấu), hoặc trả lời mà không có client id. Body đầu tiên đã được capture trên dev, 2026-10-02, với một JWT Locate. Hai body còn lại chưa được capture. |
500 | {"success":false,"error":{"code":"INTERNAL_ERROR","message":"Internal server error"}} | Bất kỳ lỗi không mong đợi nào trong point service, ví dụ lỗi database. Message là AUTH_SERVICE_URL not set khi service bị cấu hình sai. Chưa capture. |
502 | {"success":false,"error":{"code":"POINT_SERVICE_UNAVAILABLE","message":"Point service unavailable: <error text>"}} | order-v2 không kết nối được point service. Body này đến từ order-v2, không phải từ point service. Chưa capture. |
504 | {"success":false,"error":{"code":"POINT_SERVICE_TIMEOUT","message":"Point service did not respond in time; the request outcome is unknown"}} | Point service không trả lời trong 10 giây. Body này đến từ order-v2. Chưa capture. |
Mọi lỗi từ point service đều có envelope {"success":false,"error":{"code":"…","message":"…"}}
(một key details được thêm cho lỗi validation). Body thành công không có envelope.
Thử ngay
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" in Details
curl -s "$ORDER_BASE_URL/api/v2/client/points" -H "Authorization: Bearer $SHUKAH_TOKEN"
Kỳ vọng: 200 và object như ở mục Response. Hãy dùng token Shukah trên dev hiện nay: một JWT Locate
nhận 401 Invalid or expired token như ở mục Lỗi (2026-10-02). Không có token bạn nhận 401
Missing or invalid authorization header.
Khác biệt so với Shukah
| Khía cạnh | Shukah | Locate | Tác động lên app |
|---|---|---|---|
| URL | GET $SHUKAH_BASE_URL/maeda/client/points | GET $ORDER_BASE_URL/api/v2/client/points | Đổi base URL và path. |
| Token | Token opaque của Shukah. | Chỉ token Shukah trên dev hiện nay. Một JWT Locate nhận 401 (capture trên dev, 2026-10-02); vào 2026-10-01 cả hai đều nhận 200. | App phải tiếp tục gửi token Shukah ở đây, ngay cả khi nó đã giữ một JWT Locate cho auth service. Với các môi trường khác: Chưa được tài liệu hóa. |
Định dạng points | Hai chữ số thập phân: "514853.64" (capture trên dev, 2026-10-02). | Tối đa bốn chữ số thập phân: "514405.6788" (cùng client, cùng ngày). | App parse bằng Number(points) || 0, nên nó chạy được. Mọi màn hình giả định hai chữ số thập phân phải làm tròn. |
| Số dư của client 356259 | "514853.64" vào 2026-10-02 ("128712.41" vào 2026-10-01: dữ liệu Shukah dev đã thay đổi trong khoảng đó). | "514405.6788" ở cả hai ngày: thấp hơn Shukah 447.96 điểm, khoảng 0.09%, vào 2026-10-02. | Số dư trên màn hình Wallet sẽ giảm một chút khi chuyển. Dòng ledger mới nhất của Locate cũ hơn của Shukah khoảng hai ngày, khớp với khoảng chênh (suy luận). Hãy kiểm tra feed từ Shukah có cập nhật trước khi chuyển. |
pointsExpirationDate | "2026-11-30". | "2026-11-28". Mỗi ngày là 60 ngày sau dòng ledger mới nhất ở phía đó, và dòng mới nhất của Locate cũ hơn hai ngày (suy luận). | Ngày mà app hiển thị có thể lệch vài ngày. Locate có chạy expiry trên dev hay không: Chưa được tài liệu hóa. |
| Body lỗi | {"name":"Unauthorized","message":"Your request was made with invalid credentials.","code":0,"status":401,"type":"Illuminate\\Auth\\AuthenticationException"} (capture trên dev, 2026-10-01, không token). | Envelope của point-service ở mục Lỗi. | Mọi chỗ so khớp theo text lỗi, name hay type đều phải đổi. |
Content-Type | application/json | application/json; charset=utf-8 | Không có. |
GET /api/v2/client/system_configs/point_rates
GET /maeda/point-rate✅ Đã thay thế🚩 enableMerchantService- 🟡 Các bậc khác nhau: Locate 70-140:2, 140-251:4, 251-451:10, 451+:16; Shukah 0-139:2 ... 451+:30 (5 dòng).
- 🟡 amount_to của Locate bằng amount_from kế tiếp (140, 251, 451); Shukah kết thúc ở 139, 153, 250, 450.
- 🟡 Dòng đầu của Locate bắt đầu từ 70, của Shukah từ 0; dưới 70, dòng đầu do app thêm cho 0 điểm.
- 🟡 Công khai ở cả hai phía (200, không cần token); app chỉ gọi route Locate khi enableMerchantService là true.
Công tắc phía app: app chỉ gọi route Locate này ở chế độ Merchant (client settings enableMerchantService: true); nếu không, app vẫn gọi Shukah.
order-v2 tự phục vụ route này và không chuyển nó sang point service. Nên các quy tắc proxy và các lỗi 502 và 504 của các trang điểm khác không áp dụng cho nó. App chỉ gọi nó ở chế độ Merchant.
Request
| Field | Vị trí | Kiểu | Bắt buộc | Ràng buộc | Mô tả |
|---|---|---|---|---|---|
Authorization | header | string | không | Không cần: dev trả 200 khi không có token và khi có token. Token được gửi không được đọc. | — |
| Bất kỳ query key nào | query | — | không | Được chấp nhận và bỏ qua. | — |
Response
200 với một mảng trần, sắp theo amount_from từ thấp đến cao (một null tính là 0). Nó là []
khi tùy chọn rate chưa được đặt:
| Field | Kiểu | Nullable | Định dạng / ràng buộc | Mô tả |
|---|---|---|---|---|
amount_from | number | có | Tổng order mà dòng bắt đầu. | Đầu của khoảng, theo SAR. |
amount_to | number | có | null ở dòng cuối. Trên dev mỗi dòng kết thúc ở giá trị mà dòng kế tiếp bắt đầu (140, 251, 451). | Cuối của khoảng, theo SAR. |
point_earn | number | không | Một số nguyên trên dev. | Điểm tích được khi tổng order rơi vào khoảng. |
[
{"amount_from": 70, "amount_to": 140, "point_earn": 2},
{"amount_from": 140, "amount_to": 251, "point_earn": 4},
{"amount_from": 251, "amount_to": 451, "point_earn": 10},
{"amount_from": 451, "amount_to": null, "point_earn": 16}
]
Capture trên dev, 2026-10-01, không có token (và lần nữa với một token Shukah).
App khớp một tổng order với một dòng như thế nào, và nó có coi amount_to là bao gồm hay không: Chưa
được tài liệu hóa, vì code của app chưa được đọc cho việc này.
Lỗi
| HTTP | Body | Khi nào |
|---|---|---|
500 | Một body lỗi của Nest. Các key chính xác của nó chưa được capture. | Không đọc được tùy chọn rate, hoặc giá trị của nó không phải một danh sách. Chưa capture. |
Không có 401: route không có guard. Các query key và header lạ bị bỏ qua.
Thử ngay
export ORDER_BASE_URL=https://dev.v2.api.order.locate.sa
curl -s "$ORDER_BASE_URL/api/v2/client/system_configs/point_rates"
Kỳ vọng: 200 và mảng như ở mục Response. Không cần token.
Khác biệt so với Shukah
| Khía cạnh | Shukah | Locate | Tác động lên app |
|---|---|---|---|
| URL | GET $SHUKAH_BASE_URL/maeda/point-rate | GET $ORDER_BASE_URL/api/v2/client/system_configs/point_rates | Đổi base URL và path. App đã có lời gọi này cho chế độ Merchant. |
| Token | Không cần (200 khi không có token trên dev, 2026-10-01). | Không cần (200 khi không có token trên dev, 2026-10-01). | Không có. |
| Các dòng trên dev | Năm dòng: 0–139: 2, 140–153: 12, 154–250: 16, 251–450: 20, 451 and up: 30 (capture trên dev, 2026-10-01). | Bốn dòng: 70–140: 2, 140–251: 4, 251–451: 10, 451 and up: 16. | Thẻ "Earn Points" sẽ hiển thị các bậc khác sau khi chuyển. Bảng nào là đúng: Chưa được tài liệu hóa. |
| Cuối khoảng | Các khoảng không chồng lên nhau: 139 rồi 140. | Các khoảng dùng chung giá trị cuối: 140 kết thúc một dòng và bắt đầu dòng kế tiếp. | Nếu app coi amount_to là bao gồm, một tổng chính xác 140 hoặc 251 khớp hai dòng. |
| Dòng đầu | Bắt đầu từ 0. | Bắt đầu từ 70. App thêm một dòng cho các tổng dưới dòng đầu, với point_earn 0. | Một tổng dưới 70 khớp dòng được thêm đó, nên thẻ hiển thị 0 điểm trên Locate. |
| Câu trả lời rỗng | Chưa được tài liệu hóa. | [] khi tùy chọn chưa được đặt. | Code của app đọc dòng đầu mà không kiểm tra, nên một mảng rỗng làm lời gọi lỗi với lỗi 422 của chính app. |
| Quy tắc credit | Không áp dụng. | order-v2 vẫn quyết định số điểm một order tích được từ bảng của Shukah, không phải bảng này. | Thẻ có thể hiển thị một tỷ lệ khác với số điểm order thực sự tích được. |
POST /api/v2/client/gift-card-code
GET /maeda/giftcard-code/use✅ Đã thay thế🚩 enableMerchantService- 🟡 GET ?code= thành POST {code}; app đã có cả hai lệnh gọi và chọn theo chế độ (points.service.ts:128).
- 🟡 Kiểm tra token giống các route points (user-profile-auth.guard.ts:60-62), nên app chỉ có JWT sẽ nhận 401.
- 🟡 Chỉ các code do order-v2 giữ mới hợp lệ; code do Shukah phát hành có thể nhận 404 "The code not exist".
- 🟡 Điểm được cộng qua add-point của Shukah trừ khi usePointService bật, nên số dư Locate thay đổi muộn hơn.
- 🟡 Lỗi có dạng Nest {message,statusCode}; app hiển thị message nên nội dung phải phù hợp với người dùng.
Công tắc phía app: app chỉ gọi route Locate này ở chế độ Merchant (client settings enableMerchantService: true); nếu không, app vẫn gọi Shukah.
Locate nhận POST với {code} thay vì GET ?code= và kiểm tra code với các gift-card code do chính Locate giữ (chưa rõ code do Shukah phát hành có tồn tại ở đó không); app đã gọi route này ở chế độ Merchant. 0 lượt gọi trong bảng traffic production ngày 2026-10-02 (3 ngày).
order-v2 tự phục vụ route này và không chuyển nó sang point service. Nên các quy tắc proxy và các lỗi 502 và 504 của các trang điểm khác không áp dụng cho nó. App chỉ gọi nó ở chế độ Merchant.
Request
| Field | Vị trí | Kiểu | Bắt buộc | Ràng buộc | Mô tả |
|---|---|---|---|---|---|
Authorization | header | string | có | Bearer <token>. Dev trả 401 khi không có token. Nó dùng cùng cách kiểm tra token như các route điểm (qua /api/v1/users, với x-auth-from: SHUKAH), nên trên dev hiện nay token Shukah chạy còn JWT Locate nhận 401 (đọc code; chưa gọi với token). | Token của chính client. Client id lấy từ nó. |
Content-Type | header | string | có | application/json. | Body là JSON. |
code | body | string | có | Một chuỗi không rỗng. Một code thiếu, rỗng hoặc không phải string bị bước validation từ chối với 400. | Gift-card code. |
Response
201 được kỳ vọng khi thành công, nhưng chưa được capture: lời gọi tiêu một code và cộng điểm,
nên nó chưa được chạy. Handler không set status nào khác, và mặc định của framework cho một POST là
201. Body có sáu key:
| Field | Kiểu | Nullable | Định dạng / ràng buộc | Mô tả |
|---|---|---|---|---|
total_points | number | không | Số tiền của code nhân với tỷ giá. Tỷ giá là setting exchanged_points (point_rate chia cho riyad_rate), hoặc 4 khi setting đó thiếu hay không đọc được. | Điểm được cộng. |
date_created | string | không | yyyy-mm-dd hh:mm:ss theo giờ Ả Rập (UTC+3). | Khi code được đổi. |
description | string | không | Points received for the <code> gift card. | Text cho lịch sử. |
message | string | không | Text tiếng Ả Rập cố định تم شحن الرصيد بنجاح. | Thông báo thành công. |
points_type | string | không | Luôn là earn. | Loại dòng. |
trans_type | string | không | Luôn là adjustment. | Loại giao dịch. |
{
"total_points": 40,
"date_created": "2026-10-01 12:00:00",
"description": "Points received for the ABC12345 gift card",
"message": "تم شحن الرصيد بنجاح",
"points_type": "earn",
"trans_type": "adjustment"
}
Minh họa, không capture: dựng từ response type trong locate-order-v2-api@d9e4e5f6. Một lời gọi
live sẽ dùng hết một gift-card code và cộng điểm. Các giá trị là bịa.
Lỗi
| HTTP | Body | Khi nào |
|---|---|---|
401 | {"message":"Unauthorized","statusCode":401} | Không có token dùng được. Đã capture trên dev, 2026-10-01, khi không có token. Đây là shape của Nest, không phải envelope của point-service. |
400 | Một lỗi validation của Nest. Chưa capture. | code thiếu, rỗng hoặc không phải string. |
404 | Message The code not exist, theo shape lỗi của Nest. Chưa capture. | Không có code nào với giá trị đó, hoặc code vẫn là draft. |
400 | Message This code has already been used, theo shape lỗi của Nest. Chưa capture. | Code đã có client hoặc được đánh dấu đã dùng. |
400 | Message The code has expired, theo shape lỗi của Nest. Chưa capture. | Thời điểm hiện tại đã qua 24:00 giờ Ả Rập (21:00 UTC) của ngày expired_at của code. |
500 | Shape lỗi của Nest. Chưa capture. | Việc cộng điểm thất bại ba lần liên tiếp, cách nhau một giây. |
Thử ngay
Lời gọi này tiêu một gift-card code và cộng điểm. Hãy dùng một code được tạo để test.
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" in Details
curl -s -X POST "$ORDER_BASE_URL/api/v2/client/gift-card-code" -H "Authorization: Bearer $SHUKAH_TOKEN" -H "Content-Type: application/json" -d '{"code":"<test-code>"}'
Kỳ vọng: body sáu key như ở mục Response. Hãy dùng token Shukah trên dev hiện nay: một JWT Locate bị
từ chối bởi cùng bước kiểm tra như các route điểm (đọc code). Không có token bạn nhận 401 như ở mục
Lỗi.
Khác biệt so với Shukah
| Khía cạnh | Shukah | Locate | Tác động lên app |
|---|---|---|---|
| Method và code | GET $SHUKAH_BASE_URL/maeda/giftcard-code/use?code=<code>. | POST $ORDER_BASE_URL/api/v2/client/gift-card-code với {"code":"<code>"} trong body. | Đổi method, URL và nơi đặt code. App đã có cả hai lời gọi và chọn một theo chế độ app. |
| Token | Token opaque của Shukah. | Cùng cách kiểm tra token như các route điểm (qua /api/v1/users), nên là token Shukah trên dev hiện nay; một JWT Locate nhận 401 (đọc code; chưa gọi với token). | App đã gửi token Shukah của nó ở chế độ Merchant. Hãy giữ nó cho đến khi bước kiểm tra token chấp nhận một JWT Locate. |
| Kho code | Các gift-card code của chính Shukah. | Các code trong merchant database của order-v2. Các code do Shukah phát hành có ở đó không: Chưa được tài liệu hóa. | Một code chạy được trên Shukah có thể bị Locate từ chối. |
| Response | Chưa được tài liệu hóa: không có capture Shukah nào, vì lời gọi cộng điểm. | Sáu key: total_points, date_created, description, message, points_type, trans_type. | App parse cùng sáu key cho cả hai chế độ. Hãy so sánh các giá trị trước khi chuyển. |
| Khoản credit đáp xuống đâu | Không áp dụng. | Shukah hoặc point service, tùy theo một server flag. Nó gửi credit sang Shukah khi flag tắt (mặc định của nó). Giá trị trên dev: Chưa được tài liệu hóa. | Với flag tắt, credit tới Shukah trước, nên số dư Locate chỉ thay đổi sau khi sync. |
| Body lỗi | Chưa được tài liệu hóa. | 401 là {"message":"Unauthorized","statusCode":401} của Nest. | Mọi chỗ so khớp theo text lỗi đều phải đổi. |
Lưu ý
- Số dư điểm cần token Shukah trên dev hiện nay (một JWT Locate nhận
401), con số của nó có bốn chữ số thập phân, và vào 2026-10-02 nó thấp hơn của Shukah 447.96 điểm cho cùng một client. - Checkout vẫn kiểm tra số dư Shukah khi client trả một order bằng điểm, và điểm tiêu ở một bên không bị trừ ở bên kia (dev, 2026-10-02, trong vài giây; về sau chưa kiểm tra): màn hình Wallet và checkout có thể mâu thuẫn nhau, nên hãy đọc số dư ở bên tạo order.
- Bảng tỷ lệ tích điểm trên dev khác của Shukah, và code của app lỗi với một mảng rỗng, mà route này trả về khi tùy chọn bị thiếu.
Chi tiết (cho backend)
Chỉ kiểm tra dev, và chỉ qua order-v2 tại https://dev.v2.api.order.locate.sa, prefix
/api/v2/client. Point service nằm ở https://dev-point-api.locate.sa (được nêu trong tài liệu
của chính point service). Việc order-v2 trên dev dùng host đó chưa được chứng minh: các capture cho thấy một
point service đang chạy phía sau nó, và bản thân URL là Chưa được tài liệu hóa. Host UAT và production:
Chưa được tài liệu hóa.
Trong mục này OV2: nghĩa là locate-order-v2-api@d9e4e5f6 (2026-10-01), PS: nghĩa là
locate-point-service@2c44ce3 (2026-10-01) và AUTH: nghĩa là locate-authentication-service@6757aa4
(cả ba đều là origin/develop, được đọc lại vào 2026-10-02). Các trích dẫn mobile nêu đầy đủ tên repo.
Các lời gọi proxy hoạt động thế nào
Mục này áp dụng cho lời gọi balance và, với các route khác, cho các trang điểm khác của module này. Các route tỷ lệ tích điểm và gift-card bên dưới nó không được proxy.
- Route. App gọi order-v2, vốn không có guard, không có validation và không có body type trên
các route này. Nó chuyển lời gọi sang point service và trả status và body không đổi
(
OV2: src/modules/client/point/point.controller.ts:17-29,OV2: src/modules/client/point/point.service.ts:64). Lời gọi balance đi tớiGET /api/client/pointstrên point service, với giới hạn 10 giây (PS: services/api/src/routes/client.routes.ts:17,PS: services/api/src/handlers/client-points.ts:6-10). - Cái gì được forward. Chỉ
Authorization(như đã nhận),Accept: application/jsonvà, vớiPOST,Content-Type: application/json. Không được forward:x-from-application,from-application,x-from-version,Accept-Language, device id và cookie, dù app gửi một vài trong số đó (OV2: src/modules/client/point/point.service.ts:54-60,mobile/locate-client@e960c3a: src/shared/api/helpers/getCommonHeaders.ts:4-11). Lời gọi balance không forward query nào. - Hai lỗi do order-v2 tạo ra. Một lỗi transport với code
ECONNABORTEDhoặcETIMEDOUTthành504. Bất kỳ lỗi transport nào khác thành502. NếuPOINT_SERVICE_URLchưa được set, mọi lời gọi đều là502(OV2: src/modules/client/point/point.service.ts:11-16,74-96). Body502cũng có mộtmessage, mà tài liệu tham khảo của order-v2 bỏ sót (OV2: docs/locate-point-client-api-reference.md:15-16). - Lỗi của framework trước khi proxy chạy (từ một lần tái hiện local trên Nest 10.4.22; phiên bản
runtime của dev chưa biết): một JSON body sai định dạng là
400theo shape của Nest{"message":"…","error":"Bad Request","statusCode":400}, và một body trên khoảng 100 kB là413. Đây không phải envelope của point-service. - Kiểm tra token. Point service yêu cầu
Authorization: Bearer <token>và gọiGET {AUTH_SERVICE_URL}/api/v1/usersvới token đó và một giá trịx-auth-frommặc định làSHUKAHvà có thể đổi bằng settingAUTH_VERIFY_APPLICATION.x-auth-fromcủa chính caller không bao giờ được forward. Mọi câu trả lời không phải 2xx là401. Client id là giá trị đầu tiên trongclient_id,user_idvàidcủa câu trả lời, và nó phải là số nguyên dương (PS: services/api/src/middleware/client-auth.ts:7-18,PS: services/api/src/services/client-auth.service.ts:15-37). Một auth service không kết nối được cũng là401(auth service unreachable: …), không phải5xx, dù tài liệu tham khảo của order-v2 nói500(PS: services/api/src/services/client-auth.service.ts:26-28,OV2: docs/locate-point-client-api-reference.md:201). - Request body được log. order-v2 log request body mà không che, kể cả
cvvcủa lời gọi trả bằng thẻ (OV2: src/main.ts:38,OV2: src/shared/lib/base.lib.ts:32-43).
Error envelope. Mọi lỗi của point-service là
{"success":false,"error":{"code":…,"message":…,"details"?:…}}. Với VALIDATION_ERROR từ
schema, details là {"formErrors":[…],"fieldErrors":{"<field>":["<message>"]}}.
error.code | HTTP |
|---|---|
VALIDATION_ERROR | 400 |
UNAUTHORIZED | 401 |
FORBIDDEN | 403 |
NOT_FOUND | 404 |
CONFLICT | 409 |
INSUFFICIENT_POINTS | 422 |
INTERNAL_ERROR | 500 |
(PS: packages/core/src/errors/error.ts:19-26,42-47.) Bất kỳ exception nào không phải AppError
đều thành INTERNAL_ERROR Internal server error
(PS: services/api/src/middleware/error-handler.ts:18-22).
Token nào chạy được
Trên dev, vào 2026-10-02, GET /api/v2/client/points trả 200 cho một token opaque của Shukah và 401
{"success":false,"error":{"code":"UNAUTHORIZED","message":"Invalid or expired token"}} cho một JWT
Locate. Các route history, offers, occasions và FAQ cho cùng một cặp kết quả. Route earn-rate trả
200 cho cả hai, vì nó không có guard. Vào 2026-10-01 route balance đã trả 200 với body
giống hệt cho cả hai token, và GET /api/v1/users với x-auth-from: SHUKAH cũng vậy.
Vì sao JWT Locate giờ lỗi:
- order-v2 không kiểm tra token trên các route được proxy. Nó forward
Authorization(OV2: src/modules/client/point/point.controller.ts:17-29,OV2: src/modules/client/point/point.service.ts:54-60). - Point service hỏi auth service
GET /api/v1/usersvớix-auth-from: SHUKAH, trừ khiAUTH_VERIFY_APPLICATIONđược đặt, và biến mọi câu trả lời không phải2xxthành401 Invalid or expired token(PS: services/api/src/services/client-auth.service.ts:17,22-29). Message khớp với capture, nên việc từ chối đến từ auth service, không phải do thiếu header. Guard riêng của order-v2 trên route gift-card thực hiện cùng lời gọi vớiSHUKAHđược hard-code (OV2: src/auth/guards/user-profile-auth.guard.ts:60-62). - Với
x-auth-from: SHUKAHauth service đọcauth_source(AUTH: src/middlewares/user.js:35-37,101;AUTH: src/services/setting.js:46-50). Client settings công khai của dev trả"auth_source":"shukah"vào 2026-10-02. Một biến môi trườngAUTH_SOURCEsẽ ghi đè nó và không thể đọc từ bên ngoài, nên điều này rất có khả năng đúng, chưa được chứng minh. Ở nhánh đó token được chuyển tiếp tới/maeda/api/verify-tokencủa Shukah, và mọi lỗi đều là401(AUTH: src/middlewares/user.js:132-136,178-185). Shukah dev chấp nhận token Shukah và từ chối JWT Locate vào 2026-10-02 (suy luận: verify-token của Shukah không được gọi với chính JWT). Vào 2026-10-01 nó vẫn chấp nhận JWT, có lẽ qua việc push các JWT Locate mới sang Shukah (xem User detail). - Với
auth_sourceđặt làlocate, mọi JWT Locate cũng sẽ nhận401:AUTH: src/services/client.js:12-17gọi.cacheQuerytrên Promise màClient.findByTokenkiểuasynctrả về (AUTH: src/models/client.js:163).
Vậy một JWT Locate chỉ chạy trên các route này nếu Shukah dev lại chấp nhận nó, hoặc nếu auth_source
trở thành locate và defect đó được sửa. Cho đến lúc đó app cần một token Shukah cho mọi route có
guard trên các trang điểm. Các route này chấp nhận token nào ở UAT và production: Chưa được tài liệu
hóa.
Route balance
- Nó đọc ba thứ cùng lúc: tài khoản điểm của client, point settings, và dòng earn được tính
mới nhất của client. Nó không ghi gì, không gửi push và không gọi Shukah
(
PS: services/api/src/services/client-points.service.ts:14-24). - Vì sao bốn chữ số thập phân. Ledger lưu điểm ở bốn chữ số thập phân và số dư được ghi dưới dạng
text với bốn chữ số thập phân.
formatShukahPointschỉ bỏ các số 0 ở cuối và dấu chấm ở cuối, nên nó không bao giờ làm tròn (PS: packages/core/src/money/minor-units.ts:3-4,21-23,PS: services/api/src/services/client-points.mappers.ts:3-8; được chốt bởiPS: services/api/src/services/client-points.mappers.test.ts:31-41). Giá trị"514405.6788"là số dư đã lưu đúng như nó. Những dòng ledger nào tạo ra phần.6788: Chưa được tài liệu hóa. - Khoảng cách với Shukah. Vào 2026-10-01 số dư khác nhau theo hệ số khoảng 3.9966
(
514405.6788so với128712.41). Vào 2026-10-02 Shukah trả514853.64, nên dữ liệu Shukah dev đã thay đổi, và Locate thấp hơn 447.96 điểm. Dòng earn mới nhất của Locate (shukah_id1351722, 2026-09-29 12:45:07) chậm 45 id so với của Shukah (1351767, 2026-10-01 17:00:01), và phần đuôi bị thiếu khớp với khoảng chênh (suy luận). Các báo cáo parity tháng 7 có cả hai phía ở khoảng 511,700 (PS: docs/reports/2026-07-17-client-api-parity.md:39-40). - Quy tắc hết hạn.
nulltrừ khi settings nóiinactivity_daysvà tồn tại một dòng earn được tính. Khi đó ngày là thời điểm tạo của dòng earn được tính mới nhất cộngexpiration_inactivity_daysngày, hiển thị dưới dạng ngày Asia/Riyadh (PS: services/api/src/services/client-points.mappers.ts:30-39; testPS: services/api/src/services/client-points.mappers.test.ts:18-29: 2026-07-16 cộng 60 ngày cho2026-09-14). Các dòng hết hạn cũ mà Shukah ghi dưới dạng earn âm không bị loại khỏi "earn mới nhất", nên một dòng như vậy có thể đẩy ngày ra xa. Các lần hết hạn mới được lưu dưới dạng expense và không như vậy (PS: services/api/src/services/client-points.service.ts:17-21,PS: packages/core/src/points/sync-shukah-points.ts:97-102). - Settings của dev (suy luận). Dev trả
2026-11-28. Dòng ledger Locate mới nhất của client là 2026-09-29 và 2026-09-29 cộng 60 ngày là 2026-11-28, nên dev hành xử như 60 ngày không hoạt động. Document settings thật chỉ đọc được qua route admin với một service key: Chưa được tài liệu hóa. Các settings được cache khoảng 10 giây (PS: packages/core/src/db/point-config.singleton.ts:20,89). - Ngày có được thực thi không? Expiry job là dry run (nó mô phỏng và không ghi gì) khi
nguồn điểm không phải
merchant, khi loại hết hạn không phảiinactivity_days, hoặc khi cửa sổ là dưới 1 ngày. Nếu nguồn làmerchantvàMERCHANT_FIRST_ENABLEDkhông phảitrue, job không mô phỏng: nó từ chối chạy và lỗi vớiINTERNAL_ERROR(PS: packages/core/src/points/expire-points.ts:245-254,PS: packages/core/src/points/merchant-first-guard.ts:21-28). Shukah vẫn ghi các lần hết hạn của chính nó. Khi job áp dụng, một số dư hết hạn khi ngày là hôm nay hoặc sớm hơn theo Asia/Riyadh, và job chạy hằng ngày lúc 01:00 Asia/Riyadh (PS: packages/core/src/points/expire-points.ts:63-66,PS: docs/plans/2026-09-16-point-expiration-job.md:6). Nó có áp dụng trên dev hay không: Chưa được tài liệu hóa. - Hai số dư. Với việc tính giá giỏ hàng và checkout order khi cửa hàng chấp nhận trả bằng điểm,
order-v2 gọi
GET {SHUKAH_BASE_URL}/maeda/client/pointsvới token của client. Nó dùng 0 nếu ngày hết hạn của Shukah đã qua hoặc lời gọi lỗi. Điều này không phụ thuộc vào flagusePointService(OV2: src/shared/lib/handle-points.lib.ts:326-350,OV2: src/modules/client/orders/orders.service.ts:1128-1136,5036-5047). - Việc tiêu điểm không được đồng bộ. Lời gọi thật trên dev, 2026-10-02 (client 356259): Shukah
514853.64và Locate514405.6788trước;514743.64và514405.6788sau một orderpointtrên Shukah (order 1257);514743.64và514295.6788sau cùng order đó trên Locate (order 100012). Mỗi bên chỉ trừpurchased_point_amount(110) trên ledger của chính nó. Một orderpointcủa Locate xếp các chân Shukah vào một outbox mà hiện nay không có gì drain trên k8s (xem Point orders, Details). Việc tiêu điểm trên Shukah có tới Locate sau đó qua luồng đồng bộ vào hay không thì chưa được kiểm tra.
Route tỷ lệ tích điểm
- Controller là
@Controller('system_configs')với@Get('/point_rates')và không có guard (OV2: src/modules/client/system-configuration-options/system-configs.controller.ts:7-13). - Service đọc option
point_rateschomerchantId: 0từ merchant-v2 database, qua một Redis hash cache. Nó parse JSON, sắp theoamount_from(mộtnulltính là 0), và trả[]khi option bị thiếu. Text không phải JSON hợp lệ cũng cho[], vì parser fallback về một danh sách rỗng. Một500cần một lỗi đọc, hoặc JSON không phải danh sách (nên sort throw) (OV2: src/modules/client/system-configuration-options/system-configs.service.ts:18-40,OV2: src/services/redis/system-config-cache.service.ts:35-62,OV2: src/shared/lib/base.lib.ts:58-75). Nó không gọi point service cũng không gọi Shukah. - Nó không phải bảng mà order-v2 credit theo.
getPointsEarnđọc/maeda/point-ratecủa Shukah (OV2: src/shared/lib/handle-points.lib.ts:25-43,98-107,OV2: src/services/points/points.service.ts:160). Point service không có route rate, chỉ có một modelpoint_rateskhông dùng (PS: packages/core/src/db/models/point-settings.model.ts:14-19). - Một bộ bậc thứ ba. Text FAQ về điểm trên cả hai service nói 35–69 SR được 1 điểm, 70–139 được 2, 140–250 được 4, 251–450 được 8 và từ 451 trở lên được 16. Nó không khớp bảng nào (xem Points FAQ). Bảng nào là đúng: Chưa được tài liệu hóa.
- App. Ở chế độ Merchant app gọi route này; cùng bước hậu xử lý chạy trên cả hai
bảng. Nó thêm một dòng đầu với
amount_fromnull vàamount_tobằngamount_fromcủa dòng đầu trừ một. Phép kiểm tra cho dòng đó luôn đúng (nó được viết bằng||), nên dòng luôn được thêm. Nó đọcresponse.data?.[0].amount_frommà không có guard, nên[]throw (mobile/locate-client@e960c3a: src/entities/points/model/api/services/points.service.ts:78-126).
Route gift-card
@UseGuards(UserProfileGuard)và@Post(), với body type{code}(@IsString(),@IsNotEmpty()) (OV2: src/modules/client/gift-card-code/gift-card-code.controller.ts:8-24,OV2: src/modules/client/gift-card-code/dtos/apply-code.dto.ts:4-9). Guard thực hiện cùng lời gọi auth như các route điểm, vớix-auth-fromhard-code làSHUKAH, và token thiếu cho{"message":"Unauthorized","statusCode":401}của Nest (OV2: src/auth/guards/user-profile-auth.guard.ts:58-72).applyCodetra code trong merchant-v2 database với status không phảiDRAFT. Một code không tồn tại là404. Một code đã có client hoặc statusUSEDlà400. Một code hết hạn từ 24:00 giờ Ả Rập (21:00 UTC) của ngàyexpired_atcủa nó, và đó là400. Số điểm là số tiền nhân với tỷ giá, với tỷ giá từ settingexchanged_pointshoặc 4. Credit đi quaPointsService.addPoints, tối đa ba lần thử cách nhau một giây, rồi500. Sau đó code được đánh dấu là đã dùng (OV2: src/modules/core/gift-card-code/gift-card-code.service.ts:40-94,96-114,OV2: src/modules/core/gift-card-code/constants/exchange-point-rate.ts:1).- Credit đi đâu.
addPointsgửi nó tới/maeda/client/add-pointcủa Shukah khi flagusePointServicelà false (mặc định, cũng dùng khi không đọc được flag), và tới point service khi nó là true (OV2: src/services/points/points.service.ts:29-73,OV2: src/services/user-points/user-points.service.ts:39-52). Tài liệu của point service nói flag là false ở mọi môi trường và production không có kế hoạch bật nó (PS: docs/integrations/2026-07-30-use-point-service-flag-routing.md:13-16,37). Giá trị của dev chưa được đọc: Chưa được tài liệu hóa. - Point service không có route gift-card, và tài liệu của nó không nhắc tới.
- Response phản chiếu shape của Shukah: app parse cùng sáu key ở cả hai chế độ
(
mobile/locate-client@e960c3a: src/entities/points/model/api/dto/points.dto.ts:215-222). App gọi Shukah bằngGETvàcodetrong query, và Locate bằngPOSTvà{code}, chọn theo chế độ app (mobile/locate-client@e960c3a: src/entities/points/model/api/services/points.service.ts:128-168).
Khi tài liệu và code mâu thuẫn (code thắng)
- Các tài liệu service nói
pointslà số dư như đã lưu, và nó là "Shukah-formatted" (PS: docs/integrations/2026-07-16-client-facing-point-apis.md:92,OV2: docs/locate-point-client-api-reference.md:97). Điều đó chỉ đúng với các số 0 ở cuối: code giữ bốn chữ số thập phân. - Cả hai tài liệu đều nói
pointsExpirationDatelànullcho đến khi point settings được đặt (PS: docs/integrations/2026-07-16-client-facing-point-apis.md:463-467,OV2: docs/locate-point-client-api-reference.md:90,98,222-224). Dev giờ trả về một ngày. - Tài liệu point service nói một token opaque luôn được forward sang Shukah
(
PS: docs/integrations/2026-07-16-client-facing-point-apis.md:56-58). Trong code auth đó chỉ là một nhánh (xem "Token nào chạy được"). - Hướng dẫn manual-test 2026-09-21 nói order-v2 dev gọi một point service trên
localhost:4010, nên mọi lời gọi sẽ là502(PS: docs/plans/2026-09-21-point-order-manual-test-guide.md:24-32). Vào 2026-10-01 proxy dev trả200với dữ liệu ledger, nên hướng dẫn đã lỗi thời. - 2026-10-02: trang này nói một token Shukah và một JWT Locate đều chạy trên route balance, và mục
Thử ngay của nó dùng một JWT Locate. Vào 2026-10-02 JWT nhận
401; trang giờ nói lý do, và mục Thử ngay dùng một token Shukah. - 2026-10-02: trang này nói số dư của Locate gấp khoảng bốn lần của Shukah (
"128712.41"). Số dư Shukah dev giờ là"514853.64", cao hơn của Locate 447.96 điểm.
Sources
locate-order-v2-api@d9e4e5f6:src/modules/client/point/point.controller.ts:17-29,src/modules/client/point/point.service.ts:11-16,54-60,64,74-96,src/main.ts:38,src/shared/lib/base.lib.ts:32-43,58-75,src/modules/client/system-configuration-options/system-configs.controller.ts:7-13,src/modules/client/system-configuration-options/system-configs.service.ts:18-40,src/services/redis/system-config-cache.service.ts:35-62,src/modules/client/gift-card-code/gift-card-code.controller.ts:8-24,src/modules/client/gift-card-code/dtos/apply-code.dto.ts:4-9,src/modules/core/gift-card-code/gift-card-code.service.ts:40-114,src/modules/core/gift-card-code/constants/exchange-point-rate.ts:1,src/auth/guards/user-profile-auth.guard.ts:58-72,src/services/points/points.service.ts:29-73,160,src/services/user-points/user-points.service.ts:39-52,src/shared/lib/handle-points.lib.ts:25-43,98-107,326-350,src/modules/client/orders/orders.service.ts:1128-1136,5036-5047- Tài liệu của
locate-order-v2-api@d9e4e5f6:docs/locate-point-client-api-reference.md:15-16,90,97,98,201,222-224 locate-point-service@2c44ce3:services/api/src/routes/client.routes.ts:17,services/api/src/handlers/client-points.ts:6-10,services/api/src/middleware/client-auth.ts:7-18,services/api/src/services/client-auth.service.ts:15-37,services/api/src/middleware/error-handler.ts:18-22,packages/core/src/errors/error.ts:19-26,42-47,services/api/src/services/client-points.service.ts:14-24,services/api/src/services/client-points.mappers.ts:3-8,30-39,services/api/src/services/client-points.mappers.test.ts:18-29,31-41,packages/core/src/money/minor-units.ts:3-4,21-23,packages/core/src/db/point-config.singleton.ts:20,89,packages/core/src/points/expire-points.ts:63-66,245-254,packages/core/src/points/merchant-first-guard.ts:21-28,packages/core/src/points/sync-shukah-points.ts:97-102,packages/core/src/db/models/point-settings.model.ts:14-19- Tài liệu của
locate-point-service@2c44ce3:docs/integrations/2026-07-16-client-facing-point-apis.md:56-58,92,463-467,docs/integrations/2026-07-30-use-point-service-flag-routing.md:13-16,37,docs/plans/2026-09-16-point-expiration-job.md:6,docs/plans/2026-09-21-point-order-manual-test-guide.md:24-32,docs/reports/2026-07-17-client-api-parity.md:39-40 - Snapshot mobile
mobile/locate-client@e960c3a(branchdevelopment, 2026-02-26; code của chính app, không phải Locate):platforms/mobile/products/locate/screens/home/mainInner/Wallet/ui/Wallet/Wallet.tsx:78-85,src/features/PointsWallet/ui/PointsWallet/PointsWallet.tsx:29,src/entities/user/model/services/fetchUserInfo/fetchUserInfo.ts:55,src/features/selectRestaurantPaymentMethod/ui/SelectRestaurantPaymentMethod/SelectRestaurantPaymentMethod.container.tsx:64,platforms/mobile/products/locate/screens/restaurant/RestaurantCart/ui/RestaurantCart/RestaurantCart.tsx:301,platforms/mobile/shared/modules/core/cart/components/public/MerchantCartGeneralInfo/components/MerchantCartGeneralInfo/MerchantCartGeneralInfo.tsx:66,src/widgets/ApplyPointsPromoCode/model/services/applyPointsPromoCode.ts:27-50,src/entities/points/model/api/services/points.service.ts:30-44,78-168,src/entities/points/model/api/controllers/points.controller.ts:95-126,src/entities/points/model/api/dto/points.dto.ts:90-102,185-222,src/entities/settings/model/services/getServiceAvailable/getServiceAvailable.ts:48-54,src/shared/features/AppModeConfigs.ts:5-62,src/shared/api/helpers/getCommonHeaders.ts:4-11 locate-authentication-service@6757aa4:src/middlewares/user.js:35-37,101,132-136,178-185,src/services/setting.js:46-50,src/services/client.js:12-17,src/models/client.js:163locate-point-service@2c44ce3, kiểm tra token:services/api/src/services/client-auth.service.ts:17,22-29;locate-order-v2-api@d9e4e5f6:src/auth/guards/user-profile-auth.guard.ts:60-62- Capture dev, 2026-10-02 (parity audit):
points--shukah,points--locate-st,points--locate-jwt,point-rate--shukah,point-rate--locate-st,point-rate--locate-jwt; client settings dev được đọc từhttps://dev.go.locate.sa/api/v1/settings/client - Capture dev, 2026-10-01:
locate-points,locate-points-locate-token,shukah-points,probe-locate-points,probe-shukah-points,locate-point-rates,probe-locate-point-rates,shukah-point-rate,probe-shukah-point-rate,probe-locate-gift-card-code,locate-points-faq,shukah-points-faq