improve BA skill
This commit is contained in:
235
ba-output/e-commerce/03-specification/API_US002-003_v1.0.md
Normal file
235
ba-output/e-commerce/03-specification/API_US002-003_v1.0.md
Normal file
@@ -0,0 +1,235 @@
|
||||
# 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)*
|
||||
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"cartId": "cart_123",
|
||||
"status": "active",
|
||||
"sellers": [
|
||||
{
|
||||
"sellerId": "seller_11",
|
||||
"sellerName": "Shop A",
|
||||
"subtotalVnd": 325000,
|
||||
"items": [
|
||||
{
|
||||
"cartItemId": "citem_001",
|
||||
"productVariantId": "variant_789",
|
||||
"productName": "Áo thun basic",
|
||||
"variantLabel": "Size M, Đỏ",
|
||||
"unitPriceVnd": 100000,
|
||||
"quantity": 2,
|
||||
"imageUrl": "https://cdn.example.com/p/789.jpg"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"totalVnd": 585000,
|
||||
"totalItemCount": 5
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**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)*
|
||||
|
||||
```json
|
||||
{ "quantity": 3 }
|
||||
```
|
||||
|
||||
| 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)*
|
||||
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"cartItemId": "citem_001",
|
||||
"quantity": 3,
|
||||
"sellerSubtotalVnd": 375000,
|
||||
"cartTotalVnd": 635000
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**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)*
|
||||
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"deletedCartItemId": "citem_001",
|
||||
"sellerSubtotalVnd": 260000,
|
||||
"cartTotalVnd": 260000,
|
||||
"sellerRemoved": false
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**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 |
|
||||
Reference in New Issue
Block a user