API — Contract — US-002, US-003 (SCR-04 Giỏ hàng)
|
|
| Version |
1.0 |
| Date |
2026-09-08 |
| Author |
BA (qua skill ba-3-specification) |
| Status |
🟡 Draft |
| Nguồn contract |
🔶 Hỗn hợp — method/path/auth: ✅ BE cung cấp (e-commerce/docs/sections/04-api-design.md §4.1.5 "Cart & Order Service"); request/response body schema: ⚠️ BA đề xuất — chờ BE xác nhận (SAD liệt kê method/path/mô tả ngắn nhưng không có ví dụ JSON cho ba endpoint dưới đây, khác với POST /v1/checkout là endpoint duy nhất SAD có ví dụ đầy đủ) |
| Approved by |
BE Lead: — |
| Source |
SRS_US002-003_v1.0.md · e-commerce/docs/sections/04-api-design.md §4.1.1, §4.1.5, §4.1.13, §4.2 |
| Scope |
US-002, US-003 |
🔴 Đọc dòng Nguồn contract trước. Method/path là ràng buộc thật (đã có trong SAD đã
duyệt). Cấu trúc request/response bên dưới là giả định của BA, dev phải đối chiếu với
tài liệu BE thật (hoặc OpenAPI spec nếu có) trước khi code.
1. Ba điểm phải chốt trước tiên
| # |
Vấn đề |
Quyết định |
Lý do |
| 1 |
Số lớn (cartItemId, productVariantId, sellerId) truyền dạng gì |
string |
Khớp ví dụ POST /v1/checkout của SAD ("orderId": "order_001" dạng string) — áp dụng nhất quán cho mọi id trong hệ thống |
| 2 |
Thời gian — SCR-04 không hiển thị trường thời gian nào |
N/A |
Không có trường *_at nào cần hiển thị ở US-002/US-003 |
| 3 |
Phân trang |
Không áp dụng — GET /v1/cart trả toàn bộ giỏ hàng, không phân trang |
Giỏ hàng không có khái niệm "trang", số dòng thực tế nhỏ (không giống danh sách sản phẩm) |
2. Quy ước chung
|
|
| Base URL |
https://api.<domain>/v1 (theo SAD §4.1.1) |
| Xác thực |
Bearer JWT (Customer) hoặc header X-Guest-Session-Id (Guest) — theo SAD §4.1.1, §4.1.5 |
| Định dạng phản hồi thành công |
{ "data": {...} } (theo SAD §4.1.1) |
| Định dạng phản hồi lỗi |
{ "error": { "code", "message", "details" }, "traceId" } (theo SAD §4.1.13 — khác với format mặc định của template BA, ưu tiên đúng theo SAD) |
| Mã HTTP dùng |
200 · 204 · 400 · 401 · 403 · 404 · 409 · 500 · 503 |
| Ngôn ngữ thông điệp |
FE tự dịch theo mã lỗi (code) + bảng text §4.2 của SRS, không dựa vào message trả về từ BE để hiển thị cho người dùng cuối (theo UICONV §9 — BA sở hữu text hiển thị, W5) |
🔴 Lỗi nghiệp vụ trả mã HTTP 4xx (không phải 200 kèm cờ lỗi) — đúng theo thiết kế đã có ở
SAD §4.1.13.
3. Endpoint
3.1 GET /v1/cart — Lấy giỏ hàng hiện tại (đa seller)
|
|
| Mục đích |
Tải dữ liệu hiển thị SCR-04 (US-002) |
| Màn hình |
SCR-04 |
| Quyền |
ROLE-01 Guest (X-Guest-Session-Id) hoặc ROLE-02 Customer (Bearer JWT) — không cần scope riêng, chỉ cần định danh hợp lệ |
Query parameters: không có (không phân trang).
Response 200 (⚠️ đề xuất của BA — SAD không có ví dụ JSON cho endpoint này)
Từng trường
| Trường |
Kiểu |
Có thể null |
Nguồn |
Ghi chú |
cartId |
string |
❌ |
cart.id |
— |
status |
enum |
❌ |
cart.status |
active/converted/abandoned — SCR-04 chỉ hiển thị khi active |
sellers[].sellerId |
string |
❌ |
cart_item.seller_id |
— |
sellers[].sellerName |
string |
❌ |
Module Seller Management (tham chiếu) |
🔴 BA đề xuất BE trả kèm tên đã join sẵn — cần BE xác nhận, xem §5 mục 1 |
sellers[].subtotalVnd |
number |
❌ |
Tính từ items[] |
🔴 BA đề xuất BE tính sẵn (không để FE tự cộng) — tránh sai lệch làm tròn giữa FE/BE, xem §5 mục 2 |
items[].unitPriceVnd |
number |
❌ |
cart_item.unit_price_snapshot hoặc giá real-time |
🔴 Phụ thuộc OQ-029 (chưa chốt ở SRS) |
items[].quantity |
number |
❌ |
cart_item.quantity |
Số nguyên |
totalVnd |
number |
❌ |
Σ sellers[].subtotalVnd |
— |
totalItemCount |
number |
❌ |
Σ items[].quantity toàn giỏ |
Dùng cho C01 |
Response lỗi
| HTTP |
code |
Khi nào |
Mã lỗi SRS |
| 401 |
ERR_AUTH_INVALID_TOKEN |
Customer token hết hạn/không hợp lệ |
Điều hướng đăng nhập (AC-US002-06), không có mã E-CART riêng |
| 500 / 503 |
ERR_INTERNAL / ERR_SERVICE_UNAVAILABLE |
Lỗi hệ thống/timeout |
E-CART-0004 |
3.2 PATCH /v1/cart/items/{cartItemId} — Cập nhật số lượng
|
|
| Mục đích |
Cập nhật quantity của một CartItem (US-003, field F01) |
| Màn hình |
SCR-04 |
| Quyền |
Chủ sở hữu CartItem (ownership theo customerId/sellerId trong JWT, hoặc session_id cho Guest — cơ chế Guest chưa xác nhận, OQ-032) |
Request body (⚠️ đề xuất)
| Trường |
Kiểu |
Bắt buộc |
Ràng buộc |
Field SRS |
quantity |
number |
✅ |
Số nguyên ≥ 1; ngưỡng trên 🔴 chưa chốt (OQ-030) |
F01 |
🔴 Ràng buộc ở API phải khớp bảng field trong SRS — giữ nguyên "≥ 1", không thêm ngưỡng
trên tự ý cho tới khi OQ-030 được trả lời.
Response 200 (⚠️ đề xuất — cần BE xác nhận trả về CartItem đã cập nhật hay toàn bộ
Cart mới, xem §5 mục 3)
Response lỗi
| HTTP |
code |
Khi nào |
Mã lỗi SRS |
| 400 |
ERR_VALIDATION |
quantity không phải số nguyên ≥ 1 |
E-CART-0002 |
| 403 |
ERR_FORBIDDEN_OWNERSHIP |
cartItemId không thuộc giỏ hàng của caller |
E-CART-0005 |
| 404 |
ERR_NOT_FOUND |
cartItemId không tồn tại (đã bị xoá) |
E-CART-0003 |
| 409 |
ERR_CONFLICT |
cartItemId đã bị xoá bởi request khác trong lúc xử lý |
E-CART-0003 |
| 422 |
ERR_BUSINESS_RULE |
🔴 Dự kiến — nếu OQ-030 chốt có kiểm tra tồn kho tại đây |
(chưa có mã — chỉ tạo khi OQ-030 xác nhận thuộc scope) |
| 500 / 503 |
ERR_INTERNAL / ERR_SERVICE_UNAVAILABLE |
Lỗi hệ thống/timeout |
E-CART-0006 |
3.3 DELETE /v1/cart/items/{cartItemId} — Xoá sản phẩm khỏi giỏ
|
|
| Mục đích |
Xoá một CartItem (US-003, nút C07, sau khi xác nhận modal) |
| Màn hình |
SCR-04 |
| Quyền |
Chủ sở hữu CartItem — cùng quy tắc §3.2 |
Response 200/204 (⚠️ đề xuất — 204 không có body; BA đề xuất 200 kèm sellerSubtotalVnd/
cartTotalVnd mới để FE khỏi tự tính lại, cần BE xác nhận, xem §5 mục 4)
Response lỗi
| HTTP |
code |
Khi nào |
Mã lỗi SRS |
| 403 |
ERR_FORBIDDEN_OWNERSHIP |
Không thuộc quyền sở hữu |
E-CART-0005 |
| 404 |
ERR_NOT_FOUND |
Đã bị xoá trước đó |
E-CART-0003 |
| 409 |
ERR_CONFLICT |
Xung đột đồng thời |
E-CART-0003 |
| 500 / 503 |
ERR_INTERNAL / ERR_SERVICE_UNAVAILABLE |
Lỗi hệ thống/timeout |
E-CART-0006 |
4. Bảng đối chiếu mã lỗi
| Mã lỗi SRS |
Endpoint |
HTTP |
code của API |
☐ Khớp |
E-CART-0002 |
PATCH /v1/cart/items/{id} |
400 |
ERR_VALIDATION |
☐ |
E-CART-0003 |
PATCH, DELETE /v1/cart/items/{id} |
404/409 |
ERR_NOT_FOUND / ERR_CONFLICT |
☐ |
E-CART-0004 |
GET /v1/cart |
500/503 |
ERR_INTERNAL / ERR_SERVICE_UNAVAILABLE |
☐ |
E-CART-0005 |
PATCH, DELETE /v1/cart/items/{id} |
403 |
ERR_FORBIDDEN_OWNERSHIP |
☐ |
E-CART-0006 |
PATCH, DELETE /v1/cart/items/{id} |
500/503 |
ERR_INTERNAL / ERR_SERVICE_UNAVAILABLE |
☐ |
Cột ☐ do BE đánh dấu khi xác nhận khớp implementation thật.
5. Điểm cần BE xác nhận
| # |
Điểm cần chốt |
Đề xuất của BA |
BE trả lời |
Ngày |
| 1 |
GET /v1/cart trả sellerName đã join sẵn, hay chỉ sellerId để FE tự gọi API khác lấy tên? |
Trả sẵn sellerName — tránh FE gọi thêm N request cho N seller |
|
|
| 2 |
sellers[].subtotalVnd/totalVnd do BE tính sẵn, hay FE tự cộng từ items[]? |
BE tính sẵn — tránh sai lệch làm tròn giữa FE/BE |
|
|
| 3 |
PATCH /v1/cart/items/{id} trả về CartItem đã cập nhật + tổng nhóm/tổng giỏ, hay toàn bộ Cart mới? |
Trả CartItem + tổng nhóm/tổng giỏ liên quan (nhẹ hơn trả toàn bộ Cart) |
|
|
| 4 |
DELETE /v1/cart/items/{id} trả 200 kèm body hay 204 rỗng? |
200 kèm tổng nhóm/tổng giỏ mới + cờ sellerRemoved (để FE biết có cần ẩn cả nhóm không) |
|
|
| 5 |
id (cartItemId, productVariantId, sellerId) dạng string hay number? |
string |
|
|
| 6 |
Với Guest, ownership của cartItemId có được đối chiếu theo session_id giống cơ chế 403 ERR_FORBIDDEN_OWNERSHIP của JWT không? (OQ-032, đã ghi ở SRS) |
Áp dụng cùng cơ chế |
|
|
| 7 |
PATCH /v1/cart/items/{id} có kiểm tra tồn kho (trả 422 ERR_BUSINESS_RULE) không? Phụ thuộc OQ-030 ở SRS |
Không kiểm tra ở endpoint này (giữ đúng khoanh vùng BACKLOG) — chờ PO xác nhận |
|
|
6. Hành vi khi API lỗi (giao diện phải làm gì)
| Tình huống |
Giao diện làm gì |
AC |
| 401 hết phiên (Customer) |
Chuyển về đăng nhập, giữ đường dẫn quay lại /cart |
AC-US002-06 |
| 403 (ownership) |
Chuyển trang "Không có quyền", không hiển thị dữ liệu |
AC-US003-12, 13 |
| 404/409 (item đã bị xoá) |
Banner tại dòng, sau đó ẩn dòng, cập nhật lại tổng |
AC-US003-11 |
5xx / timeout khi tải (GET) |
Banner C12 thay chỗ danh sách, nút "Thử lại" |
AC-US002-03 |
5xx / timeout khi sửa/xoá (PATCH/DELETE) |
Banner tại dòng, giữ nguyên dữ liệu trước đó, mở lại nút |
AC-US003-09, 10 |
Đang gửi PATCH/DELETE |
Khoá F01/C07 của đúng dòng đang xử lý (không khoá toàn trang) để tránh gửi trùng |
— |
🔴 Khoá đúng nút/dòng đang xử lý, không khoá toàn màn hình — SCR-04 thường có nhiều dòng
CartItem cùng lúc; khoá toàn trang khi chỉ một dòng đang cập nhật sẽ chặn nhầm thao tác trên
các dòng khác.
7. Open Questions
| ID |
Câu hỏi |
Hỏi ai |
Từ ngày |
Chặn gì |
| OQ-032 |
Cơ chế ownership check của Guest cho PATCH/DELETE /v1/cart/items/{id} (xem SRS §8) |
Tech Lead |
2026-09-08 |
§5 mục 6, E-CART-0005 cho Guest |
| OQ-030 |
PATCH /v1/cart/items/{id} có kiểm tra tồn kho (422 ERR_BUSINESS_RULE) không (xem SRS §8) |
PO, Tech Lead |
2026-09-08 |
§3.2, §5 mục 7 |