Point offers
Bối cảnh
Form tặng điểm cần hai danh sách: các gói điểm (offer) và các dịp (occasion). Trên Shukah app
đọc chúng bằng GET /maeda/point-offer và GET /maeda/point-occasion. Locate có cùng hai
route đó, và một route mới trả về một offer đơn lẻ. Các field giống của Shukah. Có ba
điều cần chú ý: id của một offer và của một occasion là chuỗi 24 ký tự thay vì
số, Locate cần token trong khi Shukah trả lời mà không cần, và ?id= của Shukah trả về toàn bộ
danh sách.
Tính năng
Một offer là một gói điểm mà client có thể mua bằng thẻ hoặc trả bằng điểm, để gửi cho client khác. Một occasion là lý do của món quà, như Birthday. Trong snapshot app ngày 2026-02-26 các lời gọi xảy ra trên form send-gift:
| Lời gọi | Ở đâu trong app |
|---|---|
| Danh sách offer | Khi bộ chọn số điểm quà tặng xuất hiện trên form send-gift. |
Một offer (Shukah ?id=) | Trong gift summary, trong nút checkout và trong bộ chọn phương thức thanh toán. App hỏi Shukah với ?id= rồi tự chọn offer ra khỏi danh sách trả về. |
| Danh sách occasion | Khi bộ chọn occasion xuất hiện trên form send-gift. |
Một occasion (Shukah ?id=) | Trong gift summary. Cùng cách tự chọn phía client. |
Các id đi vào các lời gọi sau. App gửi id của offer làm point_offer_id và id của occasion
làm point_occasion_id khi nó xin text của lời nhắn
(Point order description) và khi nó tạo order
(Create point order).
Tất cả đều cần token trên Locate, và trên dev hiện nay chỉ có token Shukah chạy: một JWT Locate nhận
401 Invalid or expired token trên danh sách offer và occasion vào 2026-10-02, dù cả hai
token đều chạy trên route balance vào 2026-10-01 (xem
Which tokens work). order-v2 chuyển từng lời gọi sang point
service và trả câu trả lời không đổi.
Id: lưu chúng, gửi lại chúng, không bao giờ parse chúng. id của một offer hay occasion là chuỗi
hex 24 ký tự của dòng database của nó. Nó khác nhau ở mỗi môi trường, nên app phải đọc id từ
danh sách và không bao giờ viết một id vào code. Một integer Shukah gửi tới Locate thay cho id bị từ chối
với 400. App đã biến id thành text (String(id)) và so sánh chúng dạng text, cách đó chạy được
với các id mới, nhưng các TypeScript type của nó vẫn ghi number.
Quy tắc is_custom. is_custom là 0 hoặc 1, không bao giờ là boolean. Khi nó là 1 (trên dev:
Thank và Other) app hiển thị một ô nhập text tự do và gửi những gì client đã gõ làm
occasion, cạnh id của occasion.
GET /api/v2/client/point-offers
GET /maeda/point-offer✅ Đã thay thế- 🟡 id là chuỗi 24 ký tự hex (Shukah 1..5); app dùng String(id) nên chạy được; id của Shukah trả 400.
- 🟡 Locate cần token; Shukah trả 200 khi không có token; app luôn gửi token.
- 🟡 JWT của Locate nhận 401 trên dev; hãy gửi token của Shukah.
- 🟡 Năm offer giống nhau ở point_amount, purchased_point_amount, amount ("40.00" dạng chuỗi) và order.
Bộ lọc ?id= bị bỏ qua: lệnh gọi vẫn trả toàn bộ danh sách.
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 trên dev hiện nay. Một JWT Locate nhận 401 Invalid or expired token (capture trên dev, 2026-10-02, trên hai danh sách): xem Which tokens work. | Token của chính client. |
id | query | any | không | Được chấp nhận và bỏ qua: toàn bộ danh sách luôn được trả về, như ?id= của Shukah. | Giữ lại để lời gọi Shukah cũ vẫn chạy. |
| Bất kỳ query key nào khác | query | — | không | Được chấp nhận và bỏ qua, ví dụ language. | — |
Response
200 với một mảng trần các offer active. Nó có thể rỗng. Không có phân trang.
| Field | Kiểu | Nullable | Định dạng / ràng buộc | Mô tả |
|---|---|---|---|---|
id | string | không | Chuỗi hex 24 ký tự, ví dụ "6a46104f3dd7b3dd6a261d90". Khác nhau ở mỗi môi trường. Shukah gửi một integer. | Id của offer. Gửi lại nó làm point_offer_id. |
point_amount | number | không | Số JSON. | Số điểm người nhận được. |
purchased_point_amount | number | có | null khi offer không có giá bằng điểm. Trên dev nó cao hơn point_amount. | Số điểm người gửi trả khi trả bằng điểm. |
amount | string | không | Chính xác hai chữ số thập phân, ví dụ "40.00". | Giá bằng SAR cho thanh toán bằng thẻ. |
[
{"id": "6a46104f3dd7b3dd6a261d90", "point_amount": 100, "purchased_point_amount": 110, "amount": "40.00"},
{"id": "6a46104f3dd7b3dd6a261d91", "point_amount": 200, "purchased_point_amount": 240, "amount": "60.00"},
{"id": "6a46104f3dd7b3dd6a261d94", "point_amount": 300, "purchased_point_amount": 360, "amount": "90.00"},
{"id": "6a46104f3dd7b3dd6a261d96", "point_amount": 400, "purchased_point_amount": 480, "amount": "120.00"},
{"id": "6a46104f3dd7b3dd6a261d98", "point_amount": 500, "purchased_point_amount": 600, "amount": "150.00"}
]
Capture trên dev, 2026-10-01 (client 356259): cả năm offer.
Người gửi trả nhiều điểm hơn số người nhận được: 110 cho 100, 240 cho 200, 360 cho 300, 480 cho
400 và 600 cho 500. Một offer có purchased_point_amount là null không thể trả bằng điểm:
create point order trả 400 cho nó.
Thứ tự. Các offer được sắp theo số thứ tự do admin đặt, rồi theo Shukah id, rồi theo row id. Cách sắp đó được commit vào 2026-10-01. Dev có chạy nó khi lấy sample hay không: Chưa được tài liệu hóa.
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 | Cùng shape, với message là Invalid or expired token, 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. Invalid or expired token đã được capture trên dev, 2026-10-02, với một JWT Locate trên hai danh sách. |
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":"…"}}.
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" on Points wallet
curl -s "$ORDER_BASE_URL/api/v2/client/point-offers" -H "Authorization: Bearer $SHUKAH_TOKEN"
Kỳ vọng: 200 và mảng 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 (2026-10-02). Không có token bạn nhận 401 như ở mục Lỗi.
Khác biệt so với Shukah
Năm offer bằng nhau về point_amount, purchased_point_amount, amount và thứ tự key vào
2026-10-01 và lại vào 2026-10-02. Chỉ id khác.
| Khía cạnh | Shukah | Locate | Tác động lên app |
|---|---|---|---|
| URL | GET $SHUKAH_BASE_URL/maeda/point-offer | GET $ORDER_BASE_URL/api/v2/client/point-offers | Đổi base URL và path. |
| Token | Không cần: 200 khi không có token (capture trên dev, 2026-10-01). | Bắt buộc: 401 khi không có token (capture trên dev, 2026-10-01). Chỉ token Shukah trên dev hiện nay: một JWT Locate nhận 401 (2026-10-02). | App đã gửi token của nó trên mọi lời gọi, và phải giữ nó là token Shukah ở đây. Một màn hình hiển thị offer trước khi đăng nhập sẽ lỗi. Màn hình như vậy có tồn tại hay không: Chưa được tài liệu hóa. |
id | Một integer, 1 đến 5 (capture trên dev, 2026-10-01). | Một chuỗi 24 ký tự. | App biến id thành text, nên nó chạy được. Các type của nó ghi number. Đừng bao giờ trộn hai loại: Locate từ chối integer của Shukah. |
?id= | Trả về toàn bộ danh sách. App tự chọn offer ra. | Được chấp nhận và bỏ qua: lại toàn bộ danh sách. | Không có: cách tự chọn phía client của app chạy được với các id mới. |
purchased_point_amount | Một số (capture trên dev, 2026-10-01). | Một số, hoặc null khi offer không có giá bằng điểm. | App khai báo kiểu là number và copy nó sang costInPoints. Phải xử lý null. |
| Thứ tự | Chưa được tài liệu hóa. | Theo số thứ tự do admin đặt, rồi Shukah id, rồi row id. | Nếu app hiển thị danh sách theo thứ tự của server, thứ tự có thể đổi. |
| Body lỗi | Chưa được tài liệu hóa. Route này public, nên không có 401 để so sánh. | Envelope của point-service ở mục Lỗi. | Mọi chỗ so khớp theo text lỗi đều phải đổi. |
GET /api/v2/client/point-offers/:id
Route mới. Shukah không có route nào trả về một offer: ?id= của nó trả về toàn bộ danh sách, và app
tự chọn offer ra khỏi danh sách đó. Hiện nay app không gọi route này.
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 trên dev hiện nay. Một JWT Locate nhận 401 Invalid or expired token (capture trên dev, 2026-10-02, trên hai danh sách): xem Which tokens work. | Token của chính client. |
id | path | string | có | Chính xác 24 ký tự hex (chữ hoa hoặc thường). Integer của Shukah bị từ chối, không được tra cứu. | Id của offer từ danh sách. |
| Bất kỳ query key nào | query | — | không | Được order-v2 forward và point service bỏ qua. | — |
Response
200 với một object, cùng shape với một dòng của danh sách (id, point_amount,
purchased_point_amount, amount; xem danh sách).
{"id": "6a46104f3dd7b3dd6a261d90", "point_amount": 100, "purchased_point_amount": 110, "amount": "40.00"}
Capture trên dev, 2026-10-01 (client 356259): offer đầu tiên.
Lỗi
| HTTP | Body | Khi nào |
|---|---|---|
400 | {"success":false,"error":{"code":"VALIDATION_ERROR","message":"point offer id must be a 24-character hex ObjectId"}} | :id không phải 24 ký tự hex. Không có key details. Chưa capture. |
404 | {"success":false,"error":{"code":"NOT_FOUND","message":"point offer <id> not found or inactive"}} | Không có offer với id đó, hoặc nó inactive hay đã bị xóa. Một id 24 hex không tồn tại là 404, không phải 400. Chưa capture. |
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 | Cùng shape, với message là Invalid or expired token, 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. Invalid or expired token đã được capture trên dev, 2026-10-02, với một JWT Locate trên hai danh sách. |
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. |
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" on Points wallet
export OFFER_ID='<id-from-the-offers-list>'
curl -s "$ORDER_BASE_URL/api/v2/client/point-offers/$OFFER_ID" -H "Authorization: Bearer $SHUKAH_TOKEN"
Kỳ vọng: 200 và offer như ở mục Response. Một id không phải 24 ký tự hex cho 400
như ở mục Lỗi. Hãy dùng token Shukah trên dev hiện nay: route này có cùng bước kiểm tra token như các danh sách.
GET /api/v2/client/point-occasions
GET /maeda/point-occasion✅ Đã thay thế- 🟡 id là chuỗi 24 ký tự hex (Shukah 1..7); app dùng String(id); gửi id của Shukah vào create của Locate trả 400.
- 🟡 Locate cần token; Shukah trả 200 khi không có token; app luôn gửi token.
- 🟡 JWT của Locate nhận 401 trên dev; hãy gửi token của Shukah.
- 🟡 Bảy dòng giống nhau ở occasion, occasion_en, is_custom (số 0/1) và thứ tự key.
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 trên dev hiện nay. Một JWT Locate nhận 401 Invalid or expired token (capture trên dev, 2026-10-02, trên hai danh sách): xem Which tokens work. | Token của chính client. |
language | query | string | không | Sau khi trim và chuyển chữ thường, chính xác ar cho nhãn tiếng Ả Rập. Mọi giá trị khác, kể cả ar-SA và en-GB, và không có giá trị, cho tiếng Anh. Không bao giờ là lỗi. | Ngôn ngữ của nhãn occasion. |
id | query | any | không | Được chấp nhận và bỏ qua: toàn bộ danh sách được trả về. | Giữ lại để lời gọi Shukah cũ vẫn chạy. |
App thêm language=en-GB hoặc language=ar-SA vào mọi lời gọi, nên với các giá trị của chính app
nhãn luôn là tiếng Anh. Có chủ đích hay không: Chưa được tài liệu hóa.
Response
200 với một mảng trần các occasion active. Nó có thể rỗng. Không có phân trang.
| Field | Kiểu | Nullable | Định dạng / ràng buộc | Mô tả |
|---|---|---|---|---|
id | string | không | Chuỗi hex 24 ký tự. Khác nhau ở mỗi môi trường. Shukah gửi một integer. | Id của occasion. Gửi lại nó làm point_occasion_id. |
occasion | string | không | Nhãn tiếng Anh, đã trim. Với language=ar, là nhãn tiếng Ả Rập, hoặc nhãn tiếng Anh khi không có tiếng Ả Rập. | Nhãn để hiển thị. |
occasion_en | string | không | Nhãn tiếng Anh, đã trim, luôn luôn. | Nhãn tiếng Anh. |
is_custom | integer | không | 0 hoặc 1, không bao giờ là boolean. | 1 nghĩa là app hiển thị một ô nhập text tự do. |
[
{"id": "6a4610503dd7b3dd6a261dbf", "occasion": "Birthday", "occasion_en": "Birthday", "is_custom": 0},
{"id": "6a4610503dd7b3dd6a261dc0", "occasion": "Born", "occasion_en": "Born", "is_custom": 0},
{"id": "6a4610503dd7b3dd6a261dc1", "occasion": "Gift", "occasion_en": "Gift", "is_custom": 0},
{"id": "6a4610503dd7b3dd6a261dc3", "occasion": "Wedding", "occasion_en": "Wedding", "is_custom": 0},
{"id": "6a4610503dd7b3dd6a261dc5", "occasion": "Graduation", "occasion_en": "Graduation", "is_custom": 0},
{"id": "6a4610503dd7b3dd6a261dc6", "occasion": "Thank", "occasion_en": "Thank", "is_custom": 1},
{"id": "6a4610503dd7b3dd6a261dc7", "occasion": "Other", "occasion_en": "Other", "is_custom": 1}
]
Capture trên dev, 2026-10-01 (client 356259), không có language: cả bảy occasion.
App có một vấn đề về type cũng tồn tại trên Shukah: nó đọc nhãn tiếng Anh là occasionEn,
nhưng cả hai server đều gửi occasion_en, nên nameEn của app hiện là undefined.
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 | Cùng shape, với message là Invalid or expired token, 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. Invalid or expired token đã được capture trên dev, 2026-10-02, với một JWT Locate trên hai danh sách. |
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. |
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" on Points wallet
curl -s "$ORDER_BASE_URL/api/v2/client/point-occasions?language=en-GB" -H "Authorization: Bearer $SHUKAH_TOKEN"
Kỳ vọng: 200 và mảng 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 (2026-10-02). Không có token bạn nhận 401 như ở mục Lỗi.
Khác biệt so với Shukah
Bảy dòng bằng nhau về occasion, occasion_en, is_custom và thứ tự key vào 2026-10-01 và lại
vào 2026-10-02. Chỉ id khác.
| Khía cạnh | Shukah | Locate | Tác động lên app |
|---|---|---|---|
| URL | GET $SHUKAH_BASE_URL/maeda/point-occasion | GET $ORDER_BASE_URL/api/v2/client/point-occasions | Đổi base URL và path. |
| Token | Không cần: 200 khi không có token (capture trên dev, 2026-10-01). | Bắt buộc: 401 khi không có token (capture trên dev, 2026-10-01). Chỉ token Shukah trên dev hiện nay: một JWT Locate nhận 401 (2026-10-02). | App đã gửi token của nó trên mọi lời gọi, và phải giữ nó là token Shukah ở đây. |
id | Một integer, 1 đến 7 (capture trên dev, 2026-10-01). | Một chuỗi 24 ký tự. | App biến id thành text, nên nó chạy được. Đừng bao giờ trộn hai loại. |
?id= | Trả về toàn bộ danh sách. | Được chấp nhận và bỏ qua: lại toàn bộ danh sách. | Không có: cách tự chọn phía client của app chạy được với các id mới. |
Nhãn occasion | Cùng text tiếng Anh trong occasion và occasion_en (capture trên dev, 2026-10-01). | Tiếng Anh, hoặc tiếng Ả Rập khi language chính xác là ar. | App gửi ar-SA, nên không bao giờ nhận được nhãn tiếng Ả Rập. |
| Thứ tự | Chưa được tài liệu hóa. | Theo số thứ tự do admin đặt, rồi Shukah id, rồi row id. | Nếu app hiển thị danh sách theo thứ tự của server, thứ tự có thể đổi. |
| Body lỗi | Chưa được tài liệu hóa. Route này public, nên không có 401 để so sánh. | Envelope của point-service ở mục Lỗi. | Mọi chỗ so khớp theo text lỗi đều phải đổi. |
Lưu ý
- Id của offer và occasion là chuỗi 24 ký tự khác nhau theo môi trường: lưu chúng, gửi lại chúng, không bao giờ parse chúng, và không bao giờ gửi integer của Shukah.
- Locate cần token trên cả ba route, trong khi các danh sách của Shukah là public.
?id=vẫn trả về toàn bộ danh sách, nên hãy chọn offer hoặc occasion ra khỏi mảng, hoặc dùngGET /point-offers/:id.
Chi tiết (cho backend)
Chỉ kiểm tra dev, qua order-v2 tại https://dev.v2.api.order.locate.sa, prefix /api/v2/client.
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 và PS: nghĩa là locate-point-service@2c44ce3. Cách proxy, việc
kiểm tra token và error envelope hoạt động được mô tả một lần, ở
Các lời gọi proxy hoạt động thế nào.
Route và proxy
| Route của app (order-v2) | Route upstream (point service) | Được forward | Giới hạn |
|---|---|---|---|
GET /api/v2/client/point-offers | GET /api/client/point-offers | Authorization và toàn bộ query | 10 s |
GET /api/v2/client/point-offers/:id | GET /api/client/point-offers/<id>, với id đi qua encodeURIComponent | Authorization và toàn bộ query (upstream bỏ qua) | 10 s |
GET /api/v2/client/point-occasions | GET /api/client/point-occasions | Authorization và toàn bộ query, nên language đến nơi | 10 s |
(OV2: src/modules/client/point/point.controller.ts:61-108,
PS: services/api/src/routes/client.routes.ts:20-22,
PS: services/api/src/handlers/client-point-offers.ts:12-23,
PS: services/api/src/handlers/client-point-occasions.ts:12-14.)
Point service đọc gì
- Offer. Các dòng của
point_offersvớistatus: 'active'vàdeleted_at: null, sắp theosequence, rồishukah_id, rồi_id. Không có phân trang hay giới hạn. Một offer do Locate tạo không có Shukah id được liệt kê như bất kỳ offer nào khác (PS: services/api/src/services/point-settings.service.ts:10-25). Cách sắp được đổi vào 2026-10-01 (commit105119c, merge 14:25 +07), trong khi tài liệu integration vẫn ghi "ordered byshukah_id" (PS: docs/plans/2026-10-01-app-language-and-arabic-reason.md:6-8,PS: docs/integrations/2026-07-16-client-facing-point-apis.md:321-322). Các capture được lấy lúc khoảng 17:19 +07. Build đó có trên dev hay không: Chưa được tài liệu hóa. - Một offer. Cùng filter trên
_id. Một offer inactive hoặc đã xóa là404(PS: services/api/src/services/point-settings.service.ts:29-34). Việc kiểm tra id là^[0-9a-fA-F]{24}$(PS: services/api/src/lib/object-id.ts:12-14,PS: services/api/src/handlers/client-point-offers.ts:16,19-23). - Occasion. Các dòng của
point_occasionsvới cùng filter, sắp cùng cách (PS: services/api/src/services/point-settings.service.ts:38-42). Quy tắclanguage(chính xácarsau khi trim và chuyển chữ thường) được ship trong cùng commit105119c(PS: services/api/src/handlers/client-point-occasions.ts:5-8,PS: docs/plans/2026-10-01-app-language-and-arabic-reason.md:10-17). - Id. Các id là giá trị
_idriêng của các dòng, render dưới dạng hex chữ thường 24 ký tự. Chữ hoa được chấp nhận ở đầu vào (PS: services/api/src/services/point-settings.mappers.ts:25-58). Các integer mà Shukah dùng vẫn được lưu trên các dòng dưới dạngshukah_id, nhưng các route client không còn chấp nhận chúng: mộtpoint_offer_idkhông phải 24 ký tự hex lỗi vớimust be a 24-character hex ObjectId(kiểm tra bằng một lần chạy local của schema, zod 3.25.76). - Query
?id=. Handler không đọcid; toàn bộ danh sách được trả về, điều mà tài liệu của point service nói là khớp với Shukah, "measured on dev 2026-09-21" (PS: services/api/src/handlers/client-point-offers.ts:7-11,PS: docs/integrations/2026-07-16-client-facing-point-apis.md:324-328). is_custom. Mapper ghidoc.is_custom ? 1 : 0(PS: services/api/src/services/point-settings.mappers.ts:56). Một occasion custom là trường hợp duy nhất mà app gửi text tự do trongoccasioncùng với id (PS: services/api/src/services/point-settings.service.ts:57-58).- Chỉnh sửa. Offer và occasion được thay đổi qua các route admin dưới
/api/admin/…với một service key, không bao giờ bởi app (PS: services/api/src/routes/admin.routes.ts:34-44).
App
Các type của app ghi id: number cho offer và occasion
(mobile/locate-client@e960c3a: src/entities/points/model/types/pointGiftOption.server.ts:1-6,
mobile/locate-client@e960c3a: src/entities/points/model/types/pointGiftOccasion.server.ts:1-6). Các
DTO biến id thành text và tính cost là Number(amount), costInPoints là
purchased_point_amount, và isCustom là String(is_custom) === '1'
(mobile/locate-client@e960c3a: src/entities/points/model/api/dto/points.dto.ts:104-136). Các helper
by-id lọc danh sách trả về bằng String(item.id) === request.id
(mobile/locate-client@e960c3a: src/entities/points/model/api/services/points.service.ts:212-241,285-295).
Language interceptor thêm language=en-GB hoặc language=ar-SA vào mọi lời gọi
(mobile/locate-client@e960c3a: src/shared/api/interceptors/languageInterceptor.ts:5-15).
Khi tài liệu và code mâu thuẫn (code thắng)
- Tài liệu integration nói offer được sắp theo
shukah_idvới_idlà tiebreak (PS: docs/integrations/2026-07-16-client-facing-point-apis.md:321-322). Code sắp theosequencetrước. - 2026-10-02: trang này nói một token Shukah và một JWT Locate đều chạy trên các route này. Vào
2026-10-02 JWT nhận
401trên danh sách offer và occasion, và các lời gọi Thử ngay giờ dùng một token Shukah (PS: services/api/src/services/client-auth.service.ts:22-29; xem Which tokens work).
Sources
locate-order-v2-api@d9e4e5f6:src/modules/client/point/point.controller.ts:61-108locate-point-service@2c44ce3:services/api/src/routes/client.routes.ts:20-22,services/api/src/handlers/client-point-offers.ts:7-23,services/api/src/handlers/client-point-occasions.ts:5-14,services/api/src/services/point-settings.service.ts:10-42,57-58,services/api/src/services/point-settings.mappers.ts:25-58,services/api/src/lib/object-id.ts:12-14,services/api/src/routes/admin.routes.ts:34-44- Tài liệu của
locate-point-service@2c44ce3:docs/integrations/2026-07-16-client-facing-point-apis.md:321-328,docs/plans/2026-10-01-app-language-and-arabic-reason.md:6-17 - Snapshot mobile
mobile/locate-client@e960c3a(branchdevelopment, 2026-02-26; code của chính app, không phải Locate):src/entities/points/model/api/services/points.service.ts:190-263,285-295,src/entities/points/model/api/dto/points.dto.ts:104-136,src/entities/points/model/types/pointGiftOption.server.ts:1-6,src/entities/points/model/types/pointGiftOccasion.server.ts:1-6,src/features/SelectPointGift/model/hooks/useSelectPointGiff.ts:16,src/features/SelectPointGiftOccasion/model/hooks/useSelectPointGiftOccasion.ts:10,src/widgets/SendPointGiftForm/ui/SendPointGiftForm/SendPointGiftForm.tsx:149-185,src/features/PointGiftSummary/ui/PointGiftSummary/PointGiftSummary.tsx:34-37,src/features/PointGiftsCheckoutButton/ui/PointGiftsCheckoutButton/PointGiftsCheckoutButton.tsx:33,src/features/SelectPointGiftPaymentMethod/model/hooks/useSelectPointGiftPaymentMethod.ts:52,src/shared/api/interceptors/languageInterceptor.ts:5-15 - Capture dev, 2026-10-02 (parity audit):
point-offer--shukah,point-offer--locate-st,point-offer--locate-jwt,point-occasion--shukah,point-occasion--locate-st,point-occasion--locate-jwt - Capture dev, 2026-10-01:
locate-point-offers,locate-point-offers-again,locate-point-offer,locate-point-occasions,locate-point-occasions-again,shukah-point-offer,shukah-point-occasion,probe-locate-point-offers,probe-locate-point-offer,probe-locate-point-occasions,probe-shukah-point-offer,probe-shukah-point-occasion