--- document: SAD version: "0.2" briefVersion: 3 status: approved --- # Ghi chú rà soát **Bản ráp v0.2** — revision nhẹ theo yêu cầu người duyệt sau khi **v0.1 đã được DUYỆT** (tổng hợp `docs/00-project-brief.md` v3 và 9 mục `docs/sections/01`–`09`, tất cả `approved`: 01/02/03/07 = v1, 04/05 = v3, 06 = v2, 08 = v2, 09 = v1). Người duyệt **không** yêu cầu chạy lại bất kỳ mục 1–9 nào; toàn bộ nội dung mục 1–9 và các finding tồn đọng đã liệt kê ở §0.4 tại v0.1 được **giữ nguyên**. Thay đổi duy nhất ở v0.2: (1) bổ sung 2 finding mới phát sinh từ việc consolidate bản ráp v0.1 vào danh sách "Findings tồn đọng chờ xử lý thủ công" ở §0.4 — mục 2 (medium): FR-14 thiếu acceptance criteria cho ngưỡng loyalty theo từng hạng (chờ chủ dự án chốt ngưỡng VND); mục 6 (medium): BR-14 thiếu công thức hoàn tiền dispute cụ thể (toàn phần/tỷ lệ, ai chịu phí ship hoàn) — người duyệt quyết định **không** chạy lại mục 2/6 vì cần chủ dự án trả lời trước; (2) thêm dòng lịch sử phiên bản v0.2 ở §0.1; (3) đánh dấu bản ráp SAD tổng thể là **đã duyệt (approved)** ở §0.2/§0.3, thay cho trạng thái `ready-for-approval` trước đó. Rà soát traceability (không đổi so với v0.1) cho thấy toàn bộ FR-01→FR-27 đều có ít nhất 1 mục thiết kế (3–8) và ≥1 test case (mục 9) tham chiếu; cột "Mục thiết kế liên quan"/"Test Case" của Ma trận truy vết mục 2.4 giữ nguyên như đã điền ở v0.1 (không sửa file gốc mục 2). Toàn bộ 9 mục vẫn ở trạng thái `approved` ở cấp độ mục — bản ráp SAD tổng thể nay ở trạng thái `approved` cấp tài liệu. # 0. Document Control ## 0.1 Lịch sử phiên bản (SAD.md) | Phiên bản | Ngày | Người soạn | Mô tả thay đổi | |---|---|---|---| | v0.1 | 2026-09-05 | AI agent pipeline | Ráp lần đầu toàn bộ mục 0–9 từ `docs/00-project-brief.md` (v3) và `docs/sections/01`–`09` (approved); xử lý ghi chú người duyệt (gom findings tồn đọng của mục 2/3/4/5 vào §0.4 thay vì chạy lại); điền cột "Mục thiết kế liên quan"/"Test Case" của Ma trận truy vết mục 2.4 trong bản ráp; bổ sung 2 finding mới (FR-14 thiếu acceptance criteria, BR-14 thiếu công thức hoàn tiền). | | v0.2 | 2026-09-05 | AI agent pipeline | Revision nhẹ theo ghi chú người duyệt sau khi v0.1 được DUYỆT: bổ sung 2 finding mới (mục 2 — FR-14 thiếu acceptance criteria ngưỡng loyalty; mục 6 — BR-14 thiếu công thức hoàn tiền dispute) vào §0.4 "Findings tồn đọng chờ xử lý thủ công" (không chạy lại mục 2/6, chờ chủ dự án); đánh dấu bản ráp SAD tổng thể là `approved` ở §0.2/§0.3; giữ nguyên toàn bộ nội dung mục 1–9 và phần còn lại của §0.4. | ## 0.2 Người phê duyệt **Đã duyệt (approved).** Product Owner / Kiến trúc sư trưởng đã phê duyệt bản ráp v0.1 và xác nhận các thay đổi bổ sung ở v0.2 (bổ sung 2 finding tồn đọng vào §0.4, không yêu cầu chạy lại mục 2/6 — chờ chủ dự án xác nhận ngưỡng VND loyalty và công thức hoàn tiền dispute trước khi xử lý). ## 0.3 Bảng trạng thái các mục | Mục | Tiêu đề | Status | Version | |---|---|---|---| | 00 | Project Brief | ready | 3 | | 01 | Tổng quan dự án | approved | 1 | | 02 | Phân tích yêu cầu | approved | 1 | | 03 | Thiết kế kiến trúc | approved | 1 | | 04 | Thiết kế API | approved | 3 | | 05 | Thiết kế dữ liệu | approved | 3 | | 06 | Thiết kế luồng xử lý chi tiết | approved | 2 | | 07 | Thiết kế giao diện | approved | 1 | | 08 | Thiết kế bảo mật | approved | 2 | | 09 | Kế hoạch vận hành & Kiểm thử | approved | 1 | Tất cả 9 mục đều ở trạng thái `approved` — không có mục nào `needs-revision`/`unknown` tại thời điểm ráp. **Bản ráp SAD tổng thể (docs/SAD.md) ở mức `approved`** kể từ v0.2 — Product Owner/Kiến trúc sư trưởng đã phê duyệt v0.1 và chấp nhận các bổ sung nhẹ tại v0.2 (xem §0.1, §0.2). ## 0.4 Findings tồn đọng chờ xử lý thủ công (theo quyết định người duyệt — không chạy lại mục đích) Danh sách dưới đây là các finding đã được người duyệt xác nhận **không yêu cầu chạy lại** mục nguồn (do mục đã hết vòng sửa hoặc mức độ severity thấp), ghi nhận tại đây để theo dõi và xử lý ở vòng sau/thủ công: **(a) Mục 3 — Thiết kế kiến trúc (low):** - Message broker MSK cần bật TLS in-transit và ACL theo topic (đặc biệt các topic mang dữ liệu tài chính/PII: `PaymentConfirmed`, `PayoutScheduled`, `OrderDelivered`, các domain event ghi `audit_log`) — chưa được cập nhật ở sơ đồ 3.2 (Finding F10, mục 8 §8.5). - Audit & Compliance Service (bổ sung ở mục 5 v3, bảng `audit_log` §5.2.11) chưa xuất hiện trong sơ đồ thành phần & triển khai 3.2 — cần bổ sung vào sơ đồ kiến trúc tổng thể khi có vòng cập nhật mục 3 tiếp theo. **(b) Mục 4 — Thiết kế API (low):** - Thiếu endpoint `GET /v1/admin/sellers/{sellerId}/kyc-documents/{documentId}/view-url` để Admin lấy pre-signed URL xem `KYCDocument` (sequence 6.1.5 v2 đã mô tả cơ chế nhưng mục 4 v3 chưa có endpoint tương ứng — Finding F11). - Thiếu mã lỗi `423 ERR_ACCOUNT_LOCKED` cho trường hợp tài khoản bị khoá tạm do vượt ngưỡng đăng nhập sai (`user_account.locked_until`, chính sách đã chốt ở mục 8 §8.1.1a — Finding F12). - Thiếu endpoint `GET /v1/admin/audit-logs` (scope `admin:audit:read`) để đọc `audit_log` (mục 5.2.11/5.5 v3 đã ghi chú giao cho `api-designer` nhưng mục 4 v3 chưa bổ sung — Finding F13). **(c) Mục 5 — Thiết kế dữ liệu:** - **(medium)** Retention `audit_log` hiện đồng nhất 5 năm — nên tách theo `resource_type`: 5 năm cho hành động vận hành (KYC review, khoá/mở seller), 10 năm cho hành động gắn trực tiếp tài chính (`commission_rule`, `payout`, `dispute` quyết định refund) để nhất quán với retention `payment`/`payout` (Finding F14, mục 8 §8.2.5c). - **(low)** `membership_tier.min_spend_threshold` vẫn là giá trị placeholder, chưa có số VND cụ thể cho từng hạng Bạc/Vàng/Kim Cương — cần chủ dự án xác nhận trước khi seed dữ liệu production. **(d) Mục 2 — Phân tích yêu cầu (low):** - FR-08 chưa định nghĩa mốc trạng thái chính xác cho "huỷ đơn (trong điều kiện cho phép)" — BR-10 (mục 6) tạm giả định mốc `packed`, cần chủ dự án xác nhận và bổ sung rõ ở mục 2. - Thiếu NFR quy định retention dữ liệu PII/KYC/tài chính cụ thể (số năm lưu trữ) — mục 5 (§5.3.6) và mục 8 (§8.4) hiện dùng giả định thận trọng theo thông lệ, cần bổ sung NFR chính thức ở mục 2 khi có xác nhận pháp lý/chủ dự án. **(e) Mục 2 — Phân tích yêu cầu (medium, bổ sung tại v0.2):** - FR-14 (Chương trình loyalty/điểm thưởng) thiếu acceptance criteria đủ chi tiết cho ngưỡng chuyển hạng thành viên (Bạc/Vàng/Kim cương) — chưa có số VND cụ thể cho từng hạng, chỉ mới mô tả nguyên tắc "theo tổng chi tiêu 12 tháng" ở brief. Liên quan trực tiếp `membership_tier.min_spend_threshold` (mục 5 §5.2.7, hiện là placeholder — xem finding (c) ở trên). Người duyệt xác nhận **không chạy lại mục 2** ở vòng này; cần chủ dự án chốt ngưỡng VND cụ thể trước khi bổ sung acceptance criteria và seed dữ liệu production. **(f) Mục 6 — Thiết kế luồng xử lý chi tiết (medium, bổ sung tại v0.2):** - BR-14 (quy tắc xử lý dispute/hoàn tiền, liên quan FR-09/FR-25) chưa có công thức hoàn tiền cụ thể: chưa rõ hoàn toàn phần hay theo tỷ lệ tuỳ mức độ lỗi, và bên nào (khách hàng/seller/sàn) chịu phí vận chuyển hoàn hàng. Ảnh hưởng đến độ chính xác của `payout_hold`/`commission_transaction` (mục 5 §5.2.6) khi dispute được giải quyết. Người duyệt xác nhận **không chạy lại mục 6** ở vòng này; cần chủ dự án trả lời trước khi bổ sung công thức chi tiết vào mục 6. > Ghi chú: toàn bộ danh sách (a)–(f) ở trên, bao gồm 2 finding mới bổ sung tại v0.2 ((e) và (f)), **không** được đưa vào `findings` của structured output — người duyệt đã xem xét và quyết định không yêu cầu chạy lại mục nguồn nào (chờ chủ dự án trả lời), tương tự các finding (a)–(d) đã xử lý từ v0.1. Các finding này tiếp tục được theo dõi tại đây và nên được phản ánh trong `openQuestions` của structured output cho đến khi chủ dự án xác nhận. ## 0.5 Tài liệu tham chiếu - `docs/00-project-brief.md` — version 3, status `ready`, round 3. - `docs/sections/01-tong-quan.md` (v1), `02-phan-tich-yeu-cau.md` (v1), `03-kien-truc.md` (v1), `04-api-design.md` (v3), `05-thiet-ke-du-lieu.md` (v3), `06-luong-xu-ly.md` (v2), `07-giao-dien.md` (v1), `08-bao-mat.md` (v2), `09-van-hanh-kiem-thu.md` (v1) — toàn bộ đều `approved`. --- # 1. Tổng quan dự án (System Overview) ## 1.1 Mục tiêu & Phạm vi ### Mục tiêu Xây dựng một **sàn thương mại điện tử marketplace đa người bán (multi-vendor B2C/B2B2C)**, quy mô lớn, cho phép: - Khách hàng (có tài khoản hoặc khách vãng lai) tìm kiếm, mua sắm sản phẩm từ nhiều người bán khác nhau trong cùng một trải nghiệm mua hàng thống nhất. - Người bán thứ ba (Seller/Vendor) tự đăng ký, được xác minh (KYC), tự quản lý sản phẩm/tồn kho/đơn hàng của mình và nhận thanh toán (payout) định kỳ từ sàn. - Sàn (Platform) thu hoa hồng (commission) trên mỗi giao dịch thành công theo bảng cấu hình theo ngành hàng, đồng thời quản trị chất lượng seller, catalog toàn sàn, khuyến mãi và xử lý tranh chấp. Bài toán cốt lõi cần giải quyết: **kết nối nhiều người bán với người mua trên một nền tảng dùng chung, xử lý được khối lượng giao dịch lớn (hàng trăm nghìn SKU, hàng trăm nghìn–hàng triệu user, cao điểm hàng nghìn–chục nghìn concurrent users mùa flash sale)**, đảm bảo dòng tiền minh bạch giữa khách hàng – sàn – seller (thanh toán, hoa hồng, payout, hoàn tiền/đổi trả). ### Phạm vi trong MVP (In-scope) - Danh mục & tìm kiếm sản phẩm đa người bán (catalog, filter, search). - Giỏ hàng đa seller trong cùng một đơn hàng, checkout, tách đơn theo seller. - Thanh toán: VNPay, Momo, COD (thu tiền mặt khi giao hàng). - Quản lý đơn hàng: tạo, theo dõi trạng thái, huỷ, đổi trả. - Tài khoản khách hàng: đăng ký/đăng nhập (email/password + tuỳ chọn Google/Facebook), địa chỉ giao hàng, lịch sử đơn hàng, wishlist. - Seller onboarding & KYC thủ công (upload giấy phép kinh doanh/CMND, admin duyệt). - Quản trị sản phẩm & tồn kho: seller tự quản lý, admin giám sát toàn sàn. - Cấu hình & tính hoa hồng (commission) theo ngành hàng (category), admin chỉnh được. - Payout cho seller: định kỳ hàng tuần, qua chuyển khoản ngân hàng, có kỳ giữ tiền (hold) sau giao hàng thành công. - Khuyến mãi/mã giảm giá cơ bản. - Đánh giá & nhận xét sản phẩm. - Chương trình loyalty/điểm thưởng và hạng thành viên (Bạc/Vàng/Kim cương). - Thông báo email/SMS xác nhận đơn hàng. - Đa ngôn ngữ: Tiếng Việt (mặc định), Tiếng Anh, Tiếng Trung, Tiếng Hàn, Tiếng Nhật (VI/EN/ZH/KO/JA). - Đa tiền tệ hiển thị: giao dịch bằng VND, hiển thị quy đổi tham khảo sang các tiền tệ khác (không giao dịch trực tiếp bằng ngoại tệ). - Tích hợp vận chuyển: GHN, GHTK. - Quản trị vận hành: xử lý tồn kho, đóng gói, giao hàng (Ops/Warehouse); xử lý khiếu nại/tranh chấp giữa khách hàng và seller (CSR). - Nền tảng client: web responsive. ### Ngoài phạm vi MVP (Out-of-scope — hoãn sang giai đoạn sau) - Affiliate marketing. - Subscription / bán hàng định kỳ. - Ứng dụng mobile app native (phase 1 chỉ web responsive). - Hoá đơn điện tử tự động cho seller. - Phân biệt commission theo seller tier (MVP chỉ phân biệt theo ngành hàng). - SSO/IdP doanh nghiệp (không có khách hàng B2B enterprise ở MVP). ## 1.2 Đối tượng sử dụng | Nhóm người dùng | Mô tả | Cấp phân quyền chính | |---|---|---| | **Khách vãng lai (Guest)** | Chưa có tài khoản | Duyệt sản phẩm, tìm kiếm, thêm giỏ hàng, checkout không cần đăng nhập (guest checkout). Không truy cập lịch sử đơn hàng, wishlist, loyalty. | | **Khách hàng đã đăng ký (Customer)** | Người mua có tài khoản | Toàn quyền trên tài khoản cá nhân: quản lý hồ sơ/địa chỉ, lịch sử đơn hàng, wishlist, điểm thưởng/hạng thành viên, viết đánh giá, khiếu nại/yêu cầu đổi trả đơn của chính mình. | | **Người bán (Seller/Vendor)** | Bên thứ ba bán hàng trên sàn, đã qua KYC | Quản lý catalog sản phẩm và tồn kho của riêng mình, xử lý đơn hàng thuộc gian hàng của mình, xem báo cáo doanh thu/hoa hồng/payout của mình. Không truy cập dữ liệu seller khác hoặc cấu hình toàn sàn. Khuyến khích bật MFA. | | **Quản trị viên sàn (Platform Admin)** | Vận hành và quản trị toàn sàn | Toàn quyền: duyệt/khoá seller (KYC), quản trị catalog toàn sàn, cấu hình bảng hoa hồng theo ngành hàng, cấu hình khuyến mãi, giám sát payout, xử lý escalation tranh chấp. Bắt buộc MFA. | | **Nhân viên vận hành/kho (Ops/Warehouse staff)** | Thuộc sàn hoặc thuộc seller | Xử lý tồn kho, đóng gói, cập nhật trạng thái giao hàng; phối hợp với đơn vị vận chuyển (GHN/GHTK). Phạm vi giới hạn theo đơn hàng/gian hàng được phân công. | | **Nhân viên chăm sóc khách hàng (CSR)** | Bộ phận hỗ trợ | Xử lý khiếu nại, yêu cầu đổi trả, tranh chấp giữa khách hàng và seller; có quyền xem (read) thông tin đơn hàng liên quan để hỗ trợ, không có quyền chỉnh sửa cấu hình hệ thống. | Ghi chú phân quyền chi tiết hơn (RBAC/ma trận quyền theo chức năng) sẽ được đặc tả trong mục 8 (Thiết kế bảo mật) — mục này chỉ mô tả ở mức nghiệp vụ. ## 1.3 Thuật ngữ (Glossary) | Thuật ngữ (EN, PascalCase) | Nghĩa tiếng Việt | |---|---| | Guest | Khách vãng lai, chưa đăng ký tài khoản | | Customer | Khách hàng đã đăng ký tài khoản | | Seller (Vendor) | Người bán thứ ba đăng ký kinh doanh trên sàn | | PlatformAdmin | Quản trị viên sàn | | OpsStaff | Nhân viên vận hành/kho | | CustomerServiceRep (CSR) | Nhân viên chăm sóc khách hàng | | Product | Sản phẩm do seller đăng bán | | ProductVariant (SKU) | Biến thể/đơn vị tồn kho cụ thể của một sản phẩm (VD: theo size, màu) | | Category | Ngành hàng/danh mục sản phẩm, dùng làm cơ sở cấu hình hoa hồng | | Cart | Giỏ hàng của khách hàng, có thể chứa sản phẩm từ nhiều seller | | CartItem | Một dòng sản phẩm trong giỏ hàng | | Order | Đơn hàng của khách hàng; một Order có thể tách thành nhiều Order con theo seller | | OrderItem | Một dòng sản phẩm trong đơn hàng | | Payment | Giao dịch thanh toán của khách hàng (VNPay/Momo/COD) | | Shipment | Lô hàng giao cho khách, gắn với đơn vị vận chuyển (GHN/GHTK) | | ReturnRequest | Yêu cầu đổi trả hàng của khách hàng | | Dispute | Tranh chấp giữa khách hàng và seller cần CSR/Admin xử lý | | Promotion (Coupon) | Chương trình khuyến mãi/mã giảm giá | | Review | Đánh giá/nhận xét sản phẩm của khách hàng | | Notification | Thông báo gửi cho người dùng (email/SMS) | | CommissionRule | Quy tắc/bảng cấu hình hoa hồng theo ngành hàng | | Payout | Khoản chi trả định kỳ cho seller sau khi trừ hoa hồng | | KYCDocument | Hồ sơ định danh/giấy tờ pháp lý seller nộp để xác minh (KYC) | | LoyaltyAccount | Tài khoản điểm thưởng của khách hàng | | LoyaltyTransaction | Giao dịch tích/đổi điểm thưởng | | MembershipTier | Hạng thành viên (Bạc/Vàng/Kim cương) theo tổng chi tiêu 12 tháng | | Wishlist | Danh sách sản phẩm yêu thích của khách hàng | | Currency | Đơn vị tiền tệ hiển thị (giao dịch chính = VND) | | Language | Ngôn ngữ giao diện (VI/EN/ZH/KO/JA) | ## 1.4 Giả định (Assumptions) Các giả định dưới đây được chốt từ `docs/00-project-brief.md` (mục 5 — Giả định đã chốt), kèm rủi ro tương ứng. Đây là các điều kiện được xem là đúng khi thiết kế các mục tiếp theo; nếu thực tế khác đi, cần rà soát lại thiết kế liên quan. 1. **Nền tảng client MVP = web responsive, mobile app = phase 2.** Rủi ro: nếu phần lớn traffic mục tiêu thực tế đến từ mobile app native, trải nghiệm và tỷ lệ chuyển đổi MVP có thể thấp hơn kỳ vọng; cần bổ sung roadmap mobile sớm hơn nếu dữ liệu thị trường cho thấy nhu cầu cao. 2. **Cổng thanh toán = VNPay + Momo + COD; vận chuyển = GHN + GHTK.** Rủi ro: nếu doanh nghiệp đã có hợp đồng/ưu đãi với đối tác khác, cần thay đổi tích hợp và có thể phát sinh chi phí/thời gian điều chỉnh thiết kế. 3. **Kỳ giữ tiền (payout hold) = 3-7 ngày sau giao hàng thành công.** Rủi ro: nếu chính sách đổi trả thực tế dài hơn (VD. 15-30 ngày cho một số ngành hàng), dòng tiền payout và mô hình đối soát (reconciliation) cần điều chỉnh lại; có thể phát sinh tranh chấp với seller nếu thời gian hold không rõ ràng trong hợp đồng seller. 4. **Tuân thủ pháp lý = áp dụng đầy đủ NĐ52/85 (thông báo website TMĐT), NĐ13/2023 (bảo vệ dữ liệu cá nhân), PCI-DSS scope giảm qua cổng thanh toán bên thứ ba; hoá đơn điện tử cho seller hoãn phase 2.** Rủi ro: nếu doanh nghiệp thực tế cần cấp phép "Sàn giao dịch TMĐT" đầy đủ (không chỉ thông báo) do quy mô/mô hình kinh doanh cụ thể, cần rà soát pháp lý bổ sung trước khi go-live; thiếu hoá đơn điện tử cho seller ở MVP có thể gây khó khăn vận hành kế toán cho seller. 5. **Chương trình loyalty: 1 điểm/10.000đ, 100 điểm = 10.000đ giảm giá, 3 hạng Bạc/Vàng/Kim cương theo chi tiêu 12 tháng gần nhất.** Rủi ro: nếu chiến lược kinh doanh thực tế muốn cơ chế tích/đổi điểm khác (VD. theo ngành hàng, theo chương trình đối tác), cần điều chỉnh mô hình dữ liệu loyalty và luồng tính điểm. 6. **Độ trễ mục tiêu <2s (catalog/search), <3s (checkout); uptime mục tiêu 99.9%.** Rủi ro: nếu SLA hợp đồng với đối tác/khách hàng doanh nghiệp yêu cầu cao hơn (VD. 99.95%+), cần đầu tư thêm cho multi-AZ/multi-region và có thể tăng chi phí hạ tầng đáng kể. 7. **Tech stack không bắt buộc, kiến trúc sư tự đề xuất theo best practice cho quy mô lớn; cloud = AWS; dự án greenfield, không có hệ thống cũ cần tích hợp/migrate; ngân sách/timeline chưa xác định (giả định theo lộ trình MVP tiêu chuẩn ~9-12 tháng).** Rủi ro: nếu ngân sách/timeline thực tế bị giới hạn chặt hơn giả định, phạm vi MVP (đặc biệt các hạng mục mở rộng như 5 ngôn ngữ, loyalty, kiến trúc scale-out ngay từ đầu) có thể cần cắt giảm hoặc chia nhỏ thành nhiều release. 8. **Xác thực/bảo mật: không có SSO doanh nghiệp; Customer dùng email/password + tuỳ chọn Google/Facebook OAuth; Admin bắt buộc MFA, Seller khuyến khích MFA.** Rủi ro: nếu về sau có đối tác B2B lớn yêu cầu tích hợp SSO/IdP riêng, cần bổ sung thiết kế xác thực liên kết (federation) sau này. 9. **UI/Brand: không có brand guideline cố định, dùng design system chuẩn (VD. Material/Ant Design) làm nền tảng; đa ngôn ngữ quản lý qua i18n framework cho 5 ngôn ngữ (VI/EN/ZH/KO/JA).** Rủi ro: nếu doanh nghiệp có bộ nhận diện thương hiệu riêng cần tuân thủ nghiêm ngặt, giai đoạn thiết kế UI cần thời gian điều chỉnh thêm; bản dịch 5 ngôn ngữ cần quy trình quản lý nội dung đa ngôn ngữ (translation workflow) chưa được đặc tả chi tiết. 10. **Vận hành: môi trường Dev/Staging/Production trên AWS; đội ops trực theo ca; hỗ trợ giờ hành chính + escalation 24/7 cho sự cố nghiêm trọng.** Rủi ro: nếu tổ chức chưa có đội ops 24/7 sẵn sàng, cần lên kế hoạch tuyển dụng/thuê ngoài dịch vụ vận hành trước go-live. ## 1.5 Ràng buộc (Constraints) - **Pháp lý:** Phải tuân thủ Nghị định 52/2013 và 85/2021 (thông báo website TMĐT dạng sàn giao dịch với Bộ Công Thương), Nghị định 13/2023 (bảo vệ dữ liệu cá nhân) do hệ thống lưu trữ PII của khách hàng và seller (bao gồm giấy tờ KYC). Phạm vi PCI-DSS được thu hẹp vì không lưu trữ dữ liệu thẻ thanh toán (giao cho VNPay/Momo xử lý). - **Công nghệ/hạ tầng:** Triển khai trên AWS; không có ràng buộc tech stack cụ thể nào khác — kiến trúc sư tự đề xuất theo best practice phù hợp quy mô lớn (mục 3 sẽ quyết định). - **Tích hợp bắt buộc:** Cổng thanh toán VNPay, Momo; đơn vị vận chuyển GHN, GHTK; chuyển khoản ngân hàng cho payout seller. - **Quy mô:** Kiến trúc phải hỗ trợ hàng trăm nghìn SKU trở lên, hàng trăm nghìn đến hàng triệu người dùng đăng ký, cao điểm hàng nghìn đến hàng chục nghìn concurrent users (mùa flash sale) ngay từ thiết kế ban đầu. - **Ngân sách & thời gian:** Chưa được xác định chính thức bởi chủ dự án; giả định theo lộ trình MVP tiêu chuẩn (xem Giả định #7). Cần chủ dự án xác nhận lại trước khi lập kế hoạch triển khai chi tiết. - **Không có hệ thống cũ:** Dự án hoàn toàn mới (greenfield), không có ERP/kho/CRM cũ cần tích hợp hoặc di trú dữ liệu. --- # 2. Phân tích yêu cầu (Requirements Analysis) ## 2.1 Yêu cầu chức năng (Functional Requirements) Mã hoá theo mã **FR-xx**. Priority: **Must** (bắt buộc cho MVP) / **Should** (nên có, có thể lùi nếu thiếu thời gian) / **Could** (nice-to-have, không ảnh hưởng go-live nếu thiếu). | ID | Tên yêu cầu | Mô tả ngắn | Actor liên quan | Priority | |---|---|---|---|---| | FR-01 | Đăng ký & đăng nhập tài khoản khách hàng | Customer tạo tài khoản, đăng nhập bằng email/password | Customer | Must | | FR-02 | Đăng nhập mạng xã hội | Customer đăng nhập qua Google/Facebook OAuth (tuỳ chọn) | Customer | Could | | FR-03 | Quản lý hồ sơ & địa chỉ giao hàng | Customer cập nhật thông tin cá nhân, quản lý nhiều địa chỉ giao hàng | Customer | Must | | FR-04 | Danh mục & tìm kiếm sản phẩm đa người bán | Duyệt catalog, lọc theo ngành hàng/seller/giá, tìm kiếm sản phẩm | Guest, Customer | Must | | FR-05 | Giỏ hàng đa người bán | Thêm sản phẩm từ nhiều seller khác nhau vào cùng một giỏ hàng | Guest, Customer | Must | | FR-06 | Checkout & tách đơn theo seller | Khách đặt hàng; hệ thống tự tách một giỏ hàng đa seller thành các đơn con theo từng seller | Guest, Customer | Must | | FR-07 | Thanh toán | Thanh toán qua VNPay, Momo hoặc COD | Guest, Customer | Must | | FR-08 | Quản lý đơn hàng (khách hàng) | Tạo đơn, theo dõi trạng thái, huỷ đơn (trong điều kiện cho phép) | Customer | Must | | FR-09 | Đổi trả & khiếu nại đơn hàng | Customer gửi yêu cầu đổi trả/khiếu nại cho đơn đã giao | Customer, CSR | Must | | FR-10 | Danh sách yêu thích (Wishlist) | Customer lưu sản phẩm quan tâm để mua sau | Customer | Should | | FR-11 | Đánh giá & nhận xét sản phẩm | Customer viết đánh giá/rating cho sản phẩm đã mua | Customer | Should | | FR-12 | Thông báo đơn hàng | Gửi email/SMS xác nhận đơn hàng, cập nhật trạng thái giao hàng | Customer, Seller | Must | | FR-13 | Khuyến mãi & mã giảm giá | Admin tạo và quản lý chương trình khuyến mãi/coupon; khách áp dụng khi checkout | PlatformAdmin, Customer | Should | | FR-14 | Chương trình loyalty/điểm thưởng | Tích điểm theo giá trị đơn hàng, đổi điểm thành giảm giá, xếp hạng thành viên (Bạc/Vàng/Kim cương) | Customer | Should | | FR-15 | Đa ngôn ngữ giao diện | Hiển thị giao diện theo 5 ngôn ngữ VI/EN/ZH/KO/JA | Guest, Customer, Seller | Should | | FR-16 | Hiển thị đa tiền tệ | Hiển thị giá quy đổi tham khảo sang các tiền tệ khác (giao dịch vẫn bằng VND) | Guest, Customer | Could | | FR-17 | Đăng ký & KYC người bán | Seller tự đăng ký, upload giấy phép kinh doanh/CMND; Admin duyệt thủ công | Seller, PlatformAdmin | Must | | FR-18 | Quản lý sản phẩm & tồn kho (seller) | Seller tự đăng sản phẩm, cập nhật tồn kho, giá bán | Seller | Must | | FR-19 | Quản lý đơn hàng (seller) | Seller xem, xử lý các đơn hàng thuộc gian hàng của mình | Seller | Must | | FR-20 | Dashboard & báo cáo doanh thu (seller) | Seller xem báo cáo doanh thu, hoa hồng, trạng thái payout của mình | Seller | Should | | FR-21 | Cấu hình hoa hồng (commission) theo ngành hàng | Admin cấu hình/chỉnh sửa bảng % hoa hồng theo từng category | PlatformAdmin | Must | | FR-22 | Payout định kỳ cho seller | Tính và chi trả payout hàng tuần qua chuyển khoản ngân hàng, áp dụng kỳ giữ tiền (hold) sau giao hàng thành công | PlatformAdmin, Seller | Must | | FR-23 | Quản trị seller | Admin duyệt/khoá tài khoản seller, giám sát hoạt động seller | PlatformAdmin | Must | | FR-24 | Quản trị catalog toàn sàn | Admin giám sát, can thiệp (ẩn/gỡ) sản phẩm vi phạm trên toàn sàn | PlatformAdmin | Must | | FR-25 | Xử lý tranh chấp & khiếu nại | CSR/Admin xử lý tranh chấp giữa khách hàng và seller (đổi trả, khiếu nại) | CSR, PlatformAdmin | Must | | FR-26 | Xử lý tồn kho & vận chuyển | Ops/Warehouse xử lý đóng gói, cập nhật trạng thái giao hàng, tích hợp đơn vị vận chuyển GHN/GHTK | OpsStaff | Must | | FR-27 | Xác thực đa yếu tố (MFA) | Bắt buộc MFA cho Admin, khuyến khích MFA cho Seller khi đăng nhập | PlatformAdmin, Seller | Should | ## 2.2 Yêu cầu phi chức năng (Non-Functional Requirements) | ID | Nhóm | Yêu cầu | |---|---|---| | NFR-01 | Hiệu năng (Performance) | Thời gian phản hồi trang danh mục/tìm kiếm sản phẩm < 2 giây; hoàn tất checkout < 3 giây, kể cả trong giai đoạn tải đỉnh (flash sale). | | NFR-02 | Khả năng mở rộng (Scalability) | Kiến trúc scale-out ngang ngay từ đầu; hỗ trợ cao điểm hàng nghìn đến hàng chục nghìn concurrent users; sử dụng cache (Redis), CDN, message queue (Kafka/RabbitMQ) để hấp thụ tải đột biến mùa sale. | | NFR-03 | Độ sẵn sàng (Availability) | Mục tiêu uptime 99.9% cho các dịch vụ giao dịch cốt lõi (catalog, checkout, thanh toán). | | NFR-04 | Bảo mật (Security) | Bảo vệ PII của khách hàng và seller (bao gồm giấy tờ KYC); MFA bắt buộc cho Admin, khuyến khích cho Seller; chi tiết mã hoá dữ liệu/OWASP/quản lý khóa sẽ đặc tả ở mục 8 (Thiết kế bảo mật). | | NFR-05 | Tuân thủ pháp lý (Compliance) | Tuân thủ Nghị định 52/2013 và 85/2021 (thông báo website TMĐT sàn giao dịch với Bộ Công Thương); Nghị định 13/2023 (bảo vệ dữ liệu cá nhân); phạm vi PCI-DSS thu hẹp do không lưu trữ dữ liệu thẻ (giao cho VNPay/Momo). | | NFR-06 | Đa ngôn ngữ/địa phương hoá (i18n/l10n) | Hỗ trợ 5 ngôn ngữ giao diện (VI mặc định, EN, ZH, KO, JA); hiển thị đa tiền tệ tham khảo trên nền giao dịch VND. | | NFR-07 | Khả năng bảo trì (Maintainability) | Sử dụng design system chuẩn (VD. Material/Ant Design) làm nền tảng giao diện; kiến trúc module hoá để các nhóm (catalog, order, seller, payment) phát triển độc lập (chi tiết ở mục 3). | | NFR-08 | Vận hành (Operability) | Ba môi trường Dev/Staging/Production tách biệt trên AWS; hỗ trợ vận hành giờ hành chính kèm escalation 24/7 cho sự cố nghiêm trọng ảnh hưởng giao dịch/doanh thu. | > Ghi chú: Các con số hiệu năng/uptime ở NFR-01, NFR-03 là **giả định mặc định đã chốt** trong brief (mục 5, giả định #6), chưa được xác nhận bằng SLA hợp đồng thực tế — xem `openQuestions`. ## 2.3 Sơ đồ Use Case ```mermaid flowchart LR Guest((Guest)) Customer((Customer)) Seller((Seller)) Admin((Platform Admin)) Ops((Ops/Warehouse)) CSR((CSR)) UC1[Duyệt & tìm kiếm sản phẩm] UC2[Giỏ hàng đa seller] UC3[Checkout & thanh toán] UC4[Quản lý đơn hàng cá nhân] UC5[Đổi trả / khiếu nại] UC6[Wishlist] UC7[Đánh giá sản phẩm] UC8[Đăng ký / đăng nhập] UC9[Điểm thưởng & hạng thành viên] UC10[Đăng ký & KYC seller] UC11[Quản lý sản phẩm & tồn kho] UC12[Quản lý đơn hàng seller] UC13[Xem báo cáo doanh thu/payout] UC14[Duyệt / khoá seller] UC15[Cấu hình hoa hồng] UC16[Quản trị catalog toàn sàn] UC17[Cấu hình khuyến mãi] UC18[Xử lý payout] UC19[Xử lý tranh chấp/khiếu nại] UC20[Xử lý tồn kho & đóng gói] UC21[Cập nhật trạng thái giao hàng] Guest --> UC1 Guest --> UC2 Guest --> UC3 Guest --> UC8 Customer --> UC1 Customer --> UC2 Customer --> UC3 Customer --> UC4 Customer --> UC5 Customer --> UC6 Customer --> UC7 Customer --> UC8 Customer --> UC9 Seller --> UC10 Seller --> UC11 Seller --> UC12 Seller --> UC13 Admin --> UC14 Admin --> UC15 Admin --> UC16 Admin --> UC17 Admin --> UC18 Admin --> UC19 Ops --> UC20 Ops --> UC21 CSR --> UC19 CSR --> UC5 ``` ## 2.4 Ma trận truy vết yêu cầu (Traceability Matrix) > Cột "Mục thiết kế liên quan" và "Test Case" đã được điền trong bản ráp SAD này dựa trên tổng hợp từ mục 3.5, 4.4, 5.4, 6.5 và 9.2.6 (không sửa file gốc `docs/sections/02-phan-tich-yeu-cau.md`). | Requirement ID | Mô tả | Mục thiết kế liên quan | Test Case | |---|---|---|---| | FR-01 | Đăng ký & đăng nhập tài khoản khách hàng | 3.1/3.5 Identity & Access Service; 4.1.3 `/v1/auth/register,login,refresh,logout`; 5.2.1 `user_account`; 6.1.6 | TC-01, TC-02 | | FR-02 | Đăng nhập mạng xã hội | 3.1/3.5 Identity & Access Service; 4.1.3 `/v1/auth/oauth/{provider}/callback`; 5.2.1 `oauth_identity`; 6.1.6 | TC-03 | | FR-03 | Quản lý hồ sơ & địa chỉ giao hàng | 3.5 Identity & Access Service; 4.1.3 `/v1/customers/me`, `/v1/customers/me/addresses`; 5.2.1 `customer_profile`, `customer_address` | TC-04 | | FR-04 | Danh mục & tìm kiếm sản phẩm đa người bán | 3.1/3.5 Catalog & Inventory Service + Search subsystem; 4.1.4 `/v1/categories,products,search/products`; 5.2.2 `category`, `product`, `product_variant` | TC-05 | | FR-05 | Giỏ hàng đa người bán | 3.5 Cart & Order Service; 4.1.5 `/v1/cart`, `/v1/cart/items`; 5.2.3 `cart`, `cart_item`; 6.1.1, BR-02 | TC-06 | | FR-06 | Checkout & tách đơn theo seller | 3.5 Cart & Order Service; 4.1.5 `/v1/checkout`; 5.2.3 `order`, `order_seller`, `order_item`; 6.1.1, BR-01, State 6.3.1 | TC-07, TC-08 | | FR-07 | Thanh toán | 3.5 Payment Service; 4.1.6 `/v1/payments`, webhooks VNPay/Momo; 5.2.4 `payment`, `payment_reconciliation_log`; 6.1.1, State 6.3.2 | TC-09, TC-10 | | FR-08 | Quản lý đơn hàng (khách hàng) | 3.5 Cart & Order Service; 4.1.5 `/v1/orders`, `/v1/orders/{orderId}/cancel`; 5.2.3 `order`, `order_status_history`; State 6.3.1, BR-10 | TC-11, TC-11b | | FR-09 | Đổi trả & khiếu nại đơn hàng | 3.5 Cart & Order Service; 4.1.5 `/v1/orders/{orderId}/return-requests`; 5.2.3 `return_request`, `dispute`; 6.1.3, State 6.3.4, BR-14 | TC-12 | | FR-10 | Danh sách yêu thích (Wishlist) | 3.5 Catalog & Inventory Service; 4.1.4 `/v1/customers/me/wishlist`; 5.2.2 `wishlist_item` | TC-13 | | FR-11 | Đánh giá & nhận xét sản phẩm | 3.5 Review Service; 4.1.10 `/v1/products/{productId}/reviews`; 5.2.8 `review`; BR-11 | TC-14, TC-14b | | FR-12 | Thông báo đơn hàng | 3.5 Notification Service; 4.1.11 `/v1/customers/me/notifications`, notification-preferences; 5.2.9 `notification_log`; 6.1.1, 6.1.2 | TC-15 | | FR-13 | Khuyến mãi & mã giảm giá | 3.5 Promotion & Loyalty Service; 4.1.9 `/v1/admin/promotions`, `/v1/cart/apply-coupon`; 5.2.7 `promotion`, `promotion_usage`; 6.1.1, BR-09 | TC-16, TC-16b | | FR-14 | Chương trình loyalty/điểm thưởng | 3.5 Promotion & Loyalty Service; 4.1.9 `/v1/customers/me/loyalty*`; 5.2.7 `loyalty_account`, `loyalty_transaction`, `membership_tier`; 6.1.2, BR-06/07/08 | TC-17, TC-17b | | FR-15 | Đa ngôn ngữ giao diện | 3.1 Cross-cutting i18n; 4.1.2 `/v1/config/languages` + `Accept-Language`; 5.2.2 `language`, `product_i18n`, `category_i18n` | TC-18 | | FR-16 | Hiển thị đa tiền tệ | 3.1 Cross-cutting currency; 4.1.2 `/v1/config/currencies` + `X-Display-Currency`; 5.2.2 `currency`, `exchange_rate` | TC-19 | | FR-17 | Đăng ký & KYC người bán | 3.5 Seller Management Service; 4.1.7 `/v1/sellers/register`, `kyc-documents`, `kyc-review`; 5.2.5 `seller`, `kyc_document`; 6.1.5, State 6.3.3, BR-13 | TC-20, TC-20b | | FR-18 | Quản lý sản phẩm & tồn kho (seller) | 3.5 Catalog & Inventory Service; 4.1.4 `/v1/seller/products*`; 5.2.2 `product`, `product_variant`, `inventory_stock`; 6.1.1, BR-02 | TC-21 | | FR-19 | Quản lý đơn hàng (seller) | 3.5 Cart & Order Service; 4.1.5 `/v1/seller/orders*`; 5.2.3 `order_seller`, `order_item`; 6.1.2, State 6.3.1 | TC-22 | | FR-20 | Dashboard & báo cáo doanh thu (seller) | 3.5 Seller Management + Commission & Payout; 4.1.7 `/v1/seller/dashboard/summary`; 5.2.6 `commission_transaction`, `payout`; 6.1.4, Class Diagram 6.2.2 | TC-23 | | FR-21 | Cấu hình hoa hồng (commission) theo ngành hàng | 3.5 Commission & Payout Service; 4.1.8 `/v1/admin/commission-rules*`; 5.2.6 `commission_rule`; 6.1.4, BR-03 | TC-24 | | FR-22 | Payout định kỳ cho seller | 3.5 Commission & Payout Service; 4.1.8 `/v1/seller/payouts`, `/v1/admin/payouts*`; 5.2.5/5.2.6 `payout`, `payout_hold`, `seller_bank_account`; 6.1.4, State 6.3.6, BR-04/05 | TC-25, TC-25b | | FR-23 | Quản trị seller | 3.5 Seller Management Service; 4.1.7 `/v1/admin/sellers*`; 5.2.5 `seller` (cột `status`); State 6.3.3 | TC-26 | | FR-24 | Quản trị catalog toàn sàn | 3.5 Catalog & Inventory Service; 4.1.4 `/v1/admin/products*`; 5.2.2 `product` (cột `status`) | TC-27 | | FR-25 | Xử lý tranh chấp & khiếu nại | 3.5 Cart & Order Service (Dispute handling); 4.1.5 `/v1/admin/disputes*`; 5.2.3/5.2.6 `dispute`, `payout_hold`; 6.1.3, State 6.3.5, BR-14 | TC-28, TC-28b | | FR-26 | Xử lý tồn kho & vận chuyển | 3.5 Shipping & Fulfillment Service; 4.1.12 `/v1/ops/*`, `/v1/shipments/*`, webhooks GHN/GHTK; 5.2.10 `shipment`, `shipment_event`; 6.1.2, BR-15 | TC-29, TC-29b | | FR-27 | Xác thực đa yếu tố (MFA) | 3.5 Identity & Access Service; 4.1.3 `/v1/auth/mfa/challenge,enroll`; 5.2.1 `user_account`(mfa_enabled/failed_login_count/locked_until), `mfa_device`; 6.1.6, BR-12 | TC-30, TC-30b | | NFR-01 | Hiệu năng (latency catalog/search, checkout) | 3.1/3.2 quyết định kiến trúc (cache Redis, Search subsystem tách rời, MQ đệm checkout) | 9.1.4 (Performance Testing — kịch bản NFR-01) | | NFR-02 | Khả năng mở rộng (scale-out, cache, CDN, MQ) | 3.1/3.2 (scale-out theo domain, MQ Kafka/MSK, ElastiCache Redis, CDN CloudFront) | 9.1.4 (Load test flash sale — kịch bản NFR-02) | | NFR-03 | Độ sẵn sàng (uptime 99.9%) | 3.2/3.3 (Multi-AZ, auto-scaling, RTO/RPO 5.3.2) | 9.1.4 (Chaos/failover test), 9.5.2 | | NFR-04 | Bảo mật (PII, MFA, mã hoá) | 5.5 (cột [PII]/[Payment]); 8.1, 8.2 | 9.1.5 (Security Testing) | | NFR-05 | Tuân thủ pháp lý (NĐ52/85, NĐ13/2023, PCI-DSS) | 5.3.6 (retention); 8.4 (Compliance) | 9.1.5, 9.4.1 | | NFR-06 | Đa ngôn ngữ/đa tiền tệ | 3.1 (cross-cutting i18n/currency); 4.1.1/4.1.2; 5.2.2; 7.0 | TC-18, TC-19 | | NFR-07 | Khả năng bảo trì | 3.1 (module hoá theo domain/bounded-context) | 9.1.1 (Unit test theo service) | | NFR-08 | Vận hành (môi trường, on-call) | 3.3 (Dev/Staging/Production); 9.4 (Monitoring), 9.5 (Rollback/DR) | 9.4.1, 9.5 | --- # 3. Thiết kế kiến trúc (System Architecture Design) ## 3.1 Mô hình kiến trúc ### Lựa chọn: Kiến trúc hướng dịch vụ theo bounded-context (Coarse-grained Service-Oriented / "modular microservices"), kết hợp Event-Driven cho các luồng bất đồng bộ Hệ thống được chia thành khoảng 10 service nghiệp vụ độc lập (mỗi service sở hữu dữ liệu riêng — database-per-service), giao tiếp đồng bộ qua REST cho các thao tác request/response và bất đồng bộ qua message broker (Kafka/Amazon MSK, hoặc SQS/SNS cho các luồng đơn giản hơn) cho các quy trình chuỗi nhiều bước (đặt hàng → thanh toán → trừ tồn kho → tính hoa hồng → payout → thông báo). Đây **không phải** microservices chi tiết theo từng entity (tránh over-engineering), mà là mô hình "modular monolith được service hoá theo domain lớn" — mỗi service tương ứng một bounded context nghiệp vụ rõ ràng, đủ nhỏ để một nhóm 3-6 kỹ sư sở hữu, đủ lớn để tránh chi phí vận hành/network overhead của hàng chục nano-service. ### Danh sách service và đối chiếu với FR/NFR | Service | Trách nhiệm chính | FR phục vụ | NFR/ràng buộc liên quan | |---|---|---|---| | **Identity & Access Service** | Đăng ký/đăng nhập email-password, OAuth Google/Facebook, MFA cho Admin/Seller, phát hành JWT/session | FR-01, FR-02, FR-27 | NFR-04 (bảo mật), tách riêng để cô lập rủi ro credential/PII | | **Catalog & Inventory Service** | Quản lý Product/SKU/Category, tồn kho do seller cập nhật, wishlist, quản trị catalog toàn sàn (admin ẩn/gỡ sản phẩm vi phạm) | FR-04 (dữ liệu gốc), FR-10, FR-18, FR-24 | NFR-01, NFR-06 (đa ngôn ngữ nội dung sản phẩm), NFR-07 | | **Search subsystem** (thành phần đọc, không phải service độc lập có team riêng) | Chỉ mục tìm kiếm/filter sản phẩm (OpenSearch), đồng bộ qua event từ Catalog | FR-04 (tìm kiếm) | NFR-01 (<2s), NFR-02 (cache/CDN, chịu tải đỉnh flash sale) | | **Cart & Order Service** | Giỏ hàng đa seller, checkout, tách đơn theo seller, vòng đời đơn hàng, tiếp nhận yêu cầu đổi trả/khiếu nại, xem đơn theo seller | FR-05, FR-06, FR-08, FR-09, FR-19 | NFR-01 (checkout <3s), NFR-02 (queue hấp thụ đột biến đặt hàng flash sale) | | **Payment Service** | Tích hợp VNPay/Momo, xử lý luồng COD, đối soát giao dịch, không lưu dữ liệu thẻ | FR-07 | NFR-04, NFR-05 (giảm phạm vi PCI-DSS bằng cách cô lập service này và không lưu card data) | | **Seller Management Service** | Onboarding & KYC (upload/duyệt giấy tờ), quản trị seller (khoá/duyệt), dashboard báo cáo doanh thu | FR-17, FR-20, FR-23 | NFR-04 (PII giấy tờ KYC lưu S3 mã hoá riêng biệt), NFR-05 | | **Commission & Payout Service** | Cấu hình bảng hoa hồng theo ngành hàng, tính hoa hồng, lịch payout hàng tuần, kỳ giữ tiền (hold), tạo lệnh chuyển khoản ngân hàng | FR-21, FR-22 | NFR-05 (tuân thủ tài chính), tách riêng khỏi Seller Management vì đây là luồng tài chính nhạy cảm cần audit trail riêng | | **Promotion & Loyalty Service** | Cấu hình mã giảm giá/khuyến mãi, tích/đổi điểm thưởng, xếp hạng thành viên | FR-13, FR-14 | NFR-07 | | **Review Service** | Đánh giá/nhận xét sản phẩm sau khi mua | FR-11 | NFR-01 | | **Notification Service** | Gửi email/SMS xác nhận đơn hàng, cập nhật trạng thái giao hàng, là consumer của các domain event | FR-12 | NFR-02 (qua queue, không chặn luồng chính), NFR-06 (nội dung đa ngôn ngữ) | | **Shipping & Fulfillment Service** | Điều phối đóng gói/tồn kho vận hành, tích hợp GHN/GHTK, cập nhật trạng thái giao hàng, hỗ trợ Ops/Warehouse | FR-26 | NFR-01, NFR-08 | | **Dispute/CSR handling** | Xử lý tranh chấp — triển khai như module trong Cart & Order Service với quyền truy cập mở rộng cho CSR/Admin (không tách service riêng vì khối lượng nghiệp vụ chưa đủ lớn để cần đội riêng) | FR-25 | NFR-04 (kiểm soát quyền truy cập CSR ở mức đọc + ghi có giới hạn) | Ghi chú: FR-15 (đa ngôn ngữ) và FR-16 (đa tiền tệ hiển thị) không phải là service riêng mà là **năng lực xuyên suốt (cross-cutting)** được triển khai qua i18n framework ở tầng frontend/BFF và trường ngôn ngữ/tỷ giá lưu ở Catalog & Pricing config — phục vụ NFR-06. ### Đối chiếu quyết định kiến trúc với NFR/ràng buộc - **NFR-02 (scale-out, cache, CDN, MQ ngay từ đầu) + quy mô "large"** → đây là lý do chính không chọn Monolith đơn khối: cần scale độc lập Catalog/Search (đọc nhiều) và Cart/Checkout (ghi nhiều, đột biến flash sale) mà không kéo theo toàn bộ hệ thống. Message broker (Kafka/MSK) tách rời các bước xử lý sau khi đặt hàng thành công (tính hoa hồng, payout, notification, loyalty) để không làm chậm phản hồi checkout. - **NFR-01 (checkout <3s, catalog/search <2s ngay cả tải đỉnh)** → Search tách thành subsystem riêng dùng OpenSearch + cache Redis, không query trực tiếp DB giao dịch; Cart & Order Service dùng cache cho giỏ hàng (Redis) và queue để đệm đơn hàng khi tải đỉnh thay vì xử lý đồng bộ toàn bộ chuỗi nghiệp vụ. - **NFR-05/PCI-DSS scope giảm** → Payment Service là biên cô lập duy nhất giao tiếp với VNPay/Momo; không service nào khác lưu trữ thông tin thẻ; giảm phạm vi kiểm toán PCI-DSS xuống 1 service thay vì toàn hệ thống. - **NFR-04 (PII, giấy tờ KYC)** → Seller Management Service lưu file KYC trong S3 bucket riêng có mã hoá + access policy giới hạn (chỉ Seller Management Service và Admin), tách khỏi Identity Service để giảm bề mặt tấn công. - **FR-06 checkout tách đơn theo seller + FR-21/22 commission/payout** → tách Commission & Payout thành service riêng để có audit trail tài chính độc lập, tránh commission logic bị lẫn với logic vận hành seller (onboarding/KYC) vốn thay đổi thường xuyên hơn. - **NFR-07 (maintainability, module hoá theo nhóm)** → ranh giới service theo domain cho phép các đội catalog/order/seller/payment phát triển và release độc lập, khớp với ghi chú NFR-07 trong mục 2. - **NFR-08 (vận hành, escalation 24/7 cho sự cố nghiêm trọng)** → các service giao dịch cốt lõi (Cart & Order, Payment, Identity) được ưu tiên chạy multi-AZ với auto-scaling và health check chặt hơn các service ít quan trọng hơn (Review, Promotion). ### Trade-off và phương án bị loại | Phương án | Lý do cân nhắc | Lý do loại/không chọn hoàn toàn | |---|---|---| | **Monolith truyền thống (1 codebase, 1 DB)** | Đơn giản triển khai, phù hợp đội nhỏ, chi phí vận hành thấp | Loại — không đáp ứng NFR-02 (yêu cầu scale-out ngang từ đầu) và không cho phép scale độc lập Catalog/Search khỏi Checkout khi tải đỉnh flash sale; rủi ro một lỗi nhỏ ở module ít quan trọng (VD Review) có thể ảnh hưởng uptime toàn hệ thống (mâu thuẫn NFR-03 99.9%) | | **Microservices chi tiết (chia theo từng entity, 20-30+ service)** | Scale/độc lập tối đa theo lý thuyết | Loại — độ phức tạp vận hành (distributed tracing, service mesh, quản lý hàng chục pipeline CI/CD) vượt quá nhu cầu thực tế của MVP; ngân sách/timeline chưa xác định (giả định #7, mục 5 brief) → rủi ro chậm tiến độ; chọn mức "coarse-grained" cân bằng hơn | | **Modular Monolith (module hoá trong 1 process, chưa tách service)** | Giữ đơn giản vận hành, vẫn module hoá code theo domain | Cân nhắc làm bước đệm hợp lý cho giai đoạn đầu, nhưng không chọn làm kiến trúc mục tiêu vì NFR-02 yêu cầu rõ scale-out ngang và MQ ngay từ đầu — nếu chọn modular monolith sẽ cần re-architect sớm khi traffic tăng, tốn kém hơn là tách service hợp lý từ đầu cho các domain đã biết rõ tải cao (Catalog/Search, Checkout) | | **Event-Driven thuần tuý (toàn bộ giao tiếp qua event, không REST)** | Độ tách rời (decoupling) cao nhất | Loại một phần — các luồng cần phản hồi tức thời cho người dùng (đăng nhập, xem catalog, checkout, thanh toán) phù hợp hơn với REST đồng bộ; event chỉ dùng cho luồng nghiệp vụ chuỗi phía sau (post-order processing) để tránh độ trễ cảm nhận (perceived latency) không cần thiết | ## 3.2 Sơ đồ thành phần & triển khai (Component & Deployment Diagram) ```mermaid flowchart TB subgraph Clients WebCustomer["Web Storefront (Customer/Guest)\nResponsive SPA"] SellerPortal["Seller Portal"] AdminPortal["Admin/Ops/CSR Backoffice"] end CDN["CloudFront CDN\n(static assets, ảnh sản phẩm)"] WAF["AWS WAF"] ALB["Application Load Balancer"] APIGW["API Gateway / BFF layer\n(routing, auth check, rate limit)"] subgraph CoreServices["Core Services (ECS Fargate / EKS, auto-scaling)"] IDSvc["Identity & Access Service"] CatalogSvc["Catalog & Inventory Service"] SearchSvc["Search subsystem\n(OpenSearch)"] CartOrderSvc["Cart & Order Service\n(+ Dispute handling)"] PaymentSvc["Payment Service"] SellerSvc["Seller Management Service\n(KYC/onboarding)"] CommissionSvc["Commission & Payout Service"] PromoLoyaltySvc["Promotion & Loyalty Service"] ReviewSvc["Review Service"] NotifySvc["Notification Service"] ShippingSvc["Shipping & Fulfillment Service"] end Redis[("ElastiCache Redis\ncache, session, giỏ hàng")] RDS[("RDS PostgreSQL Multi-AZ\ndatabase-per-service")] S3[("S3\nảnh sản phẩm, KYC docs, invoice")] MQ["Message Broker\n(Amazon MSK/Kafka hoặc SQS/SNS)"] subgraph External["Dịch vụ bên ngoài"] VNPay["VNPay"] Momo["Momo"] GHN["GHN"] GHTK["GHTK"] EmailSMS["Email/SMS Provider\n(SES/SNS hoặc SendGrid/Twilio)"] Bank["Ngân hàng\n(chuyển khoản payout)"] OAuth["Google/Facebook OAuth"] end WebCustomer --> CDN WebCustomer --> WAF SellerPortal --> WAF AdminPortal --> WAF WAF --> ALB --> APIGW APIGW --> IDSvc APIGW --> CatalogSvc APIGW --> SearchSvc APIGW --> CartOrderSvc APIGW --> PaymentSvc APIGW --> SellerSvc APIGW --> CommissionSvc APIGW --> PromoLoyaltySvc APIGW --> ReviewSvc APIGW --> ShippingSvc IDSvc --> RDS IDSvc --> OAuth CatalogSvc --> RDS CatalogSvc --> S3 CatalogSvc -.event.-> MQ MQ -.sync index.-> SearchSvc SearchSvc --> Redis CartOrderSvc --> RDS CartOrderSvc --> Redis CartOrderSvc -.event.-> MQ PaymentSvc --> RDS PaymentSvc --> VNPay PaymentSvc --> Momo PaymentSvc -.event.-> MQ SellerSvc --> RDS SellerSvc --> S3 MQ -.consume.-> CommissionSvc CommissionSvc --> RDS CommissionSvc --> Bank MQ -.consume.-> PromoLoyaltySvc PromoLoyaltySvc --> RDS ReviewSvc --> RDS MQ -.consume.-> NotifySvc NotifySvc --> EmailSMS ShippingSvc --> RDS ShippingSvc --> GHN ShippingSvc --> GHTK MQ -.consume.-> ShippingSvc ``` > **(Finding tồn đọng — xem §0.4a):** Audit & Compliance Service (bổ sung ở mục 5 v3, §5.2.11) chưa được thể hiện trong sơ đồ trên; ACL/mã hoá theo topic của Message Broker cũng chưa được đặc tả chi tiết — ghi nhận để xử lý ở vòng cập nhật mục 3 tiếp theo, không chặn việc ráp bản SAD này. Ghi chú kiến trúc triển khai: - Mỗi service chạy container hoá trên ECS Fargate (hoặc EKS nếu cần kiểm soát sâu hơn), auto-scaling group riêng theo tải thực tế của từng domain (Catalog/Search và Cart/Order được cấp cấu hình auto-scale nhanh hơn cho mùa flash sale). - Database theo mô hình "database-per-service" trên RDS PostgreSQL Multi-AZ; không service nào truy cập trực tiếp DB của service khác — chỉ qua API hoặc event. - Redis (ElastiCache) dùng chung cho cache catalog/search, lưu session, và giỏ hàng (giỏ hàng cần độ trễ thấp, có thể chấp nhận mất dữ liệu tạm thời thấp). - Message broker là xương sống cho các luồng bất đồng bộ: OrderPlaced, PaymentConfirmed, OrderDelivered (khởi động đếm hold), CommissionCalculated, PayoutScheduled, InventoryReserved, ReviewEligible, LoyaltyPointsEarned, NotificationRequested. - API Gateway/BFF đảm nhiệm xác thực token (JWT), rate limiting, và có thể tách thành 3 BFF nhỏ (Customer BFF, Seller BFF, Admin BFF) để tối ưu payload riêng cho từng loại client — chi tiết endpoint sẽ do `api-designer` đặc tả ở mục 4. - Thiết kế chi tiết bảo mật (mã hoá at-rest/in-transit, KMS, WAF rule cụ thể) thuộc mục 8; ở đây chỉ thể hiện vị trí kiến trúc của các control đó (WAF, S3 mã hoá, cô lập Payment Service). ## 3.3 Môi trường triển khai (Environments) | Môi trường | Kích cỡ hạ tầng | Dữ liệu | Feature flag | Quyền truy cập | |---|---|---|---|---| | **Dev** | 1 instance/service, cấu hình nhỏ nhất (VD Fargate 0.25-0.5 vCPU), RDS single-AZ, không cần OpenSearch cluster nhiều node | Dữ liệu giả lập (seed/synthetic), không chứa PII/KYC thật | Tất cả feature flag mặc định bật để dev/test tính năng mới | Đội kỹ sư phát triển; không giới hạn IP | | **Staging** | Cấu hình gần giống Production nhưng scale nhỏ hơn (1-2 instance/service), RDS Multi-AZ nhỏ, OpenSearch cluster nhỏ | Dữ liệu đã ẩn danh hoá (anonymized) từ Production hoặc dữ liệu giả lập quy mô lớn hơn Dev để test hiệu năng; **không** đưa PII/KYC thật vào Staging (tuân thủ NĐ13/2023) | Feature flag phản ánh trạng thái sắp release (dùng để UAT/regression trước khi lên Production) | Đội QA, Product Owner, stakeholder UAT; giới hạn qua VPN/IP allowlist | | **Production** | Auto-scaling theo tải thực tế, RDS Multi-AZ + read replica cho các bảng đọc nhiều (Catalog), OpenSearch cluster đa node, CDN toàn cầu qua CloudFront | Dữ liệu thật (PII khách hàng/seller, giao dịch thanh toán, KYC) — mã hoá at-rest, phân quyền truy cập nghiêm ngặt | Feature flag kiểm soát rollout dần (canary/phần trăm người dùng) cho tính năng rủi ro cao (VD thay đổi luồng thanh toán/commission) | Chỉ đội vận hành (Ops) và Admin được cấp quyền truy cập hạ tầng qua IAM role có audit log; không truy cập DB Production trực tiếp trừ trường hợp khẩn cấp có phê duyệt | Ghi chú: cả 3 môi trường đều nằm trên AWS theo giả định #7/#10 (mục 1.4). Production yêu cầu hỗ trợ vận hành giờ hành chính kèm escalation 24/7 cho sự cố nghiêm trọng (NFR-08) — chi tiết on-call/runbook thuộc mục 9 (Kế hoạch vận hành & Kiểm thử). ## 3.4 Tích hợp bên thứ ba | Dịch vụ | Giao thức | Timeout/Retry | Fallback khi lỗi | Trách nhiệm | |---|---|---|---|---| | **VNPay** | REST/HTTPS (redirect + callback/IPN xác nhận giao dịch) | Timeout gọi API: 10s; retry callback xử lý idempotent tối đa 3 lần với backoff (do VNPay có thể gọi lại IPN) | Nếu callback không nhận được sau ngưỡng thời gian, đơn hàng chuyển trạng thái "chờ xác nhận thanh toán" và có job đối soát định kỳ (reconciliation) gọi API tra cứu giao dịch; khách hàng được thông báo trạng thái tạm thời | Payment Service | | **Momo** | REST/HTTPS (tương tự VNPay: redirect + IPN) | Timeout 10s; retry callback idempotent tối đa 3 lần | Tương tự VNPay — job đối soát định kỳ tra cứu trạng thái giao dịch qua API Momo | Payment Service | | **COD (thu tiền mặt khi giao)** | Không phải tích hợp API bên ngoài — là luồng nghiệp vụ nội bộ, xác nhận thu tiền do đơn vị vận chuyển/Ops cập nhật thủ công hoặc qua webhook GHN/GHTK | Không áp dụng timeout API; SLA xác nhận thu tiền phụ thuộc đơn vị vận chuyển | Nếu đơn vị vận chuyển không cập nhật trạng thái thu tiền đúng hạn, CSR có quy trình đối soát thủ công định kỳ | Cart & Order Service (trạng thái đơn) + Shipping & Fulfillment Service | | **GHN** | REST/HTTPS (tạo vận đơn, tra cứu trạng thái, webhook cập nhật) | Timeout 8s; retry tạo vận đơn tối đa 3 lần với backoff; webhook xử lý idempotent | Nếu GHN không phản hồi, hệ thống chuyển sang thử tạo vận đơn qua GHTK (nếu seller/khu vực hỗ trợ) hoặc đưa vào hàng đợi retry thủ công cho Ops xử lý | Shipping & Fulfillment Service | | **GHTK** | REST/HTTPS (tương tự GHN) | Timeout 8s; retry tối đa 3 lần | Tương tự GHN — fallback chéo hoặc hàng đợi retry thủ công | Shipping & Fulfillment Service | | **Email/SMS Provider** (đề xuất: AWS SES cho email + AWS SNS/hoặc nhà cung cấp nội địa cho SMS — *nhà cung cấp cụ thể chưa chốt, xem giả định*) | REST/HTTPS hoặc SDK, gửi bất đồng bộ qua queue | Timeout 5s; retry tối đa 5 lần với exponential backoff (do đây là thông báo không chặn luồng chính) | Nếu gửi thất bại sau tất cả lần retry, ghi log lỗi và đưa vào dead-letter queue để CSR/Ops xử lý thủ công (gọi lại/gửi lại); không chặn hoặc rollback đơn hàng | Notification Service | | **Chuyển khoản ngân hàng (payout)** | Batch file (theo chuẩn ngân hàng, VD NAPAS) hoặc API ngân hàng đối tác — *chưa chốt ngân hàng cụ thể, xem giả định* | Không áp dụng timeout theo nghĩa API tức thời; SLA xử lý batch theo chu kỳ hàng tuần; retry submit file nếu bị từ chối do lỗi định dạng | Nếu batch payout bị từ chối/thất bại, Commission & Payout Service giữ trạng thái "payout thất bại", cảnh báo Admin, và seller được thông báo chậm trễ; không tự động thử lại chuyển tiền để tránh double-payout — cần xác nhận thủ công | Commission & Payout Service + Admin (giám sát) | | **Google/Facebook OAuth** | OAuth 2.0 / OpenID Connect (redirect flow) | Timeout xác thực 10s | Nếu OAuth provider lỗi, Customer vẫn có thể đăng nhập bằng email/password (không phụ thuộc hoàn toàn vào OAuth) | Identity & Access Service | ## 3.5 Tóm tắt truy vết Bảng dưới bổ sung cho Ma trận truy vết ở mục 2.4 (cột "Mục thiết kế liên quan" — phần kiến trúc): | Requirement ID | Service/thành phần chịu trách nhiệm chính | |---|---| | FR-01, FR-02, FR-27 | Identity & Access Service | | FR-03 | Identity & Access Service (hồ sơ) + Catalog & Inventory Service (địa chỉ giao hàng liên kết Order) | | FR-04, FR-10, FR-18, FR-24 | Catalog & Inventory Service + Search subsystem | | FR-05, FR-06, FR-08, FR-09, FR-19, FR-25 | Cart & Order Service | | FR-07 | Payment Service | | FR-11 | Review Service | | FR-12 | Notification Service | | FR-13, FR-14 | Promotion & Loyalty Service | | FR-15, FR-16 | Cross-cutting i18n/currency (BFF/frontend + Catalog config) | | FR-17, FR-20, FR-23 | Seller Management Service | | FR-21, FR-22 | Commission & Payout Service | | FR-26 | Shipping & Fulfillment Service | `api-designer` sẽ dùng bảng này làm cơ sở để nhóm endpoint theo service; `data-modeler` dùng ranh giới service ở mục 3.1 làm cơ sở database-per-service khi thiết kế ERD (mục 5). --- # 4. Thiết kế API (API Design) > Phạm vi & style: theo mục 3.1, hệ thống dùng kiến trúc "modular microservices" theo bounded-context, giao tiếp đồng bộ giữa client và backend qua **REST/HTTPS** (JSON), giao tiếp nội bộ giữa service qua event (Kafka/MSK) — không thuộc phạm vi đặc tả API công khai ở mục này. Không có yêu cầu Partner/Public API cho bên thứ ba trong phạm vi MVP (brief không đề cập đối tác tích hợp ngoài VNPay/Momo/GHN/GHTK/OAuth, và các bên này được hệ thống gọi ra — không phải bên ngoài gọi vào), nên không thiết kế cơ chế API key cấp cho đối tác/public developer portal; toàn bộ endpoint dưới đây phục vụ 3 nhóm client nội bộ: **Web Storefront (Guest/Customer)**, **Seller Portal**, **Admin/Ops/CSR Backoffice**, đi qua **API Gateway/BFF** (Customer BFF, Seller BFF, Admin BFF — theo mục 3.2). > > Tên entity trong request/response tham chiếu đúng Glossary mục 1.3 (`Product`, `ProductVariant`, `Category`, `Cart`, `CartItem`, `Order`, `OrderItem`, `Payment`, `Shipment`, `ReturnRequest`, `Dispute`, `Promotion`, `Review`, `Notification`, `CommissionRule`, `Payout`, `KYCDocument`, `LoyaltyAccount`, `LoyaltyTransaction`, `MembershipTier`, `Wishlist`, `Currency`, `Language`). Không thiết kế bảng CSDL ở mục này (xem mục 5). ## 4.1 Đặc tả API ### 4.1.1 Quy ước chung - **Base path:** `https://api./v1/...` — tất cả endpoint dưới đây ngầm định tiền tố `/v1` (xem 4.3 Versioning). - **Định dạng:** JSON (`Content-Type: application/json`); upload tài liệu KYC dùng `multipart/form-data`. - **Đa ngôn ngữ (FR-15):** mọi endpoint hỗ trợ header `Accept-Language: vi-VN|en-US|zh-CN|ko-KR|ja-JP` (mặc định `vi-VN`); các trường nội dung đa ngôn ngữ (tên sản phẩm, mô tả, nội dung thông báo) trả về theo ngôn ngữ yêu cầu, fallback về `vi-VN` nếu thiếu bản dịch. Đây là năng lực cross-cutting áp dụng toàn bộ API, không phải endpoint/service riêng (khớp ghi chú mục 3.1). - **Đa tiền tệ (FR-16):** mọi response có trường giá đều trả về `priceVnd` (giá giao dịch thật, VND) kèm `displayPrices[]` (mảng quy đổi tham khảo theo `Currency`) khi client gửi header `X-Display-Currency`; **không** có endpoint giao dịch bằng ngoại tệ (khớp brief — chỉ hiển thị quy đổi tham khảo). - **Khách vãng lai (Guest):** các endpoint Cart/Checkout hỗ trợ định danh qua `X-Guest-Session-Id` thay cho JWT, cho phép FR-05/FR-06 hoạt động không cần đăng nhập. Giá trị `X-Guest-Session-Id` **phải** được sinh phía server bằng CSPRNG (cryptographically secure random) với entropy **tối thiểu 128-bit** (VD UUIDv4 sinh bằng CSPRNG, hoặc chuỗi random ≥16 byte mã hoá base64url); truyền cho client qua cookie `HttpOnly; Secure; SameSite=Lax` (không dùng `localStorage` — tránh lộ giá trị qua XSS), TTL tối đa 30 ngày không hoạt động. Toàn bộ endpoint **ghi** dữ liệu Cart cho Guest (`POST/PATCH/DELETE /v1/cart/items`, `POST /v1/cart/apply-coupon`, `POST /v1/checkout` khi không có JWT) áp dụng **rate limit riêng theo IP nguồn** (VD 30 req/phút/IP) ngoài giới hạn theo session, để chống lạm dụng khi chưa có định danh JWT (chi tiết rate limiting tại 4.2). - **Quy tắc ownership (chống IDOR):** với mọi endpoint có tham số định danh tài nguyên trong path (VD `{orderId}`, `{shipmentId}`, `{returnRequestId}`, `{paymentId}`, ...) mà tài nguyên gắn với một `Customer`/`Seller` cụ thể, tầng Gateway/BFF hoặc service xử lý **bắt buộc** đối chiếu tài nguyên đó thuộc về `sub`/`customerId`/`sellerId` trong JWT của caller trước khi trả dữ liệu — **trừ khi** caller có scope `admin:*`/`ops:*`/`csr:*` được thiết kế truy cập toàn cục cho nhóm tài nguyên đó (ghi rõ theo từng endpoint tại 4.1.5–4.1.8, 4.1.12). Không khớp ownership → `403 ERR_FORBIDDEN_OWNERSHIP` (phân biệt với `403 ERR_FORBIDDEN_SCOPE` khi thiếu quyền/scope, xem 4.1.13). - **Phân trang:** query `?page=&pageSize=` (mặc định `pageSize=20`, tối đa `100`), response bọc trong `{ "data": [...], "pagination": { "page", "pageSize", "totalItems" } }`. - **Idempotency:** các endpoint ghi tiền (checkout, payment, payout, đổi điểm loyalty) yêu cầu header `Idempotency-Key` để tránh xử lý trùng khi client retry. ### 4.1.2 Cross-cutting config (FR-15, FR-16) | Method | Path | Mô tả | FR | Response tóm tắt | |---|---|---|---|---| | GET | `/v1/config/languages` | Danh sách ngôn ngữ hỗ trợ và ngôn ngữ mặc định | FR-15 | `[{code:"vi",name:"Tiếng Việt",isDefault:true}, ...]` | | GET | `/v1/config/currencies` | Danh sách tiền tệ hiển thị tham khảo và tỷ giá quy đổi hiện hành (nguồn: cấu hình tại Catalog & Inventory Service) | FR-16 | `[{code:"USD",rateToVnd:25400,updatedAt}, ...]` | ### 4.1.3 Identity & Access Service (FR-01, FR-02, FR-03, FR-27) | Method | Path | Mô tả | FR | Auth | |---|---|---|---|---| | POST | `/v1/auth/register` | Customer đăng ký tài khoản bằng email/password | FR-01 | Không | | POST | `/v1/auth/login` | Đăng nhập email/password; trả `mfaRequired:true` nếu tài khoản Admin/Seller đã bật MFA | FR-01, FR-27 | Không | | POST | `/v1/auth/mfa/challenge` | Xác minh mã OTP (TOTP/SMS) bước 2 sau `login`, trả access/refresh token khi thành công | FR-27 | Mã thách thức tạm (challenge token) | | POST | `/v1/auth/mfa/enroll` | Bật MFA cho tài khoản Seller/Admin đang đăng nhập | FR-27 | Bearer JWT | | POST | `/v1/auth/oauth/{provider}/callback` | Xử lý callback OAuth2 (`provider=google\|facebook`); xác thực tham số `state` (chống CSRF) khớp giá trị đã phát hành khi khởi tạo luồng OAuth — từ chối (`400 ERR_OAUTH_STATE_INVALID`) nếu thiếu/không khớp; nếu email do provider trả về đã có tài khoản Customer đăng ký sẵn bằng email/password, **không tự động liên kết (no auto-merge)** — trả `409 ERR_ACCOUNT_LINK_REQUIRED` và yêu cầu xác minh sở hữu email (gửi mã xác minh tới email đã đăng ký) trước khi cho phép liên kết tài khoản OAuth; nếu email chưa tồn tại, tạo tài khoản Customer mới liên kết provider | FR-02 | Không (redirect flow); tham số `state` bắt buộc | | POST | `/v1/auth/refresh` | Cấp access token mới từ refresh token | FR-01 | Refresh token | | POST | `/v1/auth/logout` | Thu hồi refresh token hiện tại | FR-01 | Bearer JWT | | GET | `/v1/customers/me` | Xem hồ sơ cá nhân Customer đang đăng nhập | FR-03 | Bearer JWT (scope `customer:profile:read`) | | PATCH | `/v1/customers/me` | Cập nhật hồ sơ (tên, số điện thoại, ngôn ngữ ưu tiên) | FR-03 | Bearer JWT (scope `customer:profile:write`) | | GET | `/v1/customers/me/addresses` | Danh sách địa chỉ giao hàng | FR-03 | Bearer JWT | | POST | `/v1/customers/me/addresses` | Thêm địa chỉ giao hàng mới | FR-03 | Bearer JWT | | PUT | `/v1/customers/me/addresses/{addressId}` | Cập nhật địa chỉ | FR-03 | Bearer JWT | | DELETE | `/v1/customers/me/addresses/{addressId}` | Xoá địa chỉ | FR-03 | Bearer JWT | **Ví dụ — POST `/v1/auth/login`** ```json // Request { "email": "customer@example.com", "password": "********" } // Response 200 (không MFA) { "accessToken": "eyJ...", "refreshToken": "eyJ...", "expiresIn": 3600 } // Response 200 (tài khoản Admin/Seller đã bật MFA) { "mfaRequired": true, "mfaChallengeToken": "chal_abc123", "mfaMethod": "TOTP" } ``` ### 4.1.4 Catalog & Inventory Service + Search subsystem (FR-04, FR-10, FR-18, FR-24) | Method | Path | Mô tả | FR | Auth | |---|---|---|---|---| | GET | `/v1/categories` | Cây danh mục ngành hàng (`Category`) | FR-04 | Không | | GET | `/v1/products` | Duyệt/lọc `Product` (theo `Category`, seller, khoảng giá, rating) — đọc qua Search subsystem (OpenSearch) | FR-04 | Không | | GET | `/v1/search/products?q=` | Tìm kiếm full-text sản phẩm | FR-04 | Không | | GET | `/v1/products/{productId}` | Chi tiết `Product` kèm danh sách `ProductVariant` | FR-04 | Không | | GET | `/v1/customers/me/wishlist` | Danh sách `Wishlist` của Customer | FR-10 | Bearer JWT | | POST | `/v1/customers/me/wishlist` | Thêm `Product` vào `Wishlist` | FR-10 | Bearer JWT | | DELETE | `/v1/customers/me/wishlist/{productId}` | Bỏ khỏi `Wishlist` | FR-10 | Bearer JWT | | GET | `/v1/seller/products` | Seller xem danh sách `Product` của gian hàng mình | FR-18 | Bearer JWT (scope `seller:catalog:write`) | | POST | `/v1/seller/products` | Seller tạo `Product` mới (kèm `ProductVariant`) | FR-18 | Bearer JWT (scope `seller:catalog:write`) | | PUT | `/v1/seller/products/{productId}` | Cập nhật thông tin `Product` | FR-18 | Bearer JWT (scope `seller:catalog:write`) | | PATCH | `/v1/seller/products/{productId}/variants/{variantId}/inventory` | Cập nhật tồn kho/giá `ProductVariant` | FR-18 | Bearer JWT (scope `seller:catalog:write`) | | GET | `/v1/admin/products` | Admin tra cứu toàn bộ `Product` trên sàn (giám sát) | FR-24 | Bearer JWT (scope `admin:catalog:read`) | | PATCH | `/v1/admin/products/{productId}/status` | Admin ẩn/gỡ `Product` vi phạm (`status: hidden\|removed`) | FR-24 | Bearer JWT (scope `admin:catalog:write`) | ### 4.1.5 Cart & Order Service — bao gồm Dispute handling (FR-05, FR-06, FR-08, FR-09, FR-19, FR-25) | Method | Path | Mô tả | FR | Auth | |---|---|---|---|---| | GET | `/v1/cart` | Xem `Cart` hiện tại (đa seller) | FR-05 | Bearer JWT hoặc `X-Guest-Session-Id` | | POST | `/v1/cart/items` | Thêm `CartItem` (sản phẩm của bất kỳ seller nào) vào `Cart` | FR-05 | Bearer JWT hoặc `X-Guest-Session-Id` | | PATCH | `/v1/cart/items/{cartItemId}` | Cập nhật số lượng `CartItem` | FR-05 | Bearer JWT hoặc `X-Guest-Session-Id` | | DELETE | `/v1/cart/items/{cartItemId}` | Xoá `CartItem` | FR-05 | Bearer JWT hoặc `X-Guest-Session-Id` | | POST | `/v1/cart/apply-coupon` | Áp mã `Promotion` (coupon) vào `Cart` trước khi checkout | FR-13 | Bearer JWT hoặc `X-Guest-Session-Id` | | POST | `/v1/checkout` | Tạo `Order` từ `Cart`; hệ thống tự tách thành các `Order` con theo từng seller | FR-06 | Bearer JWT hoặc `X-Guest-Session-Id`; header `Idempotency-Key` bắt buộc | | GET | `/v1/orders` | Danh sách `Order` của Customer đang đăng nhập | FR-08 | Bearer JWT | | GET | `/v1/orders/{orderId}` | Chi tiết `Order` (bao gồm `OrderItem`, `Shipment`, `Payment`) | FR-08 | Bearer JWT (chủ đơn — `customerId` trong JWT phải khớp `Order.customerId`; không khớp → `403 ERR_FORBIDDEN_OWNERSHIP`) | | POST | `/v1/orders/{orderId}/cancel` | Huỷ `Order` (chỉ khi trạng thái cho phép) | FR-08 | Bearer JWT (chủ đơn — cùng quy tắc ownership như GET `/v1/orders/{orderId}`) | | POST | `/v1/orders/{orderId}/return-requests` | Tạo `ReturnRequest` cho `Order` đã giao | FR-09 | Bearer JWT (chủ đơn — cùng quy tắc ownership như GET `/v1/orders/{orderId}`) | | GET | `/v1/orders/{orderId}/return-requests/{returnRequestId}` | Xem trạng thái `ReturnRequest` | FR-09 | Bearer JWT (chủ đơn — ownership như trên) **hoặc** CSR/Admin (scope `csr:disputes:read`/`admin:*`, truy cập toàn cục theo thiết kế, không áp dụng kiểm tra ownership) | | GET | `/v1/seller/orders` | Seller xem danh sách `Order` con thuộc gian hàng mình | FR-19 | Bearer JWT (scope `seller:orders:read`; kết quả tự động lọc theo `sellerId` trong JWT, không nhận `sellerId` qua query param — chống IDOR) | | PATCH | `/v1/seller/orders/{orderId}/status` | Seller cập nhật trạng thái xử lý `Order` (xác nhận, chuẩn bị hàng) | FR-19 | Bearer JWT (scope `seller:orders:write`; `sellerId` trong JWT phải khớp seller sở hữu `Order`/`OrderItem` tương ứng `orderId`; không khớp → `403 ERR_FORBIDDEN_OWNERSHIP`) | | GET | `/v1/admin/disputes` | CSR/Admin xem danh sách `Dispute` cần xử lý (phát sinh từ `ReturnRequest`/khiếu nại) | FR-25 | Bearer JWT (scope `csr:disputes:read` hoặc `admin:disputes:read`; truy cập toàn cục theo thiết kế — không áp dụng kiểm tra ownership vì CSR/Admin xử lý tranh chấp toàn sàn) | | GET | `/v1/admin/disputes/{disputeId}` | Chi tiết `Dispute` kèm lịch sử `Order` liên quan | FR-25 | Bearer JWT (scope `csr:disputes:read`; truy cập toàn cục theo thiết kế, không áp dụng kiểm tra ownership) | | PATCH | `/v1/admin/disputes/{disputeId}` | CSR/Admin cập nhật quyết định xử lý `Dispute` (hoàn tiền/từ chối/chuyển escalation); khi quyết định là hoàn tiền, hệ thống loại vĩnh viễn khoản hoa hồng liên quan khỏi payout kỳ tới (chuyển `payout_hold.release_status` sang trạng thái kết thúc `reversed`, xem mục 5.2.6/5 và mục 6) | FR-25 | Bearer JWT (scope `csr:disputes:write` hoặc `admin:disputes:write`; truy cập toàn cục theo thiết kế, không áp dụng kiểm tra ownership) | **Ví dụ — POST `/v1/checkout`** ```json // Request { "cartId": "cart_123", "shippingAddressId": "addr_456", "paymentMethod": "VNPAY", "couponCode": "SALE50" } // Response 201 { "parentOrderId": "order_parent_789", "orders": [ { "orderId": "order_001", "sellerId": "seller_11", "totalAmountVnd": 350000, "status": "PENDING_PAYMENT" }, { "orderId": "order_002", "sellerId": "seller_22", "totalAmountVnd": 120000, "status": "PENDING_PAYMENT" } ], "paymentRedirectUrl": "https://sandbox.vnpayment.vn/..." } ``` ### 4.1.6 Payment Service (FR-07) | Method | Path | Mô tả | FR | Auth | |---|---|---|---|---| | POST | `/v1/payments` | Khởi tạo `Payment` cho một `Order` (VNPay/Momo redirect URL, hoặc xác nhận COD) | FR-07 | Bearer JWT hoặc `X-Guest-Session-Id`; `Idempotency-Key` bắt buộc | | GET | `/v1/payments/{paymentId}` | Tra cứu trạng thái `Payment` | FR-07 | Bearer JWT (chủ đơn — `customerId` khớp `Order.customerId` của Payment; không khớp → `403 ERR_FORBIDDEN_OWNERSHIP`) | | POST | `/v1/payments/webhooks/vnpay` | Callback/IPN xác nhận giao dịch từ VNPay (nội bộ, không public docs) | FR-07 | Xác thực chữ ký VNPay (checksum), không dùng JWT; chống replay — xem ghi chú bên dưới | | POST | `/v1/payments/webhooks/momo` | Callback/IPN xác nhận giao dịch từ Momo | FR-07 | Xác thực chữ ký Momo; chống replay — xem ghi chú bên dưới | > **Chống replay cho toàn bộ webhook bên thứ ba** (`vnpay`, `momo`, `ghn`, `ghtk` — xem thêm 4.1.12): ngoài xác thực chữ ký/token của bên gửi, mỗi webhook **bắt buộc**: (1) kiểm tra trường timestamp có trong payload gốc của gateway — **từ chối** (`400 ERR_VALIDATION`, không xử lý) nếu lệch quá **5 phút** so với giờ hệ thống nhận; (2) áp dụng **idempotency theo `gatewayTransactionRef`** (mã giao dịch/mã vận đơn phía gateway, lưu kèm trạng thái đã xử lý) — nếu đã ghi nhận cùng `gatewayTransactionRef` trước đó, trả `200 OK` mà **không** xử lý lại nghiệp vụ (không tạo side-effect lần 2), tránh trùng khi gateway tự động retry hợp lệ. Hai lớp này kết hợp chống tấn công phát lại (replay) payload cũ hợp lệ chữ ký lẫn duplicate delivery thông thường. ### 4.1.7 Seller Management Service (FR-17, FR-20, FR-23) | Method | Path | Mô tả | FR | Auth | |---|---|---|---|---| | POST | `/v1/sellers/register` | Seller tự đăng ký gian hàng | FR-17 | Không (tạo tài khoản mới) hoặc Bearer JWT nếu nâng cấp từ Customer | | POST | `/v1/sellers/{sellerId}/kyc-documents` | Upload `KYCDocument` (giấy phép kinh doanh/CMND), `multipart/form-data` | FR-17 | Bearer JWT (chủ seller) | | GET | `/v1/sellers/{sellerId}/kyc-status` | Seller xem trạng thái duyệt KYC | FR-17 | Bearer JWT (chủ seller) | | GET | `/v1/admin/sellers` | Admin danh sách seller (lọc theo trạng thái KYC/hoạt động) | FR-23 | Bearer JWT (scope `admin:sellers:read`) | | PATCH | `/v1/admin/sellers/{sellerId}/kyc-review` | Admin duyệt/từ chối `KYCDocument` (`status: approved\|rejected`, `reason`) | FR-17 | Bearer JWT (scope `admin:sellers:write`) | | PATCH | `/v1/admin/sellers/{sellerId}/status` | Admin khoá/mở khoá tài khoản Seller | FR-23 | Bearer JWT (scope `admin:sellers:write`) | | GET | `/v1/seller/dashboard/summary` | Seller xem tóm tắt doanh thu, hoa hồng, trạng thái `Payout` | FR-20 | Bearer JWT (scope `seller:reports:read`) | > **(Finding tồn đọng F11 — xem §0.4b):** chưa có endpoint `GET /v1/admin/sellers/{sellerId}/kyc-documents/{documentId}/view-url` để Admin lấy pre-signed URL xem `KYCDocument` (sequence 6.1.5 đã mô tả cơ chế). Ghi nhận để bổ sung ở vòng cập nhật tiếp theo của mục 4, không chặn bản ráp SAD này. ### 4.1.8 Commission & Payout Service (FR-21, FR-22) | Method | Path | Mô tả | FR | Auth | |---|---|---|---|---| | GET | `/v1/admin/commission-rules` | Danh sách `CommissionRule` theo `Category`, kèm `holdDays` (số ngày giữ tiền payout riêng cho ngành hàng — BR-04) | FR-21 | Bearer JWT (scope `admin:commission:read`) | | PUT | `/v1/admin/commission-rules/{categoryId}` | Admin cấu hình/chỉnh % hoa hồng và `holdDays` cho một `Category` | FR-21 | Bearer JWT (scope `admin:commission:write`) | | GET | `/v1/seller/payouts` | Seller xem lịch sử/trạng thái `Payout` của mình | FR-22 | Bearer JWT (scope `seller:payouts:read`; kết quả tự động lọc theo `sellerId` trong JWT, không nhận `sellerId` qua query param — chống IDOR) | | GET | `/v1/admin/payouts` | Admin giám sát toàn bộ `Payout` theo kỳ (hàng tuần) | FR-22 | Bearer JWT (scope `admin:payouts:read`; truy cập toàn cục theo thiết kế) | | POST | `/v1/admin/payouts/{payoutId}/retry` | Admin yêu cầu thử lại `Payout` thất bại (không tự động, theo mục 3.4) | FR-22 | Bearer JWT (scope `admin:payouts:write`; truy cập toàn cục theo thiết kế) | **Ví dụ — GET `/v1/admin/commission-rules`** ```json // Response 200 { "data": [ { "categoryId": "cat_electronics", "commissionPercent": 8.5, "holdDays": 7, "effectiveFrom": "2026-10-01", "updatedBy": "admin_01" }, { "categoryId": "cat_fashion", "commissionPercent": 10, "holdDays": null, "effectiveFrom": "2026-09-01", "updatedBy": "admin_02" } ], "pagination": { "page": 1, "pageSize": 20, "totalItems": 2 } } ``` **Ví dụ — PUT `/v1/admin/commission-rules/{categoryId}`** ```json // Request { "commissionPercent": 8.5, "holdDays": 7, "effectiveFrom": "2026-10-01" } // Response 200 { "categoryId": "cat_electronics", "commissionPercent": 8.5, "holdDays": 7, "effectiveFrom": "2026-10-01", "updatedBy": "admin_01" } ``` > `holdDays` (integer, nullable, khuyến nghị **3-7**): số ngày giữ tiền payout riêng cho `Category` này sau khi `Order` giao hàng thành công, theo BR-04. Nếu `null`/không truyền, hệ thống áp dụng mặc định toàn sàn **5 ngày** (khớp `commission_rule.hold_days` mục 5.2.6). Validation: nếu có giá trị, `422 ERR_BUSINESS_RULE` khi ngoài khoảng 3-7 (cảnh báo, vẫn cho phép admin override có xác nhận theo BR-04, ghi log audit). ### 4.1.9 Promotion & Loyalty Service (FR-13, FR-14) | Method | Path | Mô tả | FR | Auth | |---|---|---|---|---| | GET | `/v1/admin/promotions` | Danh sách `Promotion` (coupon) | FR-13 | Bearer JWT (scope `admin:promotions:read`) | | POST | `/v1/admin/promotions` | Tạo `Promotion` mới | FR-13 | Bearer JWT (scope `admin:promotions:write`) | | PUT | `/v1/admin/promotions/{promotionId}` | Cập nhật `Promotion` | FR-13 | Bearer JWT (scope `admin:promotions:write`) | | GET | `/v1/customers/me/loyalty` | Xem `LoyaltyAccount` (điểm hiện có, `MembershipTier`) | FR-14 | Bearer JWT | | GET | `/v1/customers/me/loyalty/transactions` | Lịch sử `LoyaltyTransaction` (tích/đổi điểm) | FR-14 | Bearer JWT | | POST | `/v1/customers/me/loyalty/redeem` | Đổi điểm thưởng thành giảm giá áp cho `Cart`/`Order` | FR-14 | Bearer JWT; header `Idempotency-Key` bắt buộc | ### 4.1.10 Review Service (FR-11) | Method | Path | Mô tả | FR | Auth | |---|---|---|---|---| | GET | `/v1/products/{productId}/reviews` | Danh sách `Review` của một `Product` | FR-11 | Không | | POST | `/v1/products/{productId}/reviews` | Customer tạo `Review` (chỉ khi đã mua và `Order` đã giao) | FR-11 | Bearer JWT | ### 4.1.11 Notification Service (FR-12) | Method | Path | Mô tả | FR | Auth | |---|---|---|---|---| | GET | `/v1/customers/me/notifications` | Lịch sử `Notification` đã gửi cho Customer (in-app) | FR-12 | Bearer JWT | | GET | `/v1/customers/me/notification-preferences` | Xem tuỳ chọn nhận thông báo (email/SMS) | FR-12 | Bearer JWT | | PATCH | `/v1/customers/me/notification-preferences` | Cập nhật tuỳ chọn nhận thông báo | FR-12 | Bearer JWT | | GET | `/v1/admin/notifications/{notificationId}` | Ops/Admin tra cứu trạng thái gửi `Notification` (phục vụ xử lý sự cố dead-letter, theo mục 3.4) | FR-12 | Bearer JWT (scope `admin:notifications:read`) | > Lưu ý: luồng gửi chính của `Notification` (email/SMS xác nhận đơn hàng, cập nhật giao hàng) được kích hoạt bất đồng bộ qua event nội bộ (`OrderPlaced`, `PaymentConfirmed`, ...) theo mục 3.2, không qua REST API công khai; các endpoint trên chỉ phục vụ tra cứu/tuỳ chọn. ### 4.1.12 Shipping & Fulfillment Service (FR-26) | Method | Path | Mô tả | FR | Auth | |---|---|---|---|---| | GET | `/v1/ops/orders/{orderId}/fulfillment` | Ops xem thông tin đóng gói/tồn kho cần xử lý cho `Order` | FR-26 | Bearer JWT (scope `ops:fulfillment:read`; giới hạn theo đơn hàng/gian hàng được phân công, xem 4.2) | | PATCH | `/v1/ops/orders/{orderId}/fulfillment` | Ops cập nhật trạng thái đóng gói | FR-26 | Bearer JWT (scope `ops:fulfillment:write`; giới hạn theo đơn hàng/gian hàng được phân công, xem 4.2) | | POST | `/v1/ops/shipments` | Tạo `Shipment` (gọi API tạo vận đơn GHN/GHTK) | FR-26 | Bearer JWT (scope `ops:fulfillment:write`) | | GET | `/v1/shipments/{shipmentId}/tracking` | Customer/Seller/Ops/Admin tra cứu trạng thái vận chuyển `Shipment` | FR-26 | Bearer JWT (chủ đơn hàng liên quan — `customerId` khớp `Order.customerId` của `Order` gắn với `Shipment`; **hoặc** `sellerId` khớp seller của `order_seller`/`OrderItem` liên quan đến `Shipment`; **hoặc** scope `ops:fulfillment:read`/`admin:*` truy cập toàn cục; không khớp → `403 ERR_FORBIDDEN_OWNERSHIP`) | | POST | `/v1/webhooks/ghn` | Webhook cập nhật trạng thái từ GHN | FR-26 | Xác thực chữ ký/token GHN; chống replay — xem ghi chú tại 4.1.6 (áp dụng đồng thời) | | POST | `/v1/webhooks/ghtk` | Webhook cập nhật trạng thái từ GHTK | FR-26 | Xác thực chữ ký/token GHTK; chống replay — xem ghi chú tại 4.1.6 (áp dụng đồng thời) | ### 4.1.13 Mã lỗi chuẩn hoá Định dạng lỗi thống nhất toàn hệ thống (mọi service qua API Gateway): ```json { "error": { "code": "ERR_VALIDATION", "message": "Trường 'quantity' phải lớn hơn 0", "details": [ { "field": "quantity", "reason": "must_be_positive" } ] }, "traceId": "req_9f8a7b6c" } ``` | HTTP Status | Mã lỗi nội bộ | Ý nghĩa | Áp dụng ví dụ | |---|---|---|---| | 400 | `ERR_VALIDATION` | Dữ liệu đầu vào không hợp lệ | Thiếu trường bắt buộc, sai định dạng | | 400 | `ERR_OAUTH_STATE_INVALID` | Tham số `state` của callback OAuth thiếu hoặc không khớp giá trị đã phát hành (nghi CSRF) | Callback `/v1/auth/oauth/{provider}/callback` giả mạo/không có `state` hợp lệ | | 401 | `ERR_AUTH_REQUIRED` | Thiếu token xác thực | Gọi endpoint yêu cầu JWT mà không có header | | 401 | `ERR_AUTH_INVALID_TOKEN` | Token hết hạn/không hợp lệ | Access token expired | | 401 | `ERR_MFA_REQUIRED` | Cần hoàn tất bước MFA | Login Admin/Seller đã bật MFA nhưng chưa xác minh OTP | | 403 | `ERR_FORBIDDEN_SCOPE` | Token hợp lệ nhưng thiếu quyền/scope | Seller gọi endpoint `admin:*` | | 403 | `ERR_FORBIDDEN_OWNERSHIP` | Token hợp lệ, đủ scope, nhưng tài nguyên không thuộc về `customerId`/`sellerId` của caller (IDOR) | Customer A gọi `GET /v1/shipments/{shipmentId}/tracking` của đơn hàng thuộc Customer B | | 404 | `ERR_NOT_FOUND` | Tài nguyên không tồn tại | `productId` không tồn tại | | 409 | `ERR_CONFLICT` | Xung đột trạng thái/dữ liệu | Trùng email khi đăng ký, tồn kho không đủ khi checkout | | 409 | `ERR_ACCOUNT_LINK_REQUIRED` | Email trả về từ OAuth trùng tài khoản email/password đã có, cần xác minh sở hữu trước khi liên kết | Đăng nhập Google với email đã đăng ký thủ công trước đó | | 422 | `ERR_BUSINESS_RULE` | Vi phạm quy tắc nghiệp vụ | Huỷ đơn khi trạng thái không cho phép, coupon hết hạn, `holdDays` ngoài khoảng khuyến nghị 3-7 | | 429 | `ERR_RATE_LIMITED` | Vượt giới hạn tần suất gọi | Bot gọi liên tục `/checkout` mùa flash sale | | 502 | `ERR_UPSTREAM_UNAVAILABLE` | Dịch vụ bên thứ ba không phản hồi | VNPay/Momo/GHN/GHTK timeout (xem mục 3.4) | | 503 | `ERR_SERVICE_UNAVAILABLE` | Service nội bộ tạm thời quá tải/bảo trì | Circuit breaker mở khi downstream lỗi | | 500 | `ERR_INTERNAL` | Lỗi hệ thống không xác định | Exception chưa được xử lý | > **(Finding tồn đọng F12 — xem §0.4b):** chưa có mã lỗi `423 ERR_ACCOUNT_LOCKED` cho trường hợp tài khoản bị khoá tạm do vượt ngưỡng đăng nhập sai (chính sách đã chốt ở mục 8 §8.1.1a). **(Finding tồn đọng F13):** chưa có endpoint `GET /v1/admin/audit-logs` để đọc `audit_log` (mục 5.2.11/8.2.5b). Cả hai ghi nhận để bổ sung ở vòng cập nhật tiếp theo. ## 4.2 Xác thực & phân quyền API - **Cơ chế:** OAuth2-style **JWT Bearer token** (access token TTL ngắn ~15-60 phút + refresh token TTL dài ~7-30 ngày), phát hành bởi **Identity & Access Service**, xác thực tại tầng **API Gateway/BFF** trước khi route tới service nội bộ (theo mục 3.2). OAuth2 Authorization Code flow áp dụng riêng cho luồng Google/Facebook social login (FR-02) — tham số `state` bắt buộc để chống CSRF và trường hợp trùng email với tài khoản email/password xử lý theo quy tắc "không auto-merge" tại 4.1.3; không dùng API Key cấp cho đối tác vì không có Public/Partner API trong phạm vi MVP. - **Guest:** không cần token cho endpoint duyệt/tìm kiếm sản phẩm; Cart/Checkout dùng `X-Guest-Session-Id` (định danh ẩn danh tạm thời sinh bằng CSPRNG ≥128-bit, cookie `HttpOnly/Secure/SameSite=Lax`, TTL theo phiên — chi tiết tại 4.1.1) thay cho JWT để hỗ trợ guest checkout (FR-05, FR-06) mà không lộ endpoint ghi dữ liệu nhạy cảm cho người chưa xác thực. - **Ownership (chống IDOR):** ngoài kiểm tra scope, mọi endpoint đọc/ghi theo ID tài nguyên gắn với một Customer/Seller cụ thể đều kiểm tra khớp `customerId`/`sellerId` trong JWT (quy tắc chi tiết và danh sách endpoint áp dụng tại 4.1.1 và các bảng 4.1.5–4.1.8, 4.1.12); vi phạm trả `403 ERR_FORBIDDEN_OWNERSHIP`. - **MFA (FR-27):** bắt buộc với scope `admin:*` (chặn hoàn toàn nếu chưa hoàn tất `mfa/challenge`); khuyến khích (không chặn) với scope `seller:*` — access token phát hành cho Seller chưa bật MFA vẫn hợp lệ nhưng hệ thống nhắc bật qua Seller Portal. Đây là kiểm soát ở tầng API; cơ chế MFA chi tiết (TOTP/SMS provider, chính sách khoá tài khoản) thuộc mục 8. - **Scope/permission theo nhóm người dùng** (ánh xạ 1-1 với nhóm actor mục 1.2): | Nhóm người dùng | Scope tiêu biểu | Ghi chú | |---|---|---| | Guest | (không token) | Chỉ endpoint public + `X-Guest-Session-Id` cho Cart/Checkout | | Customer | `customer:profile:read/write`, `customer:orders:read`, `customer:loyalty:read` | Chỉ truy cập dữ liệu của chính mình (kiểm tra `sub` claim khớp `customerId` tài nguyên — xem quy tắc ownership 4.1.1) | | Seller | `seller:catalog:write`, `seller:orders:read/write`, `seller:reports:read`, `seller:payouts:read` | Chỉ truy cập dữ liệu gian hàng của chính mình (kiểm tra `sellerId` claim — xem quy tắc ownership 4.1.1) | | PlatformAdmin | `admin:*` (catalog, sellers, commission, payouts, promotions, disputes, notifications) | Toàn quyền theo mục 1.2; bắt buộc MFA; các nhóm tài nguyên toàn cục (disputes, payouts giám sát) không áp dụng kiểm tra ownership theo thiết kế | | OpsStaff | `ops:fulfillment:read/write` | Giới hạn theo đơn hàng/gian hàng được phân công (kiểm tra assignment, chi tiết RBAC ở mục 8) | | CSR | `csr:disputes:read/write`, `customer:orders:read` (read-only hỗ trợ tra cứu) | Không có quyền `write` lên cấu hình hệ thống; truy cập `Dispute` toàn cục theo thiết kế (không áp dụng ownership) | - **Rate limiting (theo NFR-01, NFR-02):** áp dụng tại API Gateway, theo cấp độ: - Endpoint đọc nhiều (catalog/search — FR-04): giới hạn rộng (VD 300 req/phút/IP), có cache CDN/Redis phía sau nên hiếm khi chạm ngưỡng. - Endpoint ghi nhạy cảm/độ trễ thấp bắt buộc (checkout, payment — FR-06, FR-07): giới hạn chặt hơn theo user/session (VD 20 req/phút) kèm cơ chế hàng đợi (queue) hấp thụ đột biến khi flash sale thay vì từ chối cứng, khớp NFR-02. - Endpoint ghi Cart cho Guest (`X-Guest-Session-Id`, chưa có JWT — VD `POST/PATCH/DELETE /v1/cart/items`, `POST /v1/cart/apply-coupon`): giới hạn bổ sung **theo IP nguồn** (VD 30 req/phút/IP), song song với giới hạn theo session, để chống tạo hàng loạt guest session/bot khi chưa có định danh JWT (xem 4.1.1). - Endpoint auth (`/auth/login`, `/auth/register`): giới hạn theo IP + captcha/backoff sau N lần thất bại để chống brute-force (bổ sung ở mục 8). - Endpoint Admin/Ops/Seller: giới hạn lỏng hơn nhưng đi kèm kiểm soát truy cập mạng (VPN/IP allowlist cho Admin theo mục 3.3), không public internet trực tiếp với Admin Backoffice. - Vượt ngưỡng trả `429 ERR_RATE_LIMITED` kèm header `Retry-After`. ## 4.3 Quản lý phiên bản API (Versioning) - **Chiến lược:** version hoá theo **path prefix** (`/v1/...`), áp dụng thống nhất tại API Gateway cho toàn bộ service — phù hợp phong cách REST đã chọn ở mục 3.1 và dễ kiểm soát khi từng service phát triển độc lập (mỗi service có thể tăng version nội bộ khác nhịp, nhưng Gateway expose version hợp nhất cho client Web Storefront/Seller Portal/Admin Backoffice). - **Không áp dụng** header-based versioning hoặc GraphQL schema versioning — không cần thiết vì chỉ phục vụ client nội bộ do chính đội dự án kiểm soát release (không có bên thứ ba tiêu thụ API theo hợp đồng SLA riêng). - **Chính sách deprecation:** khi phát hành `/v2` cho một nhóm endpoint, `/v1` tương ứng được giữ tối thiểu **6 tháng** kèm header `Deprecation: true` và `Sunset: ` trong response; thông báo trước cho đội frontend/Seller Portal qua changelog nội bộ ít nhất 1 sprint trước khi khoá `/v1`. Breaking change (đổi cấu trúc response, xoá trường bắt buộc) luôn đi kèm version mới, không sửa trực tiếp trên version đang chạy production. - **Không áp dụng — Partner/Public API versioning phức tạp** (API catalog công khai, hợp đồng SLA theo version cho đối tác bên ngoài): brief không xác nhận có đối tác tích hợp API công khai nào ngoài các dịch vụ hệ thống chủ động gọi ra (VNPay/Momo/GHN/GHTK/OAuth), nên không cần cổng thông tin nhà phát triển (developer portal), API key marketplace, hay chính sách billing theo version. ## 4.4 Truy vết yêu cầu bổ sung cho mục 2.4 | Requirement ID | Endpoint/nhóm endpoint chính | |---|---| | FR-01 | `/v1/auth/register`, `/v1/auth/login`, `/v1/auth/refresh`, `/v1/auth/logout` | | FR-02 | `/v1/auth/oauth/{provider}/callback` | | FR-03 | `/v1/customers/me`, `/v1/customers/me/addresses` | | FR-04 | `/v1/categories`, `/v1/products`, `/v1/search/products` | | FR-05 | `/v1/cart`, `/v1/cart/items` | | FR-06 | `/v1/checkout` | | FR-07 | `/v1/payments`, `/v1/payments/webhooks/{vnpay,momo}` | | FR-08 | `/v1/orders`, `/v1/orders/{orderId}/cancel` | | FR-09 | `/v1/orders/{orderId}/return-requests` | | FR-10 | `/v1/customers/me/wishlist` | | FR-11 | `/v1/products/{productId}/reviews` | | FR-12 | `/v1/customers/me/notifications`, `/v1/customers/me/notification-preferences` | | FR-13 | `/v1/admin/promotions`, `/v1/cart/apply-coupon` | | FR-14 | `/v1/customers/me/loyalty`, `/v1/customers/me/loyalty/transactions`, `/v1/customers/me/loyalty/redeem` | | FR-15 | `/v1/config/languages` + header `Accept-Language` (cross-cutting) | | FR-16 | `/v1/config/currencies` + header `X-Display-Currency` (cross-cutting) | | FR-17 | `/v1/sellers/register`, `/v1/sellers/{sellerId}/kyc-documents`, `/v1/admin/sellers/{sellerId}/kyc-review` | | FR-18 | `/v1/seller/products`, `/v1/seller/products/{productId}/variants/{variantId}/inventory` | | FR-19 | `/v1/seller/orders`, `/v1/seller/orders/{orderId}/status` | | FR-20 | `/v1/seller/dashboard/summary` | | FR-21 | `/v1/admin/commission-rules`, `/v1/admin/commission-rules/{categoryId}` (kèm `holdDays`, BR-04) | | FR-22 | `/v1/seller/payouts`, `/v1/admin/payouts` | | FR-23 | `/v1/admin/sellers`, `/v1/admin/sellers/{sellerId}/status` | | FR-24 | `/v1/admin/products`, `/v1/admin/products/{productId}/status` | | FR-25 | `/v1/admin/disputes`, `/v1/admin/disputes/{disputeId}` | | FR-26 | `/v1/ops/orders/{orderId}/fulfillment`, `/v1/ops/shipments`, `/v1/shipments/{shipmentId}/tracking`, `/v1/webhooks/{ghn,ghtk}` | | FR-27 | `/v1/auth/mfa/challenge`, `/v1/auth/mfa/enroll` | --- # 5. Thiết kế dữ liệu (Data & Database Design) > Đầu vào: `docs/00-project-brief.md` (profile: scale = **large**, hasPII = **true**, hasPayment = **true**, greenfield không có hệ thống cũ), `01-tong-quan.md` (Glossary/entities mục 1.3), `02-phan-tich-yeu-cau.md` (FR-01..FR-27, NFR-04/05/06), `03-kien-truc.md` (kiến trúc **database-per-service** trên **RDS PostgreSQL Multi-AZ**, cache **ElastiCache Redis**, tìm kiếm **OpenSearch** như read-model phái sinh, lưu file lớn — ảnh sản phẩm/KYC — trên **S3**). ## 5.0 Nguyên tắc thiết kế - **Database-per-service** theo ranh giới đã chốt ở mục 3.1: mỗi service sở hữu schema/database riêng trên RDS PostgreSQL Multi-AZ; **không có ràng buộc khoá ngoại (FK) vật lý xuyên service** — các trường tham chiếu chéo service (VD `seller_id` trong Cart & Order Service trỏ tới `seller.id` của Seller Management Service) là **FK logic**, được đảm bảo nhất quán qua sự kiện (event) trên message broker (Kafka/MSK) theo mô hình saga/eventual consistency, không qua transaction DB phân tán. - **Khoá chính:** dùng `UUID` (sinh phía ứng dụng hoặc `gen_random_uuid()`) cho phần lớn bảng nghiệp vụ để tránh xung đột ID khi các service độc lập sinh dữ liệu và hỗ trợ replication/migration sau này. Riêng các bảng log khối lượng lớn, append-only (`notification_log`, `shipment_event`, `audit_log`) dùng `BIGINT IDENTITY` để tối ưu ghi tuần tự và partitioning theo thời gian. - **Tên entity/bảng khớp Glossary mục 1.3** (Product, ProductVariant, Category, Cart, CartItem, Order, OrderItem, Payment, Shipment, ReturnRequest, Dispute, Promotion, Review, Notification, CommissionRule, Payout, KYCDocument, LoyaltyAccount, LoyaltyTransaction, MembershipTier, Wishlist, Currency, Language). Tên bảng SQL dùng `snake_case` số ít (VD `product`, `order_item`) — quy ước đặt tên kỹ thuật, không đổi nghĩa entity. - **Đánh dấu dữ liệu nhạy cảm** bằng nhãn **[PII]** (dữ liệu cá nhân — NĐ13/2023) và **[Payment]** (dữ liệu tài chính/thanh toán) ngay tại cột liên quan để `security-architect` rà soát mã hoá at-rest/in-transit, tokenization, và kiểm soát truy cập ở mục 8. - **Không thiết kế API request/response** — thuộc phạm vi `api-designer` (mục 4). - Do brief không cung cấp số liệu khối lượng/tăng trưởng cụ thể theo tháng/năm (chỉ có ước lượng bậc lớn ở mục brief: hàng trăm nghìn SKU, hàng trăm nghìn–hàng triệu user, hàng nghìn–chục nghìn concurrent), các quyết định partitioning/retention dưới đây dựa trên **giả định thận trọng** (xem `assumptions`), thiết kế đủ đơn giản để điều chỉnh khi có số liệu thực tế. ## 5.1 Mô hình dữ liệu tổng quan (ERD) ### 5.1.1 ERD logic toàn hệ thống (rút gọn quan hệ chính giữa các bounded context) ```mermaid erDiagram CUSTOMER ||--o{ CUSTOMER_ADDRESS : has CUSTOMER ||--o{ OAUTH_IDENTITY : links CUSTOMER ||--o| LOYALTY_ACCOUNT : owns CUSTOMER ||--o{ WISHLIST_ITEM : saves CUSTOMER ||--o{ CART : owns CUSTOMER ||--o{ ORDER : places CUSTOMER ||--o{ REVIEW : writes CUSTOMER ||--o{ RETURN_REQUEST : requests CUSTOMER ||--o{ DISPUTE : raises LOYALTY_ACCOUNT ||--o{ LOYALTY_TRANSACTION : records LOYALTY_ACCOUNT }o--|| MEMBERSHIP_TIER : classified_as CART ||--o{ CART_ITEM : contains CART_ITEM }o--|| PRODUCT_VARIANT : references ORDER ||--o{ ORDER_SELLER : splits_into ORDER ||--o| PAYMENT : paid_by ORDER ||--o{ PROMOTION_USAGE : applies ORDER_SELLER ||--o{ ORDER_ITEM : contains ORDER_SELLER ||--o| SHIPMENT : fulfilled_by ORDER_SELLER ||--o{ RETURN_REQUEST : may_have ORDER_SELLER ||--o{ DISPUTE : may_have ORDER_SELLER ||--o| COMMISSION_TRANSACTION : generates ORDER_SELLER }o--|| SELLER : belongs_to ORDER_ITEM }o--|| PRODUCT_VARIANT : references PROMOTION ||--o{ PROMOTION_USAGE : used_in SELLER ||--o{ KYC_DOCUMENT : submits SELLER ||--o{ PRODUCT : lists SELLER ||--o| SELLER_BANK_ACCOUNT : has SELLER ||--o{ COMMISSION_TRANSACTION : accrues SELLER ||--o{ PAYOUT : receives PAYOUT ||--o{ PAYOUT_HOLD : contains PRODUCT ||--o{ PRODUCT_VARIANT : has PRODUCT }o--|| CATEGORY : classified_as CATEGORY ||--o| COMMISSION_RULE : rated_by PRODUCT_VARIANT ||--o| INVENTORY_STOCK : tracked_by PRODUCT ||--o{ REVIEW : receives PRODUCT ||--o{ WISHLIST_ITEM : saved_in ``` > Ghi chú: đường nối trong ERD tổng quan thể hiện quan hệ **logic nghiệp vụ**, không phải FK vật lý (vì mỗi khối thực thể nằm ở database riêng của service tương ứng — xem 5.2). `PlatformAdmin`, `OpsStaff`, `CSR` không xuất hiện là entity dữ liệu riêng vì chỉ là vai trò (role) trong bảng `user_account` của Identity Service (5.2.1); `Language`, `Currency` là bảng cấu hình dùng chung, đặt tại 5.2.2. Bảng `audit_log` (Audit & Compliance Service, bổ sung v3 — xem 5.2.11) cũng không xuất hiện trong ERD tổng quan này vì đây là bảng ghi vết (audit trail) **polymorphic** tham chiếu tới nhiều loại resource khác nhau qua `resource_type`/`resource_id` chứ không phải quan hệ nghiệp vụ 1-1/1-n/n-n cố định với một entity duy nhất — xem ERD riêng tại 5.1.2. ### 5.1.2 ERD chi tiết theo bounded context **Identity & Access Service** ```mermaid erDiagram USER_ACCOUNT ||--o{ OAUTH_IDENTITY : links USER_ACCOUNT ||--o{ MFA_DEVICE : enrolls USER_ACCOUNT ||--o| CUSTOMER_PROFILE : extends USER_ACCOUNT ||--o{ CUSTOMER_ADDRESS : has USER_ACCOUNT { uuid id PK string email "PII" string phone "PII" string password_hash string role boolean mfa_enabled string status int failed_login_count timestamp locked_until timestamp last_failed_login_at } OAUTH_IDENTITY { uuid id PK uuid user_account_id FK string provider string provider_user_id } MFA_DEVICE { uuid id PK uuid user_account_id FK string method string secret_encrypted "PII" } CUSTOMER_PROFILE { uuid user_account_id PK, FK string full_name "PII" date date_of_birth "PII" string preferred_language string preferred_currency } CUSTOMER_ADDRESS { uuid id PK uuid user_account_id FK string recipient_name "PII" string phone "PII" string address_line "PII" boolean is_default } ``` **Catalog & Inventory Service** ```mermaid erDiagram CATEGORY ||--o{ CATEGORY : parent_of CATEGORY ||--o{ CATEGORY_I18N : localized_as CATEGORY ||--o{ PRODUCT : classifies PRODUCT ||--o{ PRODUCT_I18N : localized_as PRODUCT ||--o{ PRODUCT_VARIANT : has PRODUCT_VARIANT ||--o| INVENTORY_STOCK : tracked_by PRODUCT ||--o{ WISHLIST_ITEM : saved_in LANGUAGE ||--o{ PRODUCT_I18N : used_by CURRENCY ||--o{ EXCHANGE_RATE : quoted_as CATEGORY { uuid id PK uuid parent_category_id FK string code boolean is_active } PRODUCT { uuid id PK uuid seller_id FK uuid category_id FK string status } PRODUCT_VARIANT { uuid id PK uuid product_id FK string sku_code numeric price_amount string currency_code } INVENTORY_STOCK { uuid variant_id PK, FK int quantity_available int quantity_reserved } WISHLIST_ITEM { uuid id PK uuid customer_id FK uuid product_id FK } LANGUAGE { string code PK string name boolean is_default } CURRENCY { string code PK string name boolean is_transactional } EXCHANGE_RATE { uuid id PK string currency_code FK numeric rate_to_vnd date effective_date } ``` **Cart & Order Service** ```mermaid erDiagram CART ||--o{ CART_ITEM : contains ORDER ||--o{ ORDER_SELLER : splits_into ORDER_SELLER ||--o{ ORDER_ITEM : contains ORDER_SELLER ||--o{ RETURN_REQUEST : may_have ORDER_SELLER ||--o{ DISPUTE : may_have ORDER_SELLER ||--o{ ORDER_STATUS_HISTORY : tracks CART { uuid id PK uuid customer_id FK string session_id string status } CART_ITEM { uuid id PK uuid cart_id FK uuid product_variant_id FK uuid seller_id FK int quantity } ORDER { uuid id PK uuid customer_id FK string order_number numeric total_amount string status } ORDER_SELLER { uuid id PK uuid order_id FK uuid seller_id FK string sub_order_number string status } ORDER_ITEM { uuid id PK uuid order_seller_id FK uuid product_variant_id FK int quantity numeric unit_price } RETURN_REQUEST { uuid id PK uuid order_seller_id FK uuid customer_id FK string status } DISPUTE { uuid id PK uuid order_seller_id FK uuid assigned_csr_id FK string status } ORDER_STATUS_HISTORY { bigint id PK uuid order_seller_id FK string status timestamp changed_at } ``` **Payment Service** ```mermaid erDiagram PAYMENT ||--o{ PAYMENT_RECONCILIATION_LOG : reconciled_by PAYMENT { uuid id PK uuid order_id FK string method numeric amount "Payment" string gateway_transaction_ref "Payment" string status } PAYMENT_RECONCILIATION_LOG { uuid id PK uuid payment_id FK string gateway_status timestamp reconciled_at } ``` **Seller Management Service** ```mermaid erDiagram SELLER ||--o{ KYC_DOCUMENT : submits SELLER ||--o| SELLER_BANK_ACCOUNT : has SELLER { uuid id PK uuid user_account_id FK string business_name string tax_code "PII" string status } KYC_DOCUMENT { uuid id PK uuid seller_id FK string document_type string file_url_s3 "PII" string verified_status } SELLER_BANK_ACCOUNT { uuid id PK uuid seller_id FK string bank_name string account_number "PII, Payment" string account_holder_name "PII" } ``` **Commission & Payout Service** ```mermaid erDiagram COMMISSION_RULE ||--o{ COMMISSION_TRANSACTION : applies_to COMMISSION_TRANSACTION }o--|| PAYOUT : settled_in PAYOUT ||--o{ PAYOUT_HOLD : contains COMMISSION_RULE { uuid id PK uuid category_id FK numeric commission_percent int hold_days date effective_from } COMMISSION_TRANSACTION { uuid id PK uuid order_seller_id FK uuid seller_id FK numeric commission_amount numeric net_amount } PAYOUT { uuid id PK uuid seller_id FK date period_start date period_end numeric total_net_amount "Payment" string bank_transfer_ref "Payment" string status } PAYOUT_HOLD { uuid id PK uuid commission_transaction_id FK date hold_until_date string release_status } ``` **Promotion & Loyalty Service** ```mermaid erDiagram PROMOTION ||--o{ PROMOTION_USAGE : used_in LOYALTY_ACCOUNT ||--o{ LOYALTY_TRANSACTION : records LOYALTY_ACCOUNT }o--|| MEMBERSHIP_TIER : classified_as PROMOTION { uuid id PK string code string type numeric value string status } PROMOTION_USAGE { uuid id PK uuid promotion_id FK uuid order_id FK uuid customer_id FK } LOYALTY_ACCOUNT { uuid id PK uuid customer_id FK int points_balance numeric total_spend_12m } LOYALTY_TRANSACTION { uuid id PK uuid loyalty_account_id FK uuid order_id FK string type int points } MEMBERSHIP_TIER { uuid id PK string name numeric min_spend_threshold } ``` **Review, Notification, Shipping & Fulfillment Service** ```mermaid erDiagram REVIEW { uuid id PK uuid product_id FK uuid customer_id FK uuid order_item_id FK int rating string status } NOTIFICATION_LOG { bigint id PK uuid recipient_user_id FK string channel string status } SHIPMENT ||--o{ SHIPMENT_EVENT : has SHIPMENT { uuid id PK uuid order_seller_id FK string carrier string tracking_number string status } SHIPMENT_EVENT { bigint id PK uuid shipment_id FK string event_status timestamp event_time } ``` **Audit & Compliance Service (bổ sung v3 — theo findings bảo mật mục 8)** ```mermaid erDiagram AUDIT_LOG { bigint id PK uuid actor_id FK string actor_role string action string resource_type uuid resource_id jsonb before_json jsonb after_json string ip_address string user_agent timestamp created_at } ``` > `AUDIT_LOG` không có quan hệ FK vật lý tới bất kỳ entity nào khác (kể cả `actor_id`) — tham chiếu là **FK logic** dạng polymorphic qua `resource_type`/`resource_id`, ghi nhận sự kiện phát sinh từ nhiều bounded context khác nhau (Seller Management, Commission & Payout, Cart & Order...). Chi tiết đặt vấn đề, cơ chế ghi và retention xem 5.2.11. ## 5.2 Database Schema chi tiết theo service > Quy ước cột chung không lặp lại ở từng bảng: `created_at timestamptz DEFAULT now()`, `updated_at timestamptz` (trigger cập nhật) có ở hầu hết bảng trừ log append-only. PK mặc định `uuid DEFAULT gen_random_uuid()` trừ khi ghi chú khác. ### 5.2.1 Identity & Access Service (phục vụ FR-01, FR-02, FR-03, FR-27) **Bảng `user_account`** | Cột | Kiểu dữ liệu | PK/FK | Constraint/Index | Ghi chú | |---|---|---|---|---| | id | uuid | PK | | | | email | varchar(255) | | UNIQUE, NOT NULL, index | **[PII]** | | phone | varchar(20) | | index | **[PII]**, nullable | | password_hash | varchar(255) | | NOT NULL | bcrypt/argon2, nullable nếu chỉ dùng OAuth | | role | varchar(20) | | CHECK IN ('customer','seller','platform_admin','ops_staff','csr') | | | mfa_enabled | boolean | | DEFAULT false | FR-27; bắt buộc `true` khi role=platform_admin (kiểm tra ở tầng ứng dụng) | | status | varchar(20) | | CHECK IN ('active','locked','deactivated') | | | last_login_at | timestamptz | | | | | failed_login_count | int | | DEFAULT 0, CHECK >= 0 | **(v3 — theo review mục 8)** đếm số lần đăng nhập sai liên tiếp; reset về 0 khi đăng nhập thành công | | locked_until | timestamptz | | nullable | **(v3)** thời điểm tài khoản được tự động mở khoá sau khi bị khoá tạm do vượt ngưỡng `failed_login_count` (ngưỡng/khoảng thời gian khoá cụ thể do `security-architect` quy định ở mục 8); index (locked_until) hỗ trợ job quét mở khoá | | last_failed_login_at | timestamptz | | nullable | **(v3)** thời điểm lần đăng nhập sai gần nhất, phục vụ giám sát brute-force | **Bảng `oauth_identity`** (FR-02) — id (PK), user_account_id (FK → user_account), provider (`google`/`facebook`), provider_user_id, linked_at. UNIQUE(provider, provider_user_id). **Bảng `mfa_device`** (FR-27) — id (PK), user_account_id (FK), method (`totp`/`sms`), secret_encrypted **[PII]** (mã hoá bắt buộc), enabled, created_at. **Bảng `customer_profile`** (FR-03) — user_account_id (PK, FK 1-1 → user_account), full_name **[PII]**, date_of_birth **[PII]**, gender, preferred_language (FK → language.code), preferred_currency (FK → currency.code). **Bảng `customer_address`** (FR-03) — id (PK), user_account_id (FK), recipient_name **[PII]**, phone **[PII]**, address_line **[PII]**, ward, district, province, country, is_default (boolean), created_at. Index (user_account_id, is_default). ### 5.2.2 Catalog & Inventory Service (phục vụ FR-04, FR-10, FR-15, FR-16, FR-18, FR-24) **Bảng `category`** — id (PK), parent_category_id (FK self-reference, nullable), code (UNIQUE), commission_rule_id (FK logic → Commission Service `commission_rule.id`), is_active. Index (parent_category_id). **Bảng `category_i18n`** (FR-15) — id (PK), category_id (FK), language_code (FK → language.code), name, description. UNIQUE(category_id, language_code). **Bảng `product`** (FR-18, FR-24) — id (PK), seller_id (FK logic → Seller Management `seller.id`), category_id (FK), status (`draft`/`active`/`hidden_by_admin`/`removed` — cột phục vụ FR-24 quản trị catalog toàn sàn), created_at, updated_at. Index (seller_id), index (category_id, status) phục vụ FR-04 lọc theo ngành hàng. **Bảng `product_i18n`** (FR-15) — id (PK), product_id (FK), language_code (FK), name, description (text). UNIQUE(product_id, language_code). **Bảng `product_variant`** (FR-04, FR-18) — id (PK), product_id (FK), sku_code (UNIQUE), attributes (jsonb — VD size/màu), price_amount (numeric(14,2)), currency_code (FK → currency.code, mặc định VND), status. Index (sku_code). **Bảng `inventory_stock`** (FR-18, FR-26) — variant_id (PK, FK 1-1 → product_variant), quantity_available (int, CHECK >= 0), quantity_reserved (int, CHECK >= 0), warehouse_location, updated_at. Index (quantity_available) hỗ trợ truy vấn còn hàng. **Bảng `wishlist_item`** (FR-10) — id (PK), customer_id (FK logic → Identity `user_account.id`), product_id (FK), added_at. UNIQUE(customer_id, product_id). **Bảng `language`** (FR-15) — code (PK, VD `vi`/`en`/`zh`/`ko`/`ja`), name, is_default (chỉ `vi`=true). Dữ liệu seed tĩnh, không tăng trưởng. **Bảng `currency`** (FR-16) — code (PK, VD `VND`/`USD`/...), name, is_transactional (chỉ `VND`=true theo brief — không giao dịch trực tiếp ngoại tệ). **Bảng `exchange_rate`** (FR-16) — id (PK), currency_code (FK), rate_to_vnd (numeric), effective_date (date). Chỉ phục vụ hiển thị quy đổi tham khảo, không dùng để thanh toán. Index (currency_code, effective_date DESC). > Ghi chú: dữ liệu tìm kiếm/lọc thời gian thực (FR-04) được **phái sinh** sang OpenSearch qua event `ProductUpdated`/`ProductCreated` từ service này (theo mục 3.2); OpenSearch không phải hệ quản trị CSDL giao dịch nên không đưa schema chi tiết vào đây — chỉ số hoá lại các trường trên. ### 5.2.3 Cart & Order Service (phục vụ FR-05, FR-06, FR-08, FR-09, FR-19, FR-25) **Bảng `cart`** (FR-05) — id (PK), customer_id (FK logic, nullable — null nếu Guest), session_id (varchar, dùng cho Guest chưa đăng nhập), status (`active`/`converted`/`abandoned`), updated_at. Index (customer_id), index (session_id). **Bảng `cart_item`** (FR-05) — id (PK), cart_id (FK), product_variant_id (FK logic), seller_id (FK logic, denormalized để hỗ trợ tách đơn ở FR-06), quantity (int, CHECK > 0), unit_price_snapshot (numeric), added_at. Index (cart_id). **Bảng `order`** (FR-06, FR-08) — id (PK), customer_id (FK logic, nullable — Guest checkout), order_number (UNIQUE, human-readable), total_amount (numeric), currency_code (mặc định VND), status (`pending_payment`/`confirmed`/`partially_fulfilled`/`completed`/`cancelled`), promotion_id (FK logic, nullable), placed_at. Index (customer_id, placed_at DESC). **Bảng `order_seller`** (FR-06, FR-19) — id (PK), order_id (FK), seller_id (FK logic), sub_order_number (UNIQUE), subtotal_amount (numeric), status (`pending`/`confirmed`/`packed`/`shipped`/`delivered`/`cancelled`/`returned`), created_at, updated_at. Index (seller_id, status) — truy vấn dashboard đơn hàng seller (FR-19). **Bảng `order_item`** (FR-06) — id (PK), order_seller_id (FK), product_variant_id (FK logic), product_name_snapshot, quantity (int), unit_price (numeric), line_total (numeric). Index (order_seller_id). **Bảng `order_status_history`** (FR-08) — id (bigint, PK, identity), order_seller_id (FK), status, changed_at, changed_by (user_account_id logic). Append-only, index (order_seller_id, changed_at). **Bảng `return_request`** (FR-09) — id (PK), order_seller_id (FK), customer_id (FK logic), reason (text), status (`requested`/`approved`/`rejected`/`refunded`), requested_at, resolved_at. **Bảng `dispute`** (FR-09, FR-25) — id (PK), order_seller_id (FK), raised_by (`customer`/`seller`), assigned_csr_id (FK logic → Identity `user_account.id` role=csr), status (`open`/`investigating`/`resolved`/`escalated`), created_at, resolved_at. Index (assigned_csr_id, status). ### 5.2.4 Payment Service (phục vụ FR-07) **Bảng `payment`** — id (PK), order_id (FK logic → Cart & Order `order.id`), method (`vnpay`/`momo`/`cod`), amount (numeric(14,2)) **[Payment]**, currency_code, gateway_transaction_ref (varchar) **[Payment]**, status (`pending`/`success`/`failed`/`refunded`), raw_gateway_response (jsonb, chỉ lưu dữ liệu phản hồi phi thẻ — không lưu số thẻ/CVV theo NFR-05), paid_at. Index (order_id), index (gateway_transaction_ref) phục vụ đối soát. **Bảng `payment_reconciliation_log`** — id (PK), payment_id (FK), gateway_status, discrepancy_note, reconciled_at. Append-only phục vụ job đối soát định kỳ (mục 3.4). > Không có bảng lưu thông tin thẻ thanh toán — đúng theo quyết định kiến trúc "PCI-DSS scope giảm" (mục 3.1): toàn bộ dữ liệu thẻ do VNPay/Momo xử lý, hệ thống chỉ lưu tham chiếu giao dịch. ### 5.2.5 Seller Management Service (phục vụ FR-17, FR-20, FR-23) **Bảng `seller`** (FR-17, FR-23) — id (PK), user_account_id (FK logic → Identity `user_account.id`), business_name, tax_code **[PII]**, business_license_number **[PII]**, status (`pending_kyc`/`active`/`suspended`/`rejected`), approved_by (FK logic, admin), approved_at, created_at. Index (status) phục vụ FR-23 giám sát danh sách seller. **Bảng `kyc_document`** (FR-17) — id (PK), seller_id (FK), document_type (`business_license`/`id_card_front`/`id_card_back`), file_url_s3 (varchar, trỏ tới object S3 riêng biệt theo mục 3.1) **[PII]**, verified_status (`pending`/`verified`/`rejected`), reviewed_by (FK logic, admin), reviewed_at, uploaded_at. **Bảng `seller_bank_account`** (FR-22, dữ liệu do FR-17 thu thập) — id (PK), seller_id (FK), bank_name, account_number **[PII, Payment]**, account_holder_name **[PII]**, is_active, updated_at. ### 5.2.6 Commission & Payout Service (phục vụ FR-20, FR-21, FR-22) **Bảng `commission_rule`** (FR-21, FR-22) — id (PK), category_id (FK logic → Catalog `category.id`), commission_percent (numeric(5,2), CHECK 0-100), **hold_days (integer, nullable, CHECK 3-7 khi có giá trị — khuyến nghị theo brief mục 2/5; `NULL` = áp dụng mặc định toàn sàn 5 ngày theo BR-04)**, effective_from (date), effective_to (date, nullable), updated_by (FK logic, admin), updated_at. Index (category_id, effective_from DESC) — cho phép lịch sử thay đổi % hoa hồng và số ngày hold theo ngành hàng. Khi Commission & Payout Service tạo `payout_hold` (xem dưới), `hold_until_date = OrderDelivered.deliveredAt + (commission_rule.hold_days nếu có giá trị, ngược lại mặc định 5 ngày toàn sàn)`. **Bảng `commission_transaction`** (FR-20, FR-21) — id (PK), order_seller_id (FK logic → Cart & Order `order_seller.id`), seller_id (FK logic), gross_amount (numeric), commission_amount (numeric), net_amount (numeric), calculated_at. Index (seller_id, calculated_at) phục vụ dashboard doanh thu seller (FR-20). **Bảng `payout`** (FR-20, FR-22) — id (PK), seller_id (FK logic), period_start (date), period_end (date), total_net_amount (numeric(14,2)) **[Payment]**, bank_transfer_ref (varchar) **[Payment]**, status (`scheduled`/`processing`/`paid`/`failed`), scheduled_at, paid_at. Index (seller_id, period_start DESC). UNIQUE(seller_id, period_start, period_end) tránh payout trùng chu kỳ. **Bảng `payout_hold`** (FR-22, FR-25) — id (PK), commission_transaction_id (FK), hold_until_date (date — tính từ `OrderDelivered` + `commission_rule.hold_days` áp dụng, xem công thức ở bảng `commission_rule` phía trên), release_status (`holding`/`released`/`disputed_frozen`/**`reversed`**), released_at. Ý nghĩa các trạng thái: - `holding`: đang trong kỳ giữ tiền, chưa đến `hold_until_date`. - `released`: đã qua `hold_until_date`, không có Dispute mở, hoa hồng được đưa vào kỳ payout kế tiếp. - `disputed_frozen`: **tạm giữ** — có Dispute liên quan đang mở/chờ xử lý trước `hold_until_date`; có thể quay lại `holding` nếu Dispute bị từ chối (reject). - `reversed`: **trạng thái kết thúc, vĩnh viễn** — Dispute liên quan được duyệt hoàn tiền cho khách; hoa hồng bị loại khỏi payout hoàn toàn, không bao giờ chuyển sang `released`. Khác với `disputed_frozen` (tạm giữ chờ quyết định), `reversed` là kết quả cuối cùng sau khi đã có quyết định hoàn tiền. Index (hold_until_date, release_status) phục vụ job quét hằng ngày để giải phóng tiền vào kỳ payout; job loại trừ mọi dòng có `release_status = 'reversed'` khỏi các lần quét tiếp theo (không xử lý lại). ### 5.2.7 Promotion & Loyalty Service (phục vụ FR-13, FR-14) **Bảng `promotion`** (FR-13) — id (PK), code (UNIQUE), type (`percent`/`fixed_amount`), value (numeric), min_order_amount (numeric, nullable), valid_from, valid_to, usage_limit (int, nullable), created_by (FK logic, admin), status (`active`/`expired`/`disabled`). **Bảng `promotion_usage`** (FR-13) — id (PK), promotion_id (FK), order_id (FK logic), customer_id (FK logic), discount_amount (numeric), used_at. UNIQUE(promotion_id, order_id). **Bảng `loyalty_account`** (FR-14) — id (PK), customer_id (FK logic, UNIQUE — 1-1 với Customer), points_balance (int, CHECK >= 0), tier_id (FK → membership_tier), total_spend_12m (numeric — cửa sổ trượt 12 tháng theo giả định #5 mục 1.4), updated_at. **Bảng `loyalty_transaction`** (FR-14) — id (PK), loyalty_account_id (FK), order_id (FK logic, nullable — null khi admin điều chỉnh thủ công), type (`earn`/`redeem`/`expire`/`adjust`), points (int, có thể âm), created_at. Index (loyalty_account_id, created_at DESC). **Bảng `membership_tier`** (FR-14) — id (PK), name (`Bạc`/`Vàng`/`Kim cương`), min_spend_threshold (numeric), benefits_description. Dữ liệu cấu hình tĩnh, ít thay đổi. **Ghi chú seed data:** giá trị `min_spend_threshold` (VND) cho từng hạng hiện là **placeholder tạm thời**, chưa có con số cụ thể từ brief/mục 2 — cần chủ dự án xác nhận ngưỡng VND chính xác cho Bạc/Vàng/Kim cương trước khi seed dữ liệu production (xem `assumptions`, `openQuestions`). ### 5.2.8 Review Service (phục vụ FR-11) **Bảng `review`** — id (PK), product_id (FK logic → Catalog `product.id`), customer_id (FK logic), order_item_id (FK logic → Cart & Order `order_item.id`, dùng để xác minh khách đã mua trước khi cho phép đánh giá), rating (int, CHECK 1-5), comment (text), status (`visible`/`hidden_by_admin`), created_at. UNIQUE(customer_id, order_item_id) — mỗi lượt mua chỉ đánh giá một lần. Index (product_id, status). ### 5.2.9 Notification Service (phục vụ FR-12) **Bảng `notification_log`** — id (bigint, PK, identity), recipient_user_id (FK logic), channel (`email`/`sms`), template_code, related_entity_type (VD `order`, `shipment`), related_entity_id (uuid), status (`queued`/`sent`/`failed`), sent_at, error_message (nullable). Append-only, partition theo thời gian (xem 5.3.3). Index (recipient_user_id, sent_at DESC). ### 5.2.10 Shipping & Fulfillment Service (phục vụ FR-26) **Bảng `shipment`** — id (PK), order_seller_id (FK logic → Cart & Order `order_seller.id`), carrier (`GHN`/`GHTK`), tracking_number, status (`created`/`picked_up`/`in_transit`/`delivered`/`failed`), estimated_delivery_date, created_at. Index (tracking_number), index (order_seller_id). **Bảng `shipment_event`** — id (bigint, PK, identity), shipment_id (FK), event_status, event_time, raw_payload (jsonb — webhook gốc từ GHN/GHTK). Append-only, index (shipment_id, event_time). ### 5.2.11 Audit & Compliance Service (bổ sung v3 — theo findings bảo mật mục 8, phục vụ NFR-04, NFR-05) **Bảng `audit_log`** (append-only) — id (bigint, PK, identity), actor_id (uuid, FK logic → Identity `user_account.id`), actor_role (varchar, snapshot vai trò tại thời điểm hành động — VD `platform_admin`/`ops_staff`/`csr`), action (varchar, VD `kyc_document.verify`, `commission_rule.update`, `dispute.resolve`, `payout.retry`, `seller.lock`, `seller.unlock`), resource_type (varchar, VD `kyc_document`/`commission_rule`/`dispute`/`payout`/`seller`), resource_id (uuid), before_json (jsonb, nullable — snapshot trạng thái trước khi thay đổi) **[PII/Payment tuỳ ngữ cảnh]**, after_json (jsonb, nullable — snapshot trạng thái sau khi thay đổi) **[PII/Payment tuỳ ngữ cảnh]**, ip_address (varchar/inet), user_agent (varchar), created_at (timestamptz, NOT NULL). Index (resource_type, resource_id, created_at DESC), index (actor_id, created_at DESC). **Vị trí đặt & cơ chế ghi:** đặt tại một **service audit riêng biệt (Audit & Compliance Service)**, sở hữu database riêng theo đúng nguyên tắc database-per-service ở 5.0 — **không** ghi trực tiếp vào một bảng dùng chung từ các service nghiệp vụ khác (tránh phá vỡ ranh giới đã chốt ở mục 3.1). Cơ chế: mỗi service nghiệp vụ khi thực hiện hành động nhạy cảm xuyên service (duyệt/từ chối KYC ở Seller Management, cập nhật `commission_rule`/`hold_days` ở Commission & Payout, quyết định Dispute ở Cart & Order, retry `payout` ở Commission & Payout, khoá/mở `seller` ở Seller Management) phát một **domain event** tương ứng (VD `KycDocumentVerified`, `CommissionRuleUpdated`, `DisputeResolved`, `PayoutRetried`, `SellerLocked`/`SellerUnlocked`) lên message broker (Kafka/MSK, theo mục 3.2); Audit & Compliance Service subscribe các event này và ghi append-only vào `audit_log`. Cách tiếp cận này tận dụng hạ tầng event-driven đã có sẵn thay vì mỗi service tự duy trì audit log riêng lẻ (khó tổng hợp khi CSR/Admin cần tra cứu xuyên service) — thiết kế API tra cứu (đọc `audit_log`, giới hạn scope admin/ops) thuộc phạm vi `api-designer` (mục 4). **Retention/partition:** partition theo tháng (range trên `created_at`) do khối lượng ghi tăng theo mọi hành động nhạy cảm toàn sàn (tương tự `notification_log`/`shipment_event` — xem 5.3.3); retention tối thiểu **5 năm** — đủ cho mục đích audit an ninh và bao trùm phần lớn hành động liên quan tài chính (commission/payout), dù ngắn hơn mốc 10 năm chứng từ kế toán riêng của `payment`/`payout` ở 5.3.6 (**assumption**, cần chủ dự án/pháp chế xác nhận mốc chính xác — xem `openQuestions`; xem thêm khuyến nghị điều chỉnh retention theo `resource_type` ở mục 8 §8.2.5c, ghi nhận là Finding F14 tồn đọng — §0.4c). Không áp dụng "quyền xoá" theo NĐ13/2023 cho bản ghi audit (ghi nhận hành động của actor vai trò vận hành/quản trị, không phải yêu cầu xoá dữ liệu cá nhân của Customer thông thường); có thể cân nhắc ẩn danh hoá `ip_address`/`user_agent` sau retention để giảm rủi ro PII thứ cấp. ## 5.3 Chiến lược dữ liệu ### 5.3.1 Cache (Redis — ElastiCache, theo mục 3.2) | Loại dữ liệu cache | Vị trí | TTL đề xuất | Chiến lược invalidation | |---|---|---|---| | Catalog/Product detail (đọc nhiều, phục vụ NFR-01 <2s) | Catalog & Inventory Service | 5-15 phút | Cache-aside; invalidate chủ động khi nhận event `ProductUpdated`/`InventoryChanged` thay vì chỉ chờ TTL hết hạn | | Kết quả tìm kiếm/danh mục phổ biến (search subsystem) | Search subsystem (OpenSearch + Redis) | 1-5 phút cho query phổ biến, không cache query dài đuôi | Invalidate theo event đồng bộ index; TTL ngắn vì tồn kho/giá thay đổi thường xuyên mùa flash sale | | Session đăng nhập (JWT refresh/session state) | Identity & Access Service | Theo thời hạn session (VD 30 phút idle, 7 ngày remember-me) | Xoá khi logout/đổi mật khẩu; TTL tự nhiên hết hạn | | Giỏ hàng (Cart) của Customer đăng nhập | Cart & Order Service | 30 ngày (đồng bộ ghi xuống RDS định kỳ/khi checkout để không mất dữ liệu nếu Redis restart) | Ghi-through (write-through) khi thêm/xoá item; TTL gia hạn mỗi lần cập nhật | | Giỏ hàng Guest (theo session_id) | Cart & Order Service | 7 ngày | Không cần đồng bộ RDS bền vững — chấp nhận mất nếu hết hạn (đúng ghi chú mục 3.2: "có thể chấp nhận mất dữ liệu tạm thời thấp") | | Bảng tỷ giá quy đổi hiển thị (exchange_rate) | Catalog & Inventory Service | 1 giờ (chỉ hiển thị tham khảo theo FR-16, không dùng để thanh toán nên không cần realtime) | Refresh theo batch job cập nhật tỷ giá hằng ngày/hằng giờ | | Cấu hình hoa hồng đang hiệu lực (commission_rule, gồm cả `hold_days`) | Commission & Payout Service | 10 phút | Invalidate khi Admin cập nhật (FR-21, bao gồm cập nhật `hold_days` qua `PUT /v1/admin/commission-rules/{categoryId}`) qua event `CommissionRuleUpdated` | ### 5.3.2 Backup & Recovery - **RDS PostgreSQL Multi-AZ** (mọi service, theo mục 3.2): tự động failover đồng bộ trong AZ cùng vùng → **RPO gần 0** cho lỗi hạ tầng tầng instance. - **Automated backup + Point-in-Time Recovery (PITR):** bật cho toàn bộ database-per-service; retention đề xuất **35 ngày** cho các service giao dịch cốt lõi có dữ liệu tài chính/PII (Payment, Commission & Payout, Seller Management, Identity, Cart & Order); **14 ngày** cho các service ít quan trọng hơn (Review, Notification, Promotion & Loyalty) — phù hợp NFR-08 (ưu tiên vận hành khác nhau theo mức độ nghiêm trọng). - **Snapshot thủ công định kỳ + sao chép cross-region** (DR): snapshot hằng ngày, lưu tối thiểu 90 ngày cho Payment/Commission & Payout/Seller Management (dữ liệu tài chính, đối soát) để phục vụ kiểm toán; sao chép sang region phụ (VD ap-southeast-1 ↔ region dự phòng) tối thiểu cho các service giao dịch cốt lõi nhằm đáp ứng NFR-03 (uptime 99.9%). - **RTO/RPO gợi ý theo mức độ ưu tiên** (đối chiếu NFR-08 — ưu tiên multi-AZ cho service giao dịch cốt lõi): | Nhóm service | RPO gợi ý | RTO gợi ý | |---|---|---| | Payment, Cart & Order, Identity & Access (giao dịch cốt lõi) | ≤ 15 phút | ≤ 1 giờ | | Commission & Payout, Seller Management (tài chính, không realtime nhưng nhạy cảm) | ≤ 1 giờ | ≤ 4 giờ | | Catalog & Inventory, Promotion & Loyalty, Shipping & Fulfillment | ≤ 1 giờ | ≤ 8 giờ | | Review, Notification, Audit & Compliance (không ảnh hưởng giao dịch trực tiếp) | ≤ 24 giờ | ≤ 24 giờ | - **S3 (ảnh sản phẩm, KYC docs)**: bật versioning + cross-region replication cho bucket KYC (dữ liệu PII pháp lý, cần bảo toàn lâu dài); lifecycle policy chuyển ảnh sản phẩm ít truy cập sang storage class rẻ hơn (Infrequent Access) sau 90 ngày. ### 5.3.3 Partitioning Do `scale: large` (hàng trăm nghìn SKU, hàng trăm nghìn–hàng triệu user, giao dịch tích luỹ liên tục), áp dụng **partitioning theo thời gian (range partitioning theo `created_at`/tháng hoặc quý)** cho các bảng có tốc độ ghi cao và tăng trưởng không giới hạn: | Bảng | Kiểu partition | Lý do | |---|---|---| | `order`, `order_seller`, `order_item`, `order_status_history` | Range theo tháng | Khối lượng đơn hàng tích luỹ lớn nhất hệ thống; tách partition giúp truy vấn "đơn hàng gần đây" nhanh và archive/xoá đơn cũ dễ dàng | | `payment`, `payment_reconciliation_log` | Range theo tháng | Cùng nhịp tăng trưởng với order; phục vụ đối soát theo kỳ | | `commission_transaction`, `payout_hold` | Range theo tháng | Gắn với chu kỳ payout hàng tuần; truy vấn chủ yếu theo kỳ gần nhất | | `loyalty_transaction` | Range theo quý | Tăng trưởng theo số đơn hàng, truy vấn chủ yếu lịch sử 12 tháng gần nhất (theo tier) | | `notification_log`, `shipment_event` | Range theo tháng | Log append-only khối lượng lớn nhất, giá trị truy vấn giảm nhanh theo thời gian → dễ archive/drop partition cũ | | `audit_log` **(v3)** | Range theo tháng | Ghi từ mọi hành động nhạy cảm toàn sàn qua event (KYC, commission/hold_days, dispute, payout retry, khoá/mở seller); retention dài hạn (5 năm, xem 5.2.11/5.3.6) nên cần partition để archive theo mốc kiểm toán mà không ảnh hưởng hiệu năng ghi/đọc gần đây | **Không áp dụng partitioning** cho các bảng còn lại (`product`, `product_variant`, `category`, `user_account`, `seller`, `review`, `promotion`...) — khối lượng bậc hàng trăm nghìn đến vài triệu dòng vẫn nằm trong khả năng xử lý tốt của một bảng B-tree index thông thường trên RDS instance lớn; việc partition thêm sẽ tăng độ phức tạp vận hành không cần thiết ở MVP. ### 5.3.4 Sharding **Chưa áp dụng sharding ở MVP.** Lý do: kiến trúc database-per-service (mục 3.1) đã cho phép scale-out theo domain (VD Catalog & Search có thể scale độc lập khỏi Cart & Order khi tải đỉnh flash sale) — đây là lớp scale đầu tiên và đã đủ đáp ứng NFR-02 với quy mô "large" hiện tại (hàng trăm nghìn SKU, hàng chục nghìn concurrent peak). Sharding trong nội bộ một service (VD sharding `order` theo `customer_id`/`seller_id`) chỉ nên cân nhắc khi: - Một service đơn lẻ vượt quá khả năng của RDS instance lớn nhất khả dụng (write IOPS/storage), hoặc - Có số liệu thực tế cho thấy tăng trưởng vượt giả định hiện tại (VD hàng chục triệu đơn hàng/năm). Đây là **quyết định hoãn có căn cứ**, không phải bỏ sót — cần đánh giá lại khi có số liệu tải thực tế sau go-live (ghi ở `openQuestions`). ### 5.3.5 Migration dữ liệu cũ **Không áp dụng — dự án greenfield**, theo brief mục 3/5: "không có hệ thống cũ cần tích hợp/migrate". Dữ liệu khởi tạo (seed) chỉ gồm dữ liệu cấu hình tĩnh: `language`, `currency`, `membership_tier` (giá trị `min_spend_threshold` tạm thời, chờ xác nhận — xem 5.2.7), `category` gốc, `commission_rule` mặc định theo ngành hàng ban đầu (bao gồm `hold_days` — mặc định để `NULL` cho hầu hết ngành hàng, dùng giá trị toàn sàn 5 ngày, trừ khi có ngành hàng đặc thù cần cấu hình riêng ngay từ đầu). ### 5.3.6 Retention & xoá dữ liệu (liên quan NĐ13/2023 — bảo vệ dữ liệu cá nhân) | Loại dữ liệu | Đề xuất retention | Ghi chú | |---|---|---| | Tài khoản Customer đã đóng/xoá theo yêu cầu (quyền xoá dữ liệu cá nhân — NĐ13/2023) | Ẩn danh hoá (anonymize) `email`, `phone`, `full_name`, địa chỉ trong vòng 30 ngày kể từ yêu cầu hợp lệ, giữ lại `order`/`payment` liên quan ở dạng tách rời định danh (cần cho đối soát/kế toán) | Cần quy trình xoá/ẩn danh cụ thể — chi tiết kỹ thuật (mã hoá, key rotation) thuộc mục 8 | | KYC documents (giấy phép kinh doanh, CMND) | Tối thiểu **5 năm** sau khi seller ngừng hoạt động (giả định theo thông lệ lưu trữ hồ sơ pháp lý — brief chưa quy định số năm cụ thể) | **assumption** — cần xác nhận với chủ dự án/pháp chế | | Payment, commission_transaction, payout (dữ liệu tài chính) | Tối thiểu **10 năm** (thông lệ lưu trữ chứng từ kế toán tại Việt Nam) | **assumption** — cần xác nhận yêu cầu kế toán/thuế cụ thể | | `audit_log` (audit trail hành động nhạy cảm xuyên service — v3) | Tối thiểu **5 năm** | **assumption** — cần chủ dự án/pháp chế xác nhận mốc chính xác cho audit an ninh/tuân thủ; xem 5.2.11 | | notification_log, shipment_event (log vận hành) | 90 ngày, sau đó archive lạnh hoặc xoá | Không có giá trị pháp lý bắt buộc lưu lâu dài | | review, wishlist_item | Không giới hạn trong khi tài khoản còn hoạt động; xoá khi Customer yêu cầu xoá tài khoản | | ## 5.4 Ma trận truy vết dữ liệu → yêu cầu chức năng | FR | Mô tả ngắn | Entity/bảng chính | |---|---|---| | FR-01 | Đăng ký & đăng nhập Customer | `user_account` | | FR-02 | Đăng nhập mạng xã hội | `oauth_identity` | | FR-03 | Hồ sơ & địa chỉ giao hàng | `customer_profile`, `customer_address` | | FR-04 | Danh mục & tìm kiếm đa seller | `category`, `product`, `product_variant` (+ chỉ mục OpenSearch phái sinh) | | FR-05 | Giỏ hàng đa seller | `cart`, `cart_item` | | FR-06 | Checkout & tách đơn theo seller | `order`, `order_seller`, `order_item` | | FR-07 | Thanh toán | `payment`, `payment_reconciliation_log` | | FR-08 | Quản lý đơn hàng (khách hàng) | `order`, `order_seller`, `order_status_history` | | FR-09 | Đổi trả & khiếu nại | `return_request`, `dispute` | | FR-10 | Wishlist | `wishlist_item` | | FR-11 | Đánh giá sản phẩm | `review` | | FR-12 | Thông báo đơn hàng | `notification_log` | | FR-13 | Khuyến mãi & mã giảm giá | `promotion`, `promotion_usage` | | FR-14 | Loyalty/điểm thưởng | `loyalty_account`, `loyalty_transaction`, `membership_tier` | | FR-15 | Đa ngôn ngữ giao diện | `language`, `product_i18n`, `category_i18n` | | FR-16 | Đa tiền tệ hiển thị | `currency`, `exchange_rate` | | FR-17 | Đăng ký & KYC seller | `seller`, `kyc_document` | | FR-18 | Quản lý sản phẩm & tồn kho (seller) | `product`, `product_variant`, `inventory_stock` | | FR-19 | Quản lý đơn hàng (seller) | `order_seller`, `order_item` | | FR-20 | Dashboard doanh thu/payout (seller) | `commission_transaction`, `payout` | | FR-21 | Cấu hình hoa hồng theo ngành hàng | `commission_rule` (gồm `hold_days` theo ngành hàng) | | FR-22 | Payout định kỳ cho seller | `payout`, `payout_hold`, `seller_bank_account` | | FR-23 | Quản trị seller | `seller` (cột `status`) | | FR-24 | Quản trị catalog toàn sàn | `product` (cột `status`) | | FR-25 | Xử lý tranh chấp & khiếu nại | `dispute`, `payout_hold` (trạng thái `disputed_frozen`/`reversed`) | | FR-26 | Xử lý tồn kho & vận chuyển | `inventory_stock`, `shipment`, `shipment_event` | | FR-27 | Xác thực đa yếu tố (MFA) | `user_account` (cột `mfa_enabled`, và **v3**: `failed_login_count`/`locked_until`/`last_failed_login_at` hỗ trợ khoá tài khoản sau nhiều lần đăng nhập sai), `mfa_device` | > **(v3)** Bảng `audit_log` (Audit & Compliance Service, 5.2.11) là dữ liệu **cross-cutting**, không gắn với một FR nghiệp vụ cụ thể — phục vụ **NFR-04** (bảo mật, audit trail) và **NFR-05** (tuân thủ pháp lý) cho các hành động nhạy cảm xuyên service: duyệt/từ chối KYC (liên quan FR-17), cấu hình commission/`hold_days` (FR-21), quyết định dispute (FR-09/FR-25), retry payout (FR-22), khoá/mở seller (FR-23). ## 5.5 Ghi chú cho `security-architect` (rà soát mã hoá tại mục 8) Danh sách cột đã đánh dấu **[PII]**/**[Payment]** cần ưu tiên rà soát mã hoá at-rest (KMS), kiểm soát truy cập theo vai trò, và masking khi hiển thị: - **PII:** `user_account.email/phone`, `mfa_device.secret_encrypted`, `customer_profile.full_name/date_of_birth`, `customer_address.recipient_name/phone/address_line`, `seller.tax_code/business_license_number`, `kyc_document.file_url_s3` (trỏ tới object S3 chứa ảnh giấy tờ — bản thân object cũng cần mã hoá S3-side), `seller_bank_account.account_holder_name`. - **Payment:** `payment.amount/gateway_transaction_ref`, `seller_bank_account.account_number`, `payout.total_net_amount/bank_transfer_ref`. - **(v3)** `user_account.failed_login_count/locked_until/last_failed_login_at` — không phải PII/Payment nhưng là dữ liệu bảo mật nhạy cảm (chống brute-force); cần kiểm soát truy cập ghi chỉ qua luồng xác thực nội bộ, không expose trực tiếp qua API đọc công khai. - **(v3)** `audit_log.before_json/after_json` — nội dung **thay đổi tuỳ resource_type** (VD snapshot `kyc_document`, `seller_bank_account`, `commission_rule` có thể chứa PII/Payment như `account_number`, `tax_code`): đề xuất `security-architect` quy định rõ (a) mã hoá at-rest cho toàn bảng `audit_log` tối thiểu bằng KMS, (b) cân nhắc redact/loại trừ các trường cực nhạy cảm (VD số tài khoản ngân hàng đầy đủ) khỏi snapshot trước khi ghi, chỉ giữ giá trị đã che (mask) hoặc hash để phục vụ audit mà không nhân bản rủi ro rò rỉ dữ liệu. - Đề xuất: mã hoá cột ở tầng ứng dụng (application-level encryption) cho `account_number`, `secret_encrypted`, `tax_code`, `business_license_number`; các cột PII còn lại tối thiểu dựa vào mã hoá at-rest của RDS (KMS) + TLS in-transit + IAM/role-based access theo service. ## 5.6 Findings & vấn đề cần làm rõ thêm - Glossary mục 1.3 không liệt kê rõ bảng nào lưu "Language"/"Currency" là entity độc lập hay chỉ là thuộc tính cấu hình — đã quyết định tạo bảng cấu hình riêng (`language`, `currency`, `exchange_rate`) đặt tại Catalog & Inventory Service theo ghi chú cross-cutting ở mục 3.1; cần xác nhận lại nếu kiến trúc sư muốn tách thành "Platform Config Service" riêng khi có thêm nhu cầu cấu hình khác. - NFR về retention dữ liệu (thời gian lưu KYC, dữ liệu tài chính, log) chưa được brief hoặc mục 2 quy định cụ thể — mục 5.3.6 đưa ra giả định thận trọng theo thông lệ, cần chủ dự án/pháp chế xác nhận lại con số chính xác trước khi go-live (đặc biệt retention KYC liên quan NĐ13/2023 và luật kế toán, và nay thêm retention `audit_log` — xem 5.2.11). - Số liệu khối lượng/tăng trưởng cụ thể theo thời gian (VD số đơn hàng/tháng dự kiến năm 1, năm 2) không có trong brief — quyết định partitioning ở 5.3.3 và ngưỡng cân nhắc sharding ở 5.3.4 dựa trên giả định định tính "large" ở mức bậc; cần rà soát lại khi có số liệu thực tế/kết quả load test. - **(v2 — theo review mục 6)** Đã bổ sung `commission_rule.hold_days` (nullable, fallback mặc định toàn sàn 5 ngày theo BR-04) để hiện thực hoá cấu hình hold theo ngành hàng; đã bổ sung trạng thái kết thúc `reversed` vào `payout_hold.release_status` để phân biệt loại hoa hồng vĩnh viễn (khi Dispute được duyệt hoàn tiền) với `disputed_frozen` (tạm giữ) — xem 5.2.6. `membership_tier.min_spend_threshold` vẫn là placeholder chờ chủ dự án xác nhận ngưỡng VND cụ thể — xem 5.2.7. - **(v3 — theo review findings bảo mật mục 8)** Đã bổ sung 3 cột chống brute-force vào `user_account` (`failed_login_count`, `locked_until`, `last_failed_login_at` — 5.2.1); ngưỡng số lần sai/khoảng thời gian khoá cụ thể để `security-architect` quy định ở mục 8. Đã bổ sung service mới **Audit & Compliance Service** với bảng `audit_log` append-only (5.2.11), cập nhật ERD (5.1.1 ghi chú, 5.1.2 thêm bounded context mới), partitioning (5.3.3), retention (5.3.6), ma trận truy vết (5.4) và ghi chú bảo mật cho `before_json`/`after_json` (5.5). Cơ chế đặt tại service riêng nhận qua event stream (Kafka/MSK) là **quyết định thiết kế của mục 5** — cần kiến trúc sư (mục 3) xác nhận bổ sung service này vào sơ đồ kiến trúc tổng thể nếu chưa có, và `api-designer` (mục 4) bổ sung endpoint đọc audit log có kiểm soát scope admin/ops nếu cần. --- # 6. Thiết kế luồng xử lý chi tiết (Detailed Design) > Đầu vào: `02-phan-tich-yeu-cau.md` (FR-01..FR-27), `03-kien-truc.md` (service boundary, event: `OrderPlaced`, `PaymentConfirmed`, `OrderDelivered`, `CommissionCalculated`, `PayoutScheduled`, `InventoryReserved`, `ReviewEligible`, `LoyaltyPointsEarned`, `NotificationRequested`), `04-api-design.md` (endpoint theo service, v3), `05-thiet-ke-du-lieu.md` (entity/bảng, enum trạng thái, v3). > > **Right-sizing:** do `profile.scale = large`, `hasPayment = true`, `hasPII = true` và mô hình marketplace nhiều bên (Customer, Seller, Admin, CSR, Ops, VNPay/Momo, GHN/GHTK, Ngân hàng), mục này vẽ sequence diagram cho **6 luồng phức tạp/rủi ro cao nhất**: (1) Checkout & thanh toán đa seller, (2) Xử lý đơn & vận chuyển, (3) Đổi trả/tranh chấp, (4) Tính hoa hồng & payout có kỳ giữ tiền, (5) Seller onboarding & KYC, (6) Đăng nhập + MFA/OAuth. Các CRUD đơn giản (wishlist, review, quản lý địa chỉ, cấu hình ngôn ngữ/tiền tệ...) không vẽ sequence riêng vì không có rẽ nhánh nghiệp vụ đáng kể. > > **(v2 — revision theo findings mục 8 và đồng bộ mục 4/5 v3):** bổ sung tối thiểu vào các luồng hiện có — không vẽ lại toàn bộ sequence/class/state diagram đã duyệt: (a) 6.1.5 KYC — Admin xem `KYCDocument` qua pre-signed URL TTL ngắn; (b) 6.1.4 payout — nêu kênh truyền batch file ngân hàng (giả định); (c) ghi chú `audit_log` (mục 5.2.11 v3) tại các hành động nhạy cảm (duyệt/từ chối KYC, cấu hình commission/`holdDays`, quyết định dispute, retry payout, khoá/mở seller); (d) 6.1.6 đăng nhập — bổ sung nhánh khoá tài khoản theo `failed_login_count`/`locked_until` (mục 5.2.1 v3); (e) phản ánh `403 ERR_FORBIDDEN_OWNERSHIP`, chống replay webhook (timestamp ±5 phút + idempotency theo `gatewayTransactionRef`), và OAuth `state`/`409 ERR_ACCOUNT_LINK_REQUIRED` (mục 4 v3) ở 6.1.1 và 6.1.6. ## 6.1 Sơ đồ tuần tự (Sequence Diagram) ### 6.1.1 Checkout & thanh toán đa seller (FR-05, FR-06, FR-07, FR-12, FR-13, FR-18) ```mermaid sequenceDiagram actor Customer participant Web as Web Storefront (Guest/Customer) participant CartOrder as Cart & Order Service participant Catalog as Catalog & Inventory Service participant Payment as Payment Service participant VNPay as VNPay/Momo participant MQ as Message Broker participant Notify as Notification Service participant Commission as Commission & Payout Service Customer->>Web: Xem giỏ hàng, bấm "Đặt hàng" Web->>CartOrder: POST /v1/cart/apply-coupon (nếu có coupon) CartOrder-->>Web: Cart đã áp giảm giá (FR-13) Web->>CartOrder: POST /v1/checkout (Idempotency-Key, shippingAddressId, paymentMethod) CartOrder->>Catalog: Kiểm tra & giữ tồn kho (reserve) từng ProductVariant trong Cart (BR-02) alt Đủ tồn kho Catalog-->>CartOrder: reserved OK (InventoryReserved) CartOrder->>CartOrder: Tách Cart đa seller thành Order (cha) + nhiều OrderSeller theo seller_id (BR-01) CartOrder->>CartOrder: Lưu Order, OrderSeller, OrderItem (status=pending_payment) CartOrder-->>Web: 201 { parentOrderId, orders[], paymentRedirectUrl? } Web->>Payment: POST /v1/payments (orderId, method, Idempotency-Key) Payment->>VNPay: Khởi tạo giao dịch (redirect URL) VNPay-->>Payment: paymentRedirectUrl Payment-->>Web: paymentRedirectUrl Customer->>VNPay: Thanh toán trên trang gateway VNPay->>Payment: POST /v1/payments/webhooks/vnpay (IPN, checksum) Payment->>Payment: Xác thực chữ ký; kiểm tra timestamp lệch <=5 phút so với giờ nhận (chống replay — quá hạn thì từ chối, 400 ERR_VALIDATION, không xử lý); kiểm tra idempotency theo gatewayTransactionRef (đã ghi nhận trước đó → 200 OK, không lặp side-effect); nếu hợp lệ, cập nhật Payment.status=success (v3 — mục 4.1.6) Payment->>MQ: publish PaymentConfirmed(orderId) MQ->>CartOrder: consume PaymentConfirmed → Order/OrderSeller.status=confirmed MQ->>Catalog: consume PaymentConfirmed → chuyển reserved → trừ kho thật (commit) MQ->>Commission: consume PaymentConfirmed → tạo CommissionTransaction (BR-03, tạm ghi nhận, chưa release) MQ->>Notify: consume PaymentConfirmed → gửi email/SMS xác nhận đơn hàng (FR-12) else Không đủ tồn kho Catalog-->>CartOrder: 409 ERR_CONFLICT (insufficient stock) CartOrder-->>Web: 409 ERR_CONFLICT — yêu cầu điều chỉnh giỏ hàng end Note over Web,Payment: Các endpoint tra cứu sau đó — GET /v1/orders/{orderId}, GET /v1/payments/{paymentId} — đều kiểm tra ownership (customerId trong JWT phải khớp chủ đơn); không khớp → 403 ERR_FORBIDDEN_OWNERSHIP (mục 4.1.1, v3) ``` ### 6.1.2 Xử lý đơn & vận chuyển (FR-19, FR-26, FR-14 điểm thưởng, FR-22 khởi tạo hold) ```mermaid sequenceDiagram actor Seller actor Ops as Ops/Warehouse participant SellerPortal as Seller Portal participant CartOrder as Cart & Order Service participant Shipping as Shipping & Fulfillment Service participant GHN as GHN/GHTK participant MQ as Message Broker participant Commission as Commission & Payout Service participant Loyalty as Promotion & Loyalty Service participant Notify as Notification Service Seller->>SellerPortal: Xác nhận đơn con của mình SellerPortal->>CartOrder: PATCH /v1/seller/orders/{orderId}/status (confirmed) CartOrder->>CartOrder: Ghi OrderStatusHistory, OrderSeller.status=confirmed CartOrder->>MQ: publish OrderSellerConfirmed MQ->>Shipping: consume → tạo yêu cầu fulfillment (status=created) Ops->>Shipping: GET/PATCH /v1/ops/orders/{orderId}/fulfillment (đóng gói xong → packed) Shipping->>GHN: POST /v1/ops/shipments (tạo vận đơn) GHN-->>Shipping: tracking_number Shipping->>CartOrder: cập nhật OrderSeller.status=shipped (qua event OrderShipped) GHN->>Shipping: POST /v1/webhooks/ghn (cập nhật in_transit/delivered, idempotent) Shipping->>Shipping: Ghi ShipmentEvent, cập nhật Shipment.status alt status=delivered Shipping->>MQ: publish OrderDelivered(orderSellerId, deliveredAt, categoryId) MQ->>CartOrder: consume → OrderSeller.status=delivered MQ->>Commission: consume → tạo PayoutHold, hold_until_date = deliveredAt + holdDays (BR-04) MQ->>Loyalty: consume → tính & ghi LoyaltyTransaction earn (BR-06) MQ->>Notify: consume → thông báo giao hàng thành công cho Customer end Note over GHN,Shipping: Nếu GHN timeout — fallback thử GHTK hoặc đưa vào hàng đợi Ops xử lý thủ công (BR-15, theo mục 3.4) ``` ### 6.1.3 Đổi trả & xử lý tranh chấp (FR-09, FR-25) ```mermaid sequenceDiagram actor Customer participant Web as Web Storefront participant CartOrder as Cart & Order Service participant MQ as Message Broker actor CSR participant AdminBO as Admin/CSR Backoffice participant Payment as Payment Service participant Commission as Commission & Payout Service participant Notify as Notification Service Customer->>Web: Yêu cầu đổi trả cho Order đã giao Web->>CartOrder: POST /v1/orders/{orderId}/return-requests (reason) CartOrder->>CartOrder: Tạo ReturnRequest (status=requested) CartOrder->>MQ: publish ReturnRequested MQ->>Commission: consume → nếu PayoutHold liên quan đang holding, chuyển release_status=disputed_frozen (BR-14a) MQ->>CartOrder: (nếu seller từ chối/không phản hồi trong SLA) tạo Dispute (status=open, assigned_csr_id=null) CSR->>AdminBO: GET /v1/admin/disputes (danh sách cần xử lý) CSR->>AdminBO: Điều tra: xem lịch sử Order, trao đổi Customer/Seller (status=investigating) CSR->>CartOrder: PATCH /v1/admin/disputes/{disputeId} (quyết định: refund/reject/escalate) Note over CartOrder,MQ: Quyết định dispute (refund/reject/escalate) phát event ghi audit_log tại Audit & Compliance Service (actor=CSR/Admin, action=dispute_decision, resource=disputeId) — mục 5.2.11 (v3) alt Quyết định hoàn tiền (refund) CartOrder->>MQ: publish DisputeResolved(decision=refund) MQ->>Payment: consume → khởi tạo hoàn tiền qua VNPay/Momo API (hoặc điều chỉnh COD) MQ->>Commission: consume → PayoutHold liên quan không được release (loại khỏi kỳ payout — xem Finding mục 6.6) CartOrder->>CartOrder: ReturnRequest.status=refunded, OrderSeller.status=returned else Từ chối khiếu nại (reject) CartOrder->>MQ: publish DisputeResolved(decision=reject) MQ->>Commission: consume → PayoutHold.release_status=holding (chờ đến hold_until_date để release bình thường) CartOrder->>CartOrder: ReturnRequest.status=rejected end MQ->>Notify: consume DisputeResolved → thông báo kết quả cho Customer và Seller ``` ### 6.1.4 Tính hoa hồng & payout định kỳ có kỳ giữ tiền (FR-20, FR-21, FR-22) ```mermaid sequenceDiagram participant Scheduler as Weekly Payout Job (cron) participant Commission as Commission & Payout Service participant DB as Commission & Payout DB actor Admin as Platform Admin participant AdminBO as Admin Backoffice participant Bank as Ngân hàng (batch transfer) participant Notify as Notification Service actor Seller participant SellerPortal as Seller Portal Scheduler->>Commission: Trigger payout run (hàng tuần) Commission->>DB: SELECT PayoutHold WHERE release_status='holding' AND hold_until_date<=today loop Với mỗi PayoutHold đủ điều kiện Commission->>DB: Kiểm tra không có Dispute đang open/investigating cho order_seller liên quan alt Không có tranh chấp mở Commission->>DB: release_status='released'; cộng CommissionTransaction.net_amount vào batch payout của seller else Có tranh chấp mở Commission->>DB: giữ nguyên 'holding' (chờ CSR xử lý xong — xem 6.1.3) end end Commission->>DB: Tạo Payout (status=scheduled) theo seller, period_start/period_end Commission->>DB: Lấy SellerBankAccount đang active Commission->>Bank: Gửi batch file chuyển khoản (Payout.status=processing) — kênh truyền: SFTP + mã hoá PGP hoặc API HTTPS của ngân hàng đối tác (v3, xem ghi chú giả định bên dưới) Bank-->>Commission: Kết quả xử lý batch (ack/reject theo dòng) alt Chuyển khoản thành công Commission->>DB: Payout.status='paid', paid_at=now Commission->>Notify: publish PayoutCompleted → thông báo Seller else Thất bại (sai thông tin NH, bị NH từ chối) Commission->>DB: Payout.status='failed' Commission->>AdminBO: Cảnh báo Admin — không tự động thử lại (tránh double-payout) Admin->>Commission: POST /v1/admin/payouts/{payoutId}/retry (thủ công, sau khi xác minh) Note over Commission: Retry payout ghi audit_log (actor=Admin, action=payout_retry, resource=payoutId) — mục 5.2.11 (v3) end Seller->>SellerPortal: GET /v1/seller/payouts (xem lịch sử/trạng thái) Admin->>AdminBO: GET /v1/admin/payouts (giám sát toàn sàn theo kỳ) ``` > **(v3)** Kênh truyền batch file payout tới ngân hàng: **giả định** dùng SFTP với mã hoá PGP cho file định dạng chuẩn ngân hàng nội địa, hoặc API HTTPS của ngân hàng đối tác (nếu ngân hàng hỗ trợ) — **ngân hàng đối tác và chuẩn kết nối cụ thể chưa được chốt trong brief**, cần chủ dự án/đối tác ngân hàng xác nhận trước go-live (ảnh hưởng cách hiện thực `Commission & Payout Service` gọi ra bên ngoài, xem mục 3 tích hợp bên thứ ba). > > **(v3)** Hành động cấu hình `CommissionRule`/`holdDays` (`PUT /v1/admin/commission-rules/{categoryId}`, mục 4.1.8) và khoá/mở khoá `Seller` (`PATCH /v1/admin/sellers/{sellerId}/status`, mục 4.1.7) là CRUD đơn giản nên không có sequence diagram riêng, nhưng đều là hành động nhạy cảm — mỗi lần ghi đều phát event ghi `audit_log` (actor, `before_json`/`after_json`, resource) tại Audit & Compliance Service, theo mục 5.2.11. ### 6.1.5 Seller onboarding & KYC (FR-17, FR-23) ```mermaid sequenceDiagram actor Seller participant SellerPortal as Seller Portal participant SellerSvc as Seller Management Service participant S3 as S3 (KYC bucket) actor Admin participant AdminBO as Admin Backoffice participant MQ as Message Broker participant Notify as Notification Service Seller->>SellerPortal: Đăng ký gian hàng SellerPortal->>SellerSvc: POST /v1/sellers/register SellerSvc->>SellerSvc: Tạo Seller (status=pending_kyc) Seller->>SellerPortal: Upload giấy phép kinh doanh/CMND SellerPortal->>SellerSvc: POST /v1/sellers/{sellerId}/kyc-documents (multipart) SellerSvc->>S3: Lưu file (mã hoá at-rest) SellerSvc->>SellerSvc: Tạo KYCDocument (verified_status=pending) cho từng document_type bắt buộc Admin->>AdminBO: GET /v1/admin/sellers?status=pending_kyc Admin->>SellerSvc: GET /v1/admin/sellers/{sellerId}/kyc-documents/{documentId}/view-url (v3 — yêu cầu link xem tài liệu; endpoint cần bổ sung ở mục 4, xem Finding 6.6) SellerSvc->>S3: Sinh pre-signed URL, quyền đọc duy nhất object đó, TTL <= 5 phút (v3) S3-->>SellerSvc: presignedUrl (hết hạn sau tối đa 300 giây) SellerSvc-->>Admin: 200 { viewUrl, expiresInSeconds<=300 } — Admin không được cấp quyền truy cập trực tiếp bucket/object storage Admin->>AdminBO: Mở viewUrl trong trình duyệt, đối chiếu từng KYCDocument thủ công (không auto-approve — BR-13) Admin->>SellerSvc: PATCH /v1/admin/sellers/{sellerId}/kyc-review (approved|rejected, reason) Note over SellerSvc,MQ: Quyết định duyệt/từ chối KYC phát event ghi audit_log (actor=Admin, action=kyc_review, resource=kycDocumentId/sellerId) — mục 5.2.11 (v3) alt Tất cả document bắt buộc đều verified SellerSvc->>SellerSvc: Seller.status=active SellerSvc->>MQ: publish SellerApproved else Có document bị rejected SellerSvc->>SellerSvc: Seller.status=rejected (giữ pending_kyc nếu seller có thể nộp lại) SellerSvc->>MQ: publish SellerRejected(reason) end MQ->>Notify: gửi email kết quả duyệt cho Seller Seller->>SellerPortal: GET /v1/sellers/{sellerId}/kyc-status (tự kiểm tra) ``` ### 6.1.6 Đăng nhập, MFA và Social login (FR-01, FR-02, FR-27) ```mermaid sequenceDiagram actor User as Customer/Seller/Admin participant Web as Web/Seller/Admin Portal participant IDSvc as Identity & Access Service participant DB as Identity DB User->>Web: Nhập email/password Web->>IDSvc: POST /v1/auth/login IDSvc->>DB: Đọc user_account (password_hash, role, mfa_enabled, failed_login_count, locked_until) — v3 alt Tài khoản đang bị khoá (locked_until > now) — v3 IDSvc-->>Web: 401 sai thông tin đăng nhập / tài khoản tạm khoá do đăng nhập sai nhiều lần (mã lỗi cụ thể và khoảng thời gian khoá do mục 8 — security-architect quy định) else Không bị khoá IDSvc->>IDSvc: So khớp password_hash alt Mật khẩu sai — v3 IDSvc->>DB: Tăng failed_login_count += 1, ghi last_failed_login_at=now alt failed_login_count vượt ngưỡng cho phép (ngưỡng cụ thể do mục 8 quy định) — v3 IDSvc->>DB: Đặt locked_until = now + khoảng thời gian khoá (khoảng thời gian do mục 8 quy định) end IDSvc-->>Web: 401 sai thông tin đăng nhập else Mật khẩu đúng IDSvc->>DB: Reset failed_login_count=0, last_failed_login_at=null — v3 alt role=platform_admin (bắt buộc MFA) hoặc role=seller có mfa_enabled=true IDSvc-->>Web: 200 { mfaRequired:true, mfaChallengeToken, mfaMethod } Web->>User: Yêu cầu nhập mã OTP User->>Web: Nhập OTP (TOTP/SMS) Web->>IDSvc: POST /v1/auth/mfa/challenge (mfaChallengeToken, otp) IDSvc->>DB: Xác minh MFA_DEVICE.secret_encrypted IDSvc-->>Web: 200 { accessToken, refreshToken } else Không cần MFA (Customer, hoặc Seller chưa bật MFA) IDSvc-->>Web: 200 { accessToken, refreshToken } end end end Note over User,IDSvc: Luồng Social login (FR-02, v3): User chọn "Đăng nhập Google/Facebook" → redirect OAuth2 kèm tham số state (sinh ngẫu nhiên, lưu tạm phía server) → provider → POST /v1/auth/oauth/{provider}/callback (state, code) → IDSvc xác thực state khớp giá trị đã phát hành (thiếu/không khớp → 400 ERR_OAUTH_STATE_INVALID, chống CSRF) → nếu email trả về từ provider đã có tài khoản email/password đăng ký sẵn (chưa liên kết OAuth), KHÔNG tự động merge (no auto-merge) → trả 409 ERR_ACCOUNT_LINK_REQUIRED, yêu cầu xác minh sở hữu email trước khi liên kết → nếu email chưa tồn tại, tạo Customer mới liên kết OAuthIdentity → phát hành accessToken/refreshToken tương tự trên ``` ## 6.2 Sơ đồ lớp (Class Diagram) & Trạng thái (State Diagram) ### 6.2.1 Class Diagram — Cart & Order domain (FR-05, FR-06, FR-08, FR-09) ```mermaid classDiagram class Cart { +UUID id +UUID customerId +String sessionId +String status +addItem(productVariantId, sellerId, quantity) +applyCoupon(code) } class CartItem { +UUID id +UUID cartId +UUID productVariantId +UUID sellerId +int quantity +Decimal unitPriceSnapshot } class Order { +UUID id +UUID customerId +String orderNumber +Decimal totalAmount +String status +splitBySeller() OrderSeller[] +cancel() } class OrderSeller { +UUID id +UUID orderId +UUID sellerId +String subOrderNumber +Decimal subtotalAmount +String status +confirm() +markShipped() +markDelivered() } class OrderItem { +UUID id +UUID orderSellerId +UUID productVariantId +int quantity +Decimal unitPrice +Decimal lineTotal } class ReturnRequest { +UUID id +UUID orderSellerId +UUID customerId +String status +String reason } class Dispute { +UUID id +UUID orderSellerId +String raisedBy +UUID assignedCsrId +String status +resolve(decision) } Cart "1" *-- "many" CartItem Order "1" *-- "many" OrderSeller OrderSeller "1" *-- "many" OrderItem OrderSeller "1" o-- "0..1" ReturnRequest OrderSeller "1" o-- "0..*" Dispute ``` ### 6.2.2 Class Diagram — Commission & Payout domain (FR-20, FR-21, FR-22) ```mermaid classDiagram class CommissionRule { +UUID id +UUID categoryId +Decimal commissionPercent +Date effectiveFrom +Date effectiveTo +calculateCommission(grossAmount) Decimal } class CommissionTransaction { +UUID id +UUID orderSellerId +UUID sellerId +Decimal grossAmount +Decimal commissionAmount +Decimal netAmount } class Payout { +UUID id +UUID sellerId +Date periodStart +Date periodEnd +Decimal totalNetAmount +String status +submitToBank() +markPaid() +markFailed() } class PayoutHold { +UUID id +UUID commissionTransactionId +Date holdUntilDate +String releaseStatus +release() +freeze() } class Seller { +UUID id +String status +approve() +suspend() } CommissionRule "1" --> "many" CommissionTransaction : applies CommissionTransaction "1" --> "0..1" PayoutHold : held_by CommissionTransaction "many" --> "1" Payout : settled_in Seller "1" --> "many" Payout : receives ``` ## 6.3 State Diagram — vòng đời entity nhiều trạng thái ### 6.3.1 OrderSeller (FR-06, FR-08, FR-09, FR-19) ```mermaid stateDiagram-v2 [*] --> pending: Checkout thành công (Cart & Order Service) pending --> confirmed: Seller xác nhận (PATCH /v1/seller/orders/{orderId}/status) hoặc auto sau PaymentConfirmed pending --> cancelled: Customer huỷ (BR-10) hoặc hết hạn thanh toán confirmed --> cancelled: Customer huỷ trong điều kiện cho phép (BR-10) — Seller/CSR cũng có thể huỷ khi hết hàng confirmed --> packed: Ops đóng gói xong (PATCH /v1/ops/orders/{orderId}/fulfillment) packed --> shipped: Shipping & Fulfillment Service tạo vận đơn GHN/GHTK thành công shipped --> delivered: Webhook GHN/GHTK báo giao thành công delivered --> returned: CSR/Admin duyệt ReturnRequest (refund) — kích hoạt bởi Dispute resolution (FR-25) cancelled --> [*] returned --> [*] delivered --> [*]: Hết thời gian khiếu nại, đơn coi như hoàn tất ``` ### 6.3.2 Payment (FR-07) ```mermaid stateDiagram-v2 [*] --> pending: POST /v1/payments khởi tạo giao dịch pending --> success: Webhook VNPay/Momo xác nhận thành công (chữ ký hợp lệ) pending --> failed: Webhook báo thất bại hoặc timeout không có callback (qua job đối soát, mục 3.4) success --> refunded: CSR/Admin duyệt hoàn tiền sau Dispute resolution (FR-25) failed --> [*] success --> [*] refunded --> [*] ``` ### 6.3.3 Seller — trạng thái KYC/hoạt động (FR-17, FR-23) ```mermaid stateDiagram-v2 [*] --> pending_kyc: Seller đăng ký (POST /v1/sellers/register) pending_kyc --> active: Admin duyệt toàn bộ KYCDocument bắt buộc (PATCH .../kyc-review, chỉ Admin) pending_kyc --> rejected: Admin từ chối KYC (chỉ Admin), Seller có thể nộp lại → về pending_kyc rejected --> pending_kyc: Seller nộp lại giấy tờ active --> suspended: Admin khoá do vi phạm (PATCH /v1/admin/sellers/{sellerId}/status, chỉ Admin) suspended --> active: Admin mở khoá sau xác minh (chỉ Admin) ``` > **(v3)** Mọi chuyển trạng thái do Admin thực hiện ở trên (`pending_kyc→active`, `pending_kyc→rejected`, `active↔suspended`) đều phát event ghi `audit_log` (actor=Admin, action tương ứng, resource=sellerId) tại Audit & Compliance Service — mục 5.2.11. ### 6.3.4 ReturnRequest (FR-09) ```mermaid stateDiagram-v2 [*] --> requested: Customer gửi yêu cầu (POST .../return-requests) requested --> approved: CSR/Admin hoặc Seller đồng ý đổi trả requested --> rejected: CSR/Admin hoặc Seller từ chối (có thể mở Dispute nếu Customer không đồng ý) approved --> refunded: Payment Service hoàn tất hoàn tiền rejected --> [*] refunded --> [*] ``` ### 6.3.5 Dispute (FR-25) ```mermaid stateDiagram-v2 [*] --> open: Tạo tự động khi Seller từ chối/không phản hồi ReturnRequest trong SLA, hoặc Customer/Seller khiếu nại trực tiếp open --> investigating: CSR nhận xử lý (assigned_csr_id được gán) investigating --> resolved: CSR/Admin ra quyết định (refund/reject) — chỉ CSR/Admin investigating --> escalated: CSR chuyển cấp cao hơn (Admin) khi vượt thẩm quyền escalated --> resolved: Admin ra quyết định cuối cùng resolved --> [*] ``` ### 6.3.6 Payout & PayoutHold (FR-22) ```mermaid stateDiagram-v2 [*] --> holding: PayoutHold tạo khi nhận event OrderDelivered (hold_until_date = deliveredAt + holdDays, BR-04) holding --> disputed_frozen: Dispute được mở cho order_seller liên quan trước hold_until_date (chỉ hệ thống, tự động qua event) disputed_frozen --> holding: Dispute resolved với quyết định "reject" (từ chối khiếu nại) — chờ đến hold_until_date bình thường holding --> released: Job payout hàng tuần release khi hold_until_date đã qua và không còn Dispute mở (chỉ hệ thống/Commission & Payout Service) disputed_frozen --> [*]: Dispute resolved với quyết định "refund" — hoa hồng bị loại khỏi payout vĩnh viễn (xem Finding 6.4 — cần bổ sung trạng thái kết thúc rõ ràng ở mục 5) ``` ```mermaid stateDiagram-v2 [*] --> scheduled: Commission & Payout Service tạo Payout theo kỳ (chỉ hệ thống, job hàng tuần) scheduled --> processing: Gửi batch file chuyển khoản tới Ngân hàng processing --> paid: Ngân hàng xác nhận chuyển thành công processing --> failed: Ngân hàng từ chối/lỗi định dạng failed --> processing: Admin xác nhận thủ công và gọi POST /v1/admin/payouts/{payoutId}/retry (chỉ Admin, không tự động) paid --> [*] ``` ## 6.4 Logic nghiệp vụ (Business Rules) | Mã | FR liên quan | Mô tả quy tắc | |---|---|---| | BR-01 | FR-06 | **Tách đơn theo seller:** khi checkout, `Cart` (nhiều `CartItem` từ nhiều seller) được nhóm theo `seller_id`; mỗi nhóm sinh ra một `OrderSeller` con thuộc `Order` cha; `Order.totalAmount` = tổng `OrderSeller.subtotalAmount`; mỗi `OrderSeller` có vòng đời trạng thái độc lập (xem 6.3.1) vì mỗi seller xử lý/giao hàng riêng. | | BR-02 | FR-05, FR-06, FR-18 | **Giữ tồn kho khi checkout (chống oversell):** tại thời điểm `POST /v1/checkout`, hệ thống tăng `inventory_stock.quantity_reserved` và kiểm tra `quantity_available - quantity_reserved >= quantity` cho từng `ProductVariant`; nếu không đủ, trả `409 ERR_CONFLICT` trước khi tạo `Order`. Sau khi `PaymentConfirmed`, phần reserved được commit trừ vào `quantity_available` thật; nếu thanh toán thất bại/timeout, phần reserved được nhả lại (release) sau một khoảng thời gian chờ. | | BR-03 | FR-21 | **Tính hoa hồng:** `commissionAmount = orderItem.lineTotal × commissionRule.commissionPercent / 100`, trong đó `commissionRule` là bản ghi `CommissionRule` có `effective_from <= orderDate` và (`effective_to` là null hoặc `>= orderDate`) cho `category_id` tương ứng sản phẩm; `netAmount = grossAmount − commissionAmount`. Nếu một `Category` chưa có `CommissionRule` nào hiệu lực, hệ thống chặn seller đăng bán sản phẩm thuộc category đó cho tới khi Admin cấu hình (ràng buộc bổ sung, cần Admin xác nhận trước go-live). | | BR-04 | FR-22 | **Kỳ giữ tiền (payout hold) — chốt giá trị mặc định + cấu hình theo ngành hàng:** brief chỉ xác nhận cơ chế "3-7 ngày sau giao hàng thành công" như một khoảng, không có giá trị cụ thể. Để Commission & Payout Service vận hành được, thiết kế chốt: **giá trị mặc định toàn sàn = 5 ngày** (điểm giữa khoảng 3-7, cân bằng giữa bảo vệ quyền lợi đổi trả của khách và dòng tiền của seller), và **cho phép Admin cấu hình số ngày hold khác nhau theo từng `Category`** (VD ngành hàng tỷ lệ đổi trả cao như thời trang có thể đặt 7 ngày; ngành hàng ít đổi trả như thực phẩm có thể đặt 3 ngày). Pseudo-code:
`holdDays = CommissionRule.findByCategory(categoryId).holdDays`
`if holdDays is null: holdDays = PLATFORM_DEFAULT_HOLD_DAYS # = 5`
`PayoutHold.hold_until_date = OrderDelivered.deliveredAt + holdDays days`
**Đây là giả định mặc định cần chủ dự án xác nhận** trước go-live (số ngày cụ thể + có nên giới hạn admin trong khoảng 3-7 hay cho phép vượt khoảng cho ngành hàng đặc thù) — xem `openQuestions` và Finding bên dưới (cần bổ sung cột `hold_days` ở mục 5 và field tương ứng ở endpoint mục 4). | | BR-05 | FR-22 | **Điều kiện release payout:** job hàng tuần chỉ release `PayoutHold` khi `hold_until_date <= ngày chạy job` **và** không tồn tại `Dispute` ở trạng thái `open`/`investigating` cho `OrderSeller` liên quan; nếu có Dispute mở, giữ nguyên `holding` (hoặc chuyển `disputed_frozen`) cho đến khi Dispute được `resolved`. Một `Payout` gộp toàn bộ `CommissionTransaction.netAmount` đã released trong kỳ của một seller thành một lần chuyển khoản (không chuyển riêng từng đơn) — theo brief "payout hàng tuần". | | BR-06 | FR-14 | **Tích điểm loyalty:** `pointsEarned = floor(orderSeller.subtotalAmount / 10000) × 1`, ghi nhận khi nhận event `OrderDelivered` (không tích điểm khi mới đặt hàng, tránh gian lận huỷ đơn sau khi tích). *Giả định cần xác nhận:* brief ghi "1 điểm/10.000đ giá trị đơn hàng" nhưng không nói rõ tính trên `Order` cha hay từng `OrderSeller`, và có trừ phí vận chuyển/giảm giá coupon hay không — thiết kế tạm tính trên `subtotalAmount` (đã trừ giảm giá) của từng `OrderSeller`, chưa gồm phí ship — xem `openQuestions`. | | BR-07 | FR-14 | **Xếp hạng thành viên (tier):** `LoyaltyAccount.total_spend_12m` là tổng chi tiêu (theo `subtotalAmount` các đơn `delivered`) trong cửa sổ trượt 12 tháng gần nhất, được tính lại bởi batch job định kỳ (đề xuất: hằng đêm) vì đơn hàng cũ hơn 12 tháng phải rớt khỏi cửa sổ tính toán, không chỉ cộng dồn một chiều. Tier được gán theo ngưỡng `MembershipTier.min_spend_threshold` (Bạc < Vàng < Kim Cương). *Giả định cần xác nhận:* brief xác nhận có 3 hạng nhưng **không cho số VND ngưỡng cụ thể** cho từng hạng — xem `openQuestions`. | | BR-08 | FR-14 | **Đổi điểm lấy giảm giá:** `100 điểm = 10.000đ`; chỉ cho đổi theo bội số 100 điểm; điểm đổi được áp làm giảm giá cho `Cart`/`Order` hiện tại qua `POST /v1/customers/me/loyalty/redeem`, ghi `LoyaltyTransaction(type=redeem, points=-N)`; không cho đổi vượt quá `points_balance` hiện có. | | BR-09 | FR-13 | **Điều kiện áp dụng Promotion/coupon:** `promotion.status='active'`, `valid_from <= now <= valid_to`, số lượt đã dùng (đếm từ `promotion_usage`) `< usage_limit` (nếu có), và `cart.subtotal >= min_order_amount` (nếu có). Giảm giá tính theo `type` (`percent`: `value%` trên subtotal; `fixed_amount`: trừ thẳng `value`, không âm). Mỗi coupon chỉ áp dụng một lần cho một `Order` (`UNIQUE(promotion_id, order_id)`). | | BR-10 | FR-08 | **Điều kiện huỷ đơn (Customer tự huỷ):** chỉ cho phép khi `OrderSeller.status` ∈ {`pending`, `confirmed`} (chưa đóng gói); từ `packed` trở đi, Customer phải gửi yêu cầu qua đổi trả/khiếu nại (FR-09/FR-25) thay vì huỷ trực tiếp. *Giả định:* brief/FR-08 chỉ nói "huỷ đơn (trong điều kiện cho phép)" mà không định nghĩa ngưỡng chính xác — mốc `packed` là giả định hợp lý theo luồng vận hành (mục 6.1.2), cần chủ dự án xác nhận (xem Finding tồn đọng §0.4d). | | BR-11 | FR-11 | **Điều kiện được đánh giá sản phẩm:** Customer chỉ được tạo `Review` cho một `order_item_id` khi `OrderSeller.status = delivered` (đã nhận hàng) và tồn tại `order_item` thuộc `customer_id` đó; ràng buộc `UNIQUE(customer_id, order_item_id)` đảm bảo mỗi lượt mua chỉ đánh giá một lần (khớp mục 5.2.8). | | BR-12 | FR-27 | **Chính sách MFA:** `role='platform_admin'` → bắt buộc `mfa_enabled=true`, chặn hoàn toàn truy cập scope `admin:*` cho đến khi hoàn tất `mfa/enroll`; `role='seller'` → khuyến khích, không chặn đăng nhập nhưng Seller Portal hiển thị nhắc bật MFA liên tục cho đến khi bật; `role='customer'` → không áp dụng MFA ở MVP. **(v3)** Ngoài MFA, đăng nhập sai mật khẩu liên tiếp làm tăng `user_account.failed_login_count`; vượt ngưỡng (do mục 8 quy định) → đặt `locked_until` tạm khoá đăng nhập — xem sequence 6.1.6. | | BR-13 | FR-17 | **Duyệt KYC thủ công, không auto-approve:** `Seller.status` chỉ chuyển `active` khi **toàn bộ** `KYCDocument` bắt buộc (`business_license`, `id_card_front`, `id_card_back`) có `verified_status='verified'`, mỗi tài liệu được một Admin xem xét và duyệt riêng lẻ (không có quy tắc tự động duyệt theo brief — marketplace xác nhận "admin duyệt thủ công"). Nếu bất kỳ tài liệu nào `rejected`, `Seller.status='rejected'` kèm `reason`, Seller có thể nộp lại. **(v3 — theo review mục 8)** Admin xem nội dung `KYCDocument` qua pre-signed URL sinh bởi `Seller Management Service`, TTL tối đa 5 phút, không truy cập trực tiếp object storage; mọi quyết định duyệt/từ chối ghi `audit_log` (xem sequence 6.1.5). | | BR-14 | FR-09, FR-25 | **Xử lý tranh chấp — nguyên tắc chung (không có công thức hoàn tiền cụ thể trong brief):** (a) khi `ReturnRequest` được tạo hoặc `Dispute` mở, `PayoutHold` liên quan (nếu còn `holding`) được tự động chuyển `disputed_frozen` để tránh giải ngân trước khi có quyết định cuối; (b) quyết định `refund`/`reject` chỉ do CSR/Admin thực hiện qua `PATCH /v1/admin/disputes/{disputeId}` (ghi `audit_log`, xem 6.1.3); (c) khi `refund`, `Payment.status` chuyển `refunded` và hoa hồng tương ứng bị loại khỏi payout. **Brief không quy định**: mức hoàn tiền (toàn phần/một phần theo tỷ lệ đã sử dụng), ai chịu phí vận chuyển hoàn trả, và SLA phản hồi của seller trước khi hệ thống tự mở Dispute — đây là **openQuestions**, không tự đặt công thức cụ thể (xem finding mới trong §0 — chưa được người duyệt xem xét). | | BR-15 | FR-26 | **Fallback vận chuyển:** khi tạo vận đơn qua GHN timeout/lỗi sau tối đa 3 lần retry (theo mục 3.4), hệ thống thử tạo lại qua GHTK nếu khu vực giao hàng được GHTK hỗ trợ; nếu cả hai đều lỗi, đưa vào hàng đợi để Ops xử lý thủ công, không chặn trạng thái `OrderSeller` (vẫn giữ `confirmed`/`packed` chờ xử lý). | ## 6.5 Ma trận truy vết bổ sung cho mục 2.4 | FR | Sequence/State/Business Rule liên quan | |---|---| | FR-01 | 6.1.6 (Sequence đăng nhập) | | FR-02 | 6.1.6 (Social login) | | FR-05 | 6.1.1 (Checkout), BR-02 | | FR-06 | 6.1.1, BR-01, State 6.3.1 | | FR-07 | 6.1.1, State 6.3.2 | | FR-08 | State 6.3.1, BR-10 | | FR-09 | 6.1.3, State 6.3.4, BR-14 | | FR-11 | BR-11 | | FR-12 | 6.1.1, 6.1.2 (Notification qua event) | | FR-13 | 6.1.1, BR-09 | | FR-14 | 6.1.2, BR-06, BR-07, BR-08 | | FR-17 | 6.1.5, State 6.3.3, BR-13 | | FR-18 | 6.1.1, BR-02 | | FR-19 | 6.1.2, State 6.3.1 | | FR-20 | 6.1.4, Class Diagram 6.2.2 | | FR-21 | 6.1.4, BR-03 | | FR-22 | 6.1.4, State 6.3.6, BR-04, BR-05 | | FR-23 | State 6.3.3 | | FR-25 | 6.1.3, State 6.3.5, BR-14 | | FR-26 | 6.1.2, BR-15 | | FR-27 | 6.1.6, BR-12 | > Các FR không xuất hiện ở trên (FR-03, FR-04, FR-10, FR-15, FR-16, FR-24) là các luồng CRUD/tra cứu/cross-cutting đơn giản, không có rẽ nhánh nghiệp vụ đáng kể cần sequence/state diagram riêng — đã được đặc tả đầy đủ qua endpoint mục 4 và schema mục 5. ## 6.6 Findings (nhắm mục 4/5) 1. **[severity: medium — đã giải quyết ở mục 4/5 v3]** Bảng `commission_rule` (mục 5.2.6) trước đây (v1) thiếu cột lưu số ngày hold theo ngành hàng (BR-04); mục 5 v3 đã bổ sung `commission_rule.hold_days` (nullable, fallback mặc định 5 ngày) và mục 4 v3 đã bổ sung field `holdDays` ở `GET/PUT /v1/admin/commission-rules` (mục 4.1.8). Không còn khoảng trống — giữ lại mục này như ghi nhận lịch sử. 2. **[severity: medium — đã giải quyết ở mục 4/5 v3]** Enum `payout_hold.release_status` (mục 5.2.6) trước đây (v1) thiếu trạng thái kết thúc rõ ràng cho trường hợp Dispute được duyệt hoàn tiền; mục 5 v3 đã bổ sung trạng thái kết thúc `reversed` để phân biệt với `disputed_frozen` (tạm giữ). Không còn khoảng trống — giữ lại mục này như ghi nhận lịch sử. 3. **[severity: low]** Bảng `membership_tier` (mục 5.2.7) có cột `min_spend_threshold` nhưng brief/mục 2 không cung cấp giá trị VND cụ thể cho từng hạng Bạc/Vàng/Kim Cương — cần chủ dự án xác nhận trước khi seed dữ liệu (liên quan BR-07; xem Finding tồn đọng §0.4c). 4. **[severity: low]** FR-08 (mục 2) mô tả "huỷ đơn (trong điều kiện cho phép)" nhưng không định nghĩa ngưỡng trạng thái chính xác — BR-10 tạm giả định mốc `packed`, cần bổ sung rõ trong mục 2 hoặc xác nhận với chủ dự án (xem Finding tồn đọng §0.4d). 5. **[severity: low, mới — v2]** Mục 4 (4.1.7 Seller Management Service) hiện chưa có endpoint cho Admin lấy pre-signed URL để xem nội dung một `KYCDocument` cụ thể (chỉ có `POST .../kyc-documents` để upload và `PATCH .../kyc-review` để duyệt). Theo ghi chú người duyệt (findings bảo mật mục 8), cần bổ sung một endpoint dạng `GET /v1/admin/sellers/{sellerId}/kyc-documents/{documentId}/view-url` trả về `{ viewUrl, expiresInSeconds<=300 }` để Admin không truy cập trực tiếp object storage — xem sequence 6.1.5 (Finding tồn đọng F11, §0.4b). 6. **[severity: low, mới — v2]** Mục 4 (4.1.3 Identity & Access) chưa có mã lỗi cụ thể cho trường hợp tài khoản bị khoá tạm do vượt ngưỡng đăng nhập sai (`user_account.locked_until`, mục 5.2.1 v3) — hiện chỉ có `401 ERR_AUTH_REQUIRED`/`ERR_AUTH_INVALID_TOKEN`/`ERR_MFA_REQUIRED`. Đề xuất bổ sung mã lỗi riêng (VD `403/423 ERR_ACCOUNT_LOCKED`) tại mục 4 khi ngưỡng/khoảng thời gian khoá được chốt ở mục 8 (Finding tồn đọng F12, §0.4b). ## 6.7 Giả định (Assumptions) - Giá trị mặc định kỳ giữ tiền (payout hold) = **5 ngày** (giữa khoảng 3-7 ngày theo brief), có thể cấu hình khác theo từng `Category` — **cần chủ dự án xác nhận** trước go-live (BR-04). - Điểm loyalty tính trên `subtotalAmount` của từng `OrderSeller` (đã trừ giảm giá, chưa gồm phí vận chuyển), kích hoạt khi đơn `delivered` — cần xác nhận với chủ dự án (BR-06). - Ngưỡng huỷ đơn tự phục vụ của Customer dừng ở trạng thái `packed` — cần xác nhận (BR-10). - SLA phản hồi của Seller trước khi hệ thống tự động mở `Dispute` từ một `ReturnRequest` bị từ chối/không phản hồi chưa được định nghĩa số ngày cụ thể — tạm không đặt giá trị cứng, cần chủ dự án cung cấp. - **(v2, mới)** Kênh truyền batch file chuyển khoản payout tới ngân hàng: giả định SFTP + mã hoá PGP hoặc API HTTPS của ngân hàng đối tác — ngân hàng đối tác và chuẩn kết nối cụ thể **chưa được chốt** trong brief, cần xác nhận trước go-live (6.1.4). - **(v2, mới)** Ngưỡng số lần đăng nhập sai (`failed_login_count`) và khoảng thời gian khoá tài khoản (`locked_until`) trong luồng 6.1.6 **chưa có giá trị cụ thể** ở mục này — theo ghi chú người duyệt, đây là phạm vi của mục 8 (security-architect) quy định; thiết kế luồng chỉ mô tả cơ chế (đếm, khoá, mở khoá tự động), không tự đặt số. ## 6.8 Câu hỏi còn mở (Open Questions) - Số ngày hold payout chính xác (đã chốt giá trị mặc định 5 ngày + cơ chế cấu hình theo category ở BR-04) có cần giới hạn cứng trong khoảng 3-7 ngày hay cho phép Admin đặt ngoài khoảng này cho ngành hàng đặc thù? - Ngưỡng chi tiêu 12 tháng (VND) cụ thể cho từng hạng thành viên Bạc/Vàng/Kim Cương là bao nhiêu? - Công thức/mức hoàn tiền khi Dispute được duyệt: hoàn toàn phần hay theo tỷ lệ đã sử dụng? Ai chịu phí vận chuyển hoàn trả (Customer/Seller/Sàn)? - SLA cụ thể (số ngày) để Seller phản hồi một `ReturnRequest` trước khi hệ thống tự động leo thang thành `Dispute`? - Điểm loyalty tính trên giá trị đơn hàng gộp (`Order` cha) hay theo từng `OrderSeller` — và có gồm phí vận chuyển/thuế hay không? - **(v2, mới)** Ngân hàng đối tác cụ thể cho payout và chuẩn kết nối (SFTP+PGP nội bộ hay API HTTPS của ngân hàng) — cần chủ dự án/đối tác ngân hàng xác nhận (6.1.4). - **(v2, mới)** Ngưỡng `failed_login_count` và khoảng thời gian `locked_until` (khoá tài khoản tạm thời) cụ thể là bao nhiêu — cần mục 8 (security-architect) quy định để hoàn thiện luồng 6.1.6 và mã lỗi tương ứng ở mục 4. --- # 7. Thiết kế giao diện (UI/UX Design) ## 7.0 Nguyên tắc & phạm vi thiết kế - **Nền tảng:** chỉ thiết kế cho **web responsive** (desktop, tablet, mobile-web), theo profile dự án (`platforms: ["web"]`). Không thiết kế ứng dụng mobile app native (out-of-scope MVP, xem mục 1.1). - **Không có brand guideline cố định** (giả định #9, mục 1.4): tài liệu này **không quy định màu sắc/typography cụ thể**, chỉ mô tả cấu trúc bố cục, thành phần (component) và hành vi. Đội phát triển áp dụng một design system chuẩn (VD. Material Design hoặc Ant Design — xem NFR-07) làm nền tảng khi triển khai UI thật. - **Đa ngôn ngữ (FR-15/NFR-06):** mọi màn hình có text hiển thị đều phải dùng khóa i18n (không hard-code chuỗi), hỗ trợ VI (mặc định)/EN/ZH/KO/JA qua component `LanguageSwitcher` đặt cố định ở header. Riêng ZH/KO/JA cần rà soát độ dài chuỗi dịch có thể dài hơn tiếng Việt — layout cần co giãn được (không fix-width cho label). - **Đa tiền tệ (FR-16/NFR-06):** mọi nơi hiển thị giá đều hiển thị giá giao dịch chính bằng **VND** kèm giá quy đổi tham khảo (secondary display, không phải giá giao dịch) qua component `CurrencyToggle`/`PriceDisplay`. - **Phân quyền:** tài liệu này **không thiết kế lại RBAC** — mỗi màn hình chỉ tham chiếu nhóm người dùng đã định nghĩa ở mục 1.2 (Guest, Customer, Seller, PlatformAdmin, OpsStaff, CSR). Chi tiết ma trận quyền thuộc mục 8. - **Quy ước mã màn hình:** `SCR-xx`, nhóm theo persona. - **Quy ước trạng thái màn hình:** mỗi màn hình chính mô tả tối thiểu 3 trạng thái: *loading* (khung xương/skeleton hoặc spinner), *empty* (không có dữ liệu), *error* (lỗi tải dữ liệu/lỗi nghiệp vụ) — theo yêu cầu NFR-01 (phản hồi nhanh, cần loading state rõ ràng khi tải đỉnh). --- ## 7.1 Wireframe & Mockup (mô tả dạng văn bản) ### 7.1.1 Nhóm Khách vãng lai & Khách hàng (Guest / Customer) #### SCR-01 — Trang chủ & Danh mục sản phẩm - **Mục đích:** điểm vào chính, giới thiệu ngành hàng, khuyến mãi, sản phẩm nổi bật; cho phép chuyển ngôn ngữ/tiền tệ. - **Persona/Role:** Guest, Customer. - **FR phục vụ:** FR-04 (danh mục & tìm kiếm), FR-15 (đa ngôn ngữ), FR-16 (đa tiền tệ). - **Bố cục:** - Header (cố định): logo sàn; thanh tìm kiếm (autocomplete); `LanguageSwitcher` (FR-15); `CurrencyToggle` (FR-16, hiển thị tham khảo); icon giỏ hàng (badge số lượng); icon tài khoản/đăng nhập. - Section 1: banner khuyến mãi/carousel. - Section 2: điều hướng ngành hàng (category nav, dạng menu/mega-menu). - Section 3: lưới sản phẩm nổi bật — `ProductCard` (ảnh, tên, giá VND + giá quy đổi tham khảo, rating trung bình, tên/logo seller, badge "Ngành hàng"). - Footer: thông tin sàn, chính sách đổi trả, liên kết ngôn ngữ, thông tin tuân thủ (thông báo Bộ Công Thương — NFR-05). - **Trạng thái:** loading = skeleton lưới sản phẩm/banner; empty = ẩn section nếu không có sản phẩm nổi bật/khuyến mãi; error = banner lỗi "Không tải được dữ liệu, thử lại" + nút retry. - **Validation chính:** ô tìm kiếm yêu cầu tối thiểu 1 ký tự trước khi gợi ý; không cho submit tìm kiếm rỗng. #### SCR-02 — Kết quả tìm kiếm & Bộ lọc - **Mục đích:** hiển thị kết quả tìm kiếm/duyệt theo ngành hàng với bộ lọc đa chiều. - **Persona/Role:** Guest, Customer. - **FR phục vụ:** FR-04. - **Bố cục:** - Header: kế thừa SCR-01; thanh breadcrumb (Trang chủ > Ngành hàng > Từ khoá). - Sidebar trái (desktop) / bottom-sheet (mobile): bộ lọc — ngành hàng (category), khoảng giá, seller, rating, tình trạng còn hàng. - Vùng chính: thanh sắp xếp (giá tăng/giảm, mới nhất, bán chạy), lưới/danh sách `ProductCard`, phân trang hoặc infinite-scroll. - **Trạng thái:** loading = skeleton lưới; empty = "Không tìm thấy sản phẩm phù hợp" + gợi ý bỏ bớt bộ lọc; error = thông báo lỗi tìm kiếm + retry. - **Validation chính:** khoảng giá min ≤ max (nếu nhập tay); tối thiểu 1 bộ lọc category hợp lệ khi áp dụng. #### SCR-03 — Chi tiết sản phẩm - **Mục đích:** cung cấp đầy đủ thông tin sản phẩm để ra quyết định mua, xem đánh giá. - **Persona/Role:** Guest, Customer. - **FR phục vụ:** FR-04, FR-11 (hiển thị đánh giá), FR-16 (giá quy đổi). - **Bố cục:** - Section 1: gallery ảnh/video sản phẩm; chọn biến thể (SKU: size/màu — cập nhật tồn kho/giá theo lựa chọn). - Section 2: tên sản phẩm, giá VND + giá quy đổi tham khảo, rating tổng hợp + số lượt đánh giá, thông tin seller (link tới gian hàng), nút "Thêm vào giỏ" / "Mua ngay" / "Thêm vào Wishlist" (FR-10). - Section 3: mô tả chi tiết, thông số kỹ thuật. - Section 4: danh sách đánh giá & rating (tham chiếu FR-11), phân trang. - Section 5: sản phẩm liên quan/gợi ý. - **Trạng thái:** loading = skeleton toàn trang; empty = ẩn section đánh giá nếu chưa có review ("Chưa có đánh giá nào"); error = "Sản phẩm không tồn tại/đã bị gỡ" (liên quan FR-24 catalog moderation) + link quay lại danh mục. - **Validation chính:** không cho thêm giỏ hàng nếu SKU hết hàng (nút chuyển trạng thái "Hết hàng", disabled); số lượng đặt mua ≤ tồn kho hiển thị. #### SCR-04 — Giỏ hàng - **Mục đích:** quản lý các sản phẩm đã chọn từ nhiều seller trước khi checkout. - **Persona/Role:** Guest, Customer. - **FR phục vụ:** FR-05. - **Bố cục:** - Header: tiêu đề "Giỏ hàng của bạn" + số lượng sản phẩm. - Vùng chính: danh sách nhóm theo **seller** (mỗi nhóm = 1 seller, hiển thị tên gian hàng), mỗi dòng `CartItem` (ảnh, tên, biến thể, đơn giá, bộ đếm số lượng, nút xoá), checkbox chọn/bỏ chọn từng dòng hoặc cả nhóm. - Sidebar/footer tổng kết: tổng số lượng đã chọn, tạm tính (subtotal theo VND), nút "Tiến hành Checkout". - **Trạng thái:** loading = skeleton danh sách; empty = "Giỏ hàng trống" + nút "Tiếp tục mua sắm"; error = cảnh báo dòng sản phẩm hết hàng/giá thay đổi (badge "Sản phẩm đã hết hàng" hoặc "Giá đã thay đổi", chặn không cho tick chọn). - **Validation chính:** số lượng ≥ 1 và ≤ tồn kho hiện tại; phải chọn ít nhất 1 sản phẩm để bật nút Checkout. #### SCR-05 — Checkout (địa chỉ, vận chuyển, tách đơn theo seller) - **Mục đích:** thu thập địa chỉ giao hàng, hiển thị đơn hàng đã tách theo từng seller, áp mã giảm giá/điểm thưởng trước khi thanh toán. - **Persona/Role:** Guest (guest checkout), Customer. - **FR phục vụ:** FR-06 (tách đơn theo seller), FR-13 (áp coupon), FR-14 (dùng điểm thưởng). - **Bố cục:** - Section 1: thông tin người nhận & địa chỉ giao hàng (chọn địa chỉ đã lưu — FR-03 — hoặc nhập mới; với Guest bắt buộc nhập đầy đủ). - Section 2: danh sách **đơn con theo từng seller** (mỗi khối = 1 seller, hiển thị sản phẩm, phí vận chuyển ước tính theo GHN/GHTK, thời gian giao dự kiến). - Section 3: ô nhập mã khuyến mãi/coupon (FR-13) — áp dụng theo toàn đơn hoặc theo từng seller tuỳ cấu hình; hiển thị số điểm thưởng khả dụng và tuỳ chọn quy đổi giảm giá (FR-14, chỉ hiện với Customer đã đăng nhập). - Section 4: tổng kết thanh toán (tạm tính, giảm giá, phí vận chuyển, tổng cộng theo VND). - CTA: nút "Tiếp tục đến thanh toán". - **Trạng thái:** loading = tính lại phí vận chuyển/khuyến mãi khi thay đổi địa chỉ (spinner cục bộ); empty = không áp dụng (luôn có ít nhất 1 sản phẩm từ SCR-04); error = coupon không hợp lệ/hết hạn (thông báo inline), địa chỉ ngoài vùng phục vụ GHN/GHTK (thông báo + gợi ý địa chỉ khác). - **Validation chính:** các trường địa chỉ bắt buộc (họ tên, số điện thoại định dạng VN, tỉnh/thành, địa chỉ chi tiết); mã coupon kiểm tra điều kiện áp dụng (giá trị đơn tối thiểu, ngành hàng) trước khi trừ tiền; điểm thưởng quy đổi không vượt quá số dư `LoyaltyAccount`. #### SCR-06 — Thanh toán - **Mục đích:** chọn phương thức thanh toán và hoàn tất giao dịch. - **Persona/Role:** Guest, Customer. - **FR phục vụ:** FR-07. - **Bố cục:** - Section 1: chọn phương thức — VNPay, Momo, COD (radio group, mỗi lựa chọn có icon/mô tả). - Section 2 (nếu VNPay/Momo): chuyển hướng tới cổng thanh toán bên thứ ba (không thu thập/lưu thông tin thẻ tại hệ thống — giảm phạm vi PCI-DSS theo NFR-05). - Section 3: tóm tắt đơn hàng (read-only, tham chiếu từ SCR-05). - CTA: nút "Xác nhận thanh toán". - **Trạng thái:** loading = trạng thái "Đang xử lý thanh toán..." (không cho thao tác khác, tránh double-submit); empty = không áp dụng; error = thanh toán thất bại/timeout từ cổng thanh toán → thông báo lý do + nút "Thử lại" hoặc "Chọn phương thức khác", đơn hàng giữ trạng thái "Chờ thanh toán". - **Validation chính:** bắt buộc chọn 1 phương thức trước khi submit; chặn double-submit (disable nút sau khi bấm). #### SCR-07 — Xác nhận đơn hàng thành công - **Mục đích:** xác nhận đặt hàng thành công, cung cấp mã đơn hàng, kích hoạt thông báo. - **Persona/Role:** Guest, Customer. - **FR phục vụ:** FR-06, FR-12 (thông báo email/SMS xác nhận). - **Bố cục:** - Thông điệp thành công + mã đơn hàng (hoặc danh sách mã đơn con theo từng seller nếu tách đơn). - Tóm tắt đơn hàng, phương thức thanh toán, địa chỉ giao hàng. - Ghi chú: "Email/SMS xác nhận đã được gửi tới [email/số điện thoại]" (FR-12). - CTA: "Theo dõi đơn hàng" (link tới SCR-10, chỉ khả dụng nếu Customer đã đăng nhập) / "Tiếp tục mua sắm". - **Trạng thái:** loading = khi đang chờ webhook xác nhận thanh toán VNPay/Momo (trạng thái "Đang xác nhận thanh toán..."); error = thanh toán chưa được xác nhận sau timeout → hướng dẫn kiểm tra lại lịch sử đơn hàng hoặc liên hệ CSKH. - **Validation chính:** không có form nhập liệu. #### SCR-08 — Đăng ký / Đăng nhập Khách hàng - **Mục đích:** tạo tài khoản hoặc đăng nhập bằng email/password hoặc mạng xã hội. - **Persona/Role:** Guest → Customer. - **FR phục vụ:** FR-01 (đăng ký/đăng nhập), FR-02 (social login). - **Bố cục:** - Tab "Đăng nhập" / "Đăng ký". - Form đăng nhập: email, mật khẩu, link "Quên mật khẩu", nút đăng nhập. - Nút đăng nhập nhanh: "Đăng nhập với Google" / "Đăng nhập với Facebook" (FR-02). - Form đăng ký: họ tên, email, mật khẩu, xác nhận mật khẩu, checkbox đồng ý điều khoản. - **Trạng thái:** loading = spinner trên nút submit; empty = không áp dụng; error = sai email/mật khẩu (thông báo chung, không tiết lộ email tồn tại hay không — chống dò tài khoản), email đã tồn tại khi đăng ký, lỗi OAuth (token hết hạn/bị từ chối quyền). - **Validation chính:** email đúng định dạng; mật khẩu tối thiểu độ dài/độ phức tạp theo chính sách bảo mật (mục 8); xác nhận mật khẩu khớp; checkbox điều khoản bắt buộc tick. #### SCR-09 — Hồ sơ cá nhân & Địa chỉ giao hàng - **Mục đích:** quản lý thông tin cá nhân và danh sách địa chỉ giao hàng. - **Persona/Role:** Customer. - **FR phục vụ:** FR-03. - **Bố cục:** - Tab "Thông tin cá nhân": họ tên, email (read-only hoặc yêu cầu xác thực lại khi đổi), số điện thoại, đổi mật khẩu. - Tab "Sổ địa chỉ": danh sách địa chỉ đã lưu (dạng card), đánh dấu "Địa chỉ mặc định", nút thêm/sửa/xoá. - Form thêm/sửa địa chỉ: modal/trang riêng — tên người nhận, số điện thoại, tỉnh/thành/quận/huyện/phường xã, địa chỉ chi tiết. - **Trạng thái:** loading = skeleton danh sách địa chỉ; empty = "Chưa có địa chỉ nào" + CTA thêm mới; error = lỗi lưu thông tin (validation inline). - **Validation chính:** số điện thoại đúng định dạng VN; không cho xoá địa chỉ đang là mặc định nếu chỉ còn 1 địa chỉ; tối thiểu 1 địa chỉ mặc định. #### SCR-10 — Lịch sử đơn hàng & Chi tiết đơn - **Mục đích:** theo dõi trạng thái, huỷ đơn, xem chi tiết từng đơn (tách theo seller). - **Persona/Role:** Customer. - **FR phục vụ:** FR-08. - **Bố cục:** - Danh sách đơn hàng: filter theo trạng thái (Chờ xác nhận, Đang xử lý, Đang giao, Đã giao, Đã huỷ, Yêu cầu đổi trả), mỗi dòng hiển thị mã đơn, seller, tổng tiền, trạng thái, ngày đặt. - Trang chi tiết đơn: timeline trạng thái (progress stepper), danh sách sản phẩm, địa chỉ giao, phương thức thanh toán, nút "Huỷ đơn" (chỉ hiện khi đơn ở trạng thái cho phép), nút "Yêu cầu đổi trả/khiếu nại" (link SCR-11, chỉ hiện khi đơn đã giao), nút "Viết đánh giá" (link SCR-13, chỉ hiện khi đơn đã giao và sản phẩm chưa được đánh giá). - **Trạng thái:** loading = skeleton danh sách/chi tiết; empty = "Bạn chưa có đơn hàng nào" + CTA mua sắm; error = lỗi tải chi tiết đơn + retry. - **Validation chính:** nút "Huỷ đơn" bị disable/ẩn nếu đơn đã ở trạng thái "Đang giao"/"Đã giao" trở đi; xác nhận (dialog) trước khi huỷ đơn. #### SCR-11 — Yêu cầu đổi trả & Khiếu nại - **Mục đích:** khách hàng gửi yêu cầu đổi trả hoặc khiếu nại cho đơn đã giao. - **Persona/Role:** Customer (khởi tạo); CSR (tiếp nhận, xem SCR-32). - **FR phục vụ:** FR-09. - **Bố cục:** - Form: chọn sản phẩm/đơn liên quan, lý do (dropdown: sai hàng, lỗi, không đúng mô tả...), mô tả chi tiết (textarea), upload ảnh/video minh chứng, chọn hình thức mong muốn (hoàn tiền/đổi hàng). - Sau khi gửi: hiển thị trạng thái yêu cầu (Đang chờ xử lý/Đã xử lý/Từ chối) + lịch sử trao đổi với CSR (thread dạng chat/comment). - **Trạng thái:** loading = spinner khi submit/upload; empty = không áp dụng; error = ngoài thời hạn cho phép đổi trả (thông báo rõ chính sách + số ngày còn lại), upload file quá dung lượng/sai định dạng. - **Validation chính:** bắt buộc chọn lý do và mô tả tối thiểu số ký tự; giới hạn dung lượng/định dạng file upload (ảnh JPG/PNG, video MP4, tối đa theo cấu hình hệ thống); chỉ cho gửi yêu cầu trong thời hạn chính sách đổi trả kể từ ngày giao thành công. #### SCR-12 — Danh sách yêu thích (Wishlist) - **Mục đích:** lưu sản phẩm quan tâm để mua sau. - **Persona/Role:** Customer. - **FR phục vụ:** FR-10. - **Bố cục:** lưới `ProductCard` rút gọn (ảnh, tên, giá, trạng thái tồn kho), nút "Thêm vào giỏ hàng" trực tiếp từ wishlist, nút xoá khỏi danh sách. - **Trạng thái:** loading = skeleton lưới; empty = "Danh sách yêu thích trống" + CTA duyệt sản phẩm; error = sản phẩm đã ngừng bán (badge "Không còn khả dụng", disable nút thêm giỏ hàng). - **Validation chính:** không cho thêm giỏ hàng nếu sản phẩm hết hàng/ngừng bán. #### SCR-13 — Viết đánh giá sản phẩm - **Mục đích:** khách hàng đánh giá/rating sản phẩm đã mua và nhận hàng thành công. - **Persona/Role:** Customer. - **FR phục vụ:** FR-11. - **Bố cục:** form — chọn số sao (1-5), textarea nhận xét, upload ảnh (tuỳ chọn), nút gửi; hiển thị lại thông tin sản phẩm/đơn hàng liên quan (read-only). - **Trạng thái:** loading = spinner submit; error = đã đánh giá sản phẩm này rồi (chặn gửi trùng), đơn hàng chưa ở trạng thái "Đã giao" (ẩn nút viết đánh giá — xem SCR-10). - **Validation chính:** bắt buộc chọn số sao; giới hạn độ dài nhận xét; mỗi `OrderItem` chỉ được đánh giá 1 lần. #### SCR-14 — Điểm thưởng & Hạng thành viên - **Mục đích:** xem số dư điểm, hạng thành viên hiện tại, lịch sử tích/đổi điểm. - **Persona/Role:** Customer. - **FR phục vụ:** FR-14. - **Bố cục:** - Section 1: thẻ tổng quan — số điểm hiện có, hạng thành viên (Bạc/Vàng/Kim cương), thanh tiến trình tới hạng tiếp theo (dựa trên tổng chi tiêu 12 tháng gần nhất). - Section 2: bảng lịch sử `LoyaltyTransaction` (tích điểm từ đơn nào, đổi điểm giảm giá ở đơn nào, ngày). - Section 3: quy tắc chương trình (1 điểm/10.000đ, 100 điểm = 10.000đ). - **Trạng thái:** loading = skeleton; empty = "Chưa có giao dịch điểm thưởng nào"; error = lỗi tải dữ liệu + retry. - **Validation chính:** không có form nhập liệu (chỉ xem; đổi điểm thực hiện tại SCR-05 lúc checkout). #### SCR-15 — Trung tâm thông báo - **Mục đích:** xem lại lịch sử thông báo trong-app liên quan đơn hàng (bổ trợ cho email/SMS gửi ngoài hệ thống). - **Persona/Role:** Customer (và tương tự cho Seller — xem SCR-17). - **FR phục vụ:** FR-12. - **Bố cục:** danh sách thông báo dạng timeline (xác nhận đơn hàng, cập nhật trạng thái giao hàng, kết quả đổi trả, khuyến mãi), mỗi item có icon loại, nội dung rút gọn, thời gian, trạng thái đã đọc/chưa đọc, click vào để tới màn hình liên quan (SCR-10, SCR-11...). - **Trạng thái:** loading = skeleton danh sách; empty = "Không có thông báo nào"; error = lỗi tải + retry. - **Validation chính:** không áp dụng (read-only). > **Ghi chú traceability FR-12:** yêu cầu gốc là gửi **email/SMS** xác nhận đơn hàng — đây là kênh ngoài giao diện web, không có "màn hình" riêng. SCR-15 (Trung tâm thông báo trong-app) là **giả định bổ sung** của thiết kế để tăng trải nghiệm, không thay thế kênh email/SMS. Xem `openQuestions`. --- ### 7.1.2 Nhóm Người bán (Seller) #### SCR-16 — Đăng ký Seller & Upload hồ sơ KYC - **Mục đích:** cho phép bên thứ ba đăng ký trở thành người bán và nộp hồ sơ xác minh. - **Persona/Role:** Seller (ứng viên, chưa được duyệt). - **FR phục vụ:** FR-17. - **Bố cục:** - Bước 1 (wizard step 1): thông tin tài khoản — email, mật khẩu, tên gian hàng. - Bước 2: thông tin doanh nghiệp/cá nhân kinh doanh — tên, mã số thuế/CMND-CCCD, địa chỉ, ngành hàng dự kiến kinh doanh. - Bước 3: upload `KYCDocument` — giấy phép kinh doanh, CMND/CCCD (mặt trước/sau), có preview file đã upload. - Bước 4: xác nhận & gửi hồ sơ; hiển thị màn hình "Hồ sơ đang chờ duyệt". - **Trạng thái:** loading = spinner khi upload file (progress bar); empty = không áp dụng; error = file upload sai định dạng/quá dung lượng, mã số thuế trùng với seller đã đăng ký (thông báo inline). - **Validation chính:** định dạng file cho phép (PDF/JPG/PNG), giới hạn dung lượng; mã số thuế/CMND-CCCD đúng định dạng và không trùng lặp; các trường bắt buộc phải điền đủ trước khi chuyển bước tiếp theo (wizard chặn "Next" nếu bước hiện tại chưa hợp lệ). #### SCR-17 — Seller Dashboard (Tổng quan) - **Mục đích:** điểm vào chính của Seller sau đăng nhập, tổng hợp số liệu vận hành. - **Persona/Role:** Seller (đã được duyệt KYC). - **FR phục vụ:** FR-20. - **Bố cục:** - Header: tên gian hàng, trạng thái tài khoản (Đang hoạt động/Tạm khoá), menu điều hướng (Sản phẩm, Đơn hàng, Báo cáo, Thông báo). - Section 1: thẻ số liệu nhanh — đơn hàng chờ xử lý, doanh thu tuần này, số dư payout sắp tới. - Section 2: biểu đồ doanh thu theo thời gian (tuần/tháng). - Section 3: danh sách đơn hàng cần chú ý (chờ xác nhận, sắp hết hạn xử lý). - **Trạng thái:** loading = skeleton thẻ số liệu/biểu đồ; empty = "Chưa có dữ liệu bán hàng" (seller mới); error = lỗi tải báo cáo + retry. - **Validation chính:** không áp dụng (dashboard read-only). #### SCR-18 — Quản lý sản phẩm & Tồn kho (Seller) - **Mục đích:** seller tự đăng bán sản phẩm, quản lý biến thể (SKU) và tồn kho. - **Persona/Role:** Seller. - **FR phục vụ:** FR-18. - **Bố cục:** - Danh sách sản phẩm: bảng/lưới (ảnh, tên, ngành hàng, giá, tồn kho tổng, trạng thái hiển thị: Đang bán/Ẩn/Bị gỡ do vi phạm), filter theo ngành hàng/trạng thái, nút "Thêm sản phẩm". - Form thêm/sửa sản phẩm: thông tin cơ bản (tên, mô tả, ngành hàng — Category), upload ảnh/video, quản lý biến thể `ProductVariant` (bảng: thuộc tính biến thể, SKU code, giá bán, số lượng tồn kho). - Trạng thái "Bị gỡ do vi phạm" (liên quan FR-24, do Admin can thiệp) hiển thị lý do, không cho seller tự bật lại mà không chỉnh sửa theo yêu cầu. - **Trạng thái:** loading = skeleton bảng sản phẩm; empty = "Chưa có sản phẩm nào" + CTA thêm mới; error = lỗi lưu (validation inline), xung đột SKU trùng. - **Validation chính:** giá bán > 0; tồn kho ≥ 0 (không âm); ngành hàng bắt buộc chọn (làm cơ sở tính hoa hồng — FR-21); ảnh sản phẩm bắt buộc tối thiểu 1 ảnh. #### SCR-19 — Quản lý đơn hàng (Seller) - **Mục đích:** seller xem và xử lý các đơn hàng con thuộc gian hàng của mình. - **Persona/Role:** Seller. - **FR phục vụ:** FR-19. - **Bố cục:** - Danh sách đơn: filter theo trạng thái (Chờ xác nhận, Đã xác nhận/Đang chuẩn bị, Đã bàn giao vận chuyển, Đã giao, Huỷ, Đổi trả), tìm theo mã đơn/khách hàng. - Chi tiết đơn: thông tin sản phẩm, khách hàng (ẩn bớt thông tin nhạy cảm theo NFR-04), địa chỉ giao hàng, nút hành động theo trạng thái (Xác nhận đơn / In vận đơn / Đánh dấu đã bàn giao cho Ops-vận chuyển). - **Trạng thái:** loading = skeleton danh sách; empty = "Chưa có đơn hàng nào"; error = lỗi cập nhật trạng thái (VD. thao tác không hợp lệ với trạng thái hiện tại) + thông báo rõ. - **Validation chính:** chỉ cho chuyển trạng thái theo đúng luồng hợp lệ (VD. không thể "Đã giao" khi chưa "Đã bàn giao vận chuyển"); giới hạn thời gian xác nhận đơn (nếu quá hạn → tự động cảnh báo/huỷ theo chính sách vận hành). #### SCR-20 — Báo cáo doanh thu, hoa hồng & Payout (Seller) - **Mục đích:** seller theo dõi doanh thu, hoa hồng bị trừ, lịch sử và trạng thái các đợt payout. - **Persona/Role:** Seller. - **FR phục vụ:** FR-20. - **Bố cục:** - Bộ lọc theo khoảng thời gian. - Bảng chi tiết: mỗi dòng = 1 đơn hàng đã hoàn tất — doanh thu gộp, % hoa hồng áp dụng (theo `CommissionRule` của ngành hàng — tham chiếu FR-21), số tiền hoa hồng, số tiền thực nhận. - Bảng lịch sử `Payout`: đợt payout (tuần), tổng tiền, trạng thái (Đang giữ - hold/Đã chuyển khoản/Thất bại), ngày dự kiến chi trả. - **Trạng thái:** loading = skeleton bảng; empty = "Chưa có giao dịch nào trong kỳ đã chọn"; error = payout thất bại (hiển thị lý do, VD sai thông tin ngân hàng) + hướng dẫn liên hệ hỗ trợ. - **Validation chính:** không có form nhập liệu chính (read-only báo cáo); cập nhật thông tin tài khoản ngân hàng nhận payout có validate định dạng số tài khoản/tên ngân hàng (thuộc form cấu hình tài khoản thanh toán của seller, liên kết với SCR-09-tương tự cho seller). #### SCR-21 — Đăng nhập Seller (MFA khuyến khích) - **Mục đích:** đăng nhập vào khu vực quản trị gian hàng. - **Persona/Role:** Seller. - **FR phục vụ:** FR-27. - **Bố cục:** form email/mật khẩu; sau đăng nhập, banner khuyến nghị bật MFA nếu chưa bật (không bắt buộc — theo mục 1.4 giả định #8); màn hình cấu hình MFA (bật/tắt, quét QR cho ứng dụng authenticator) trong phần cài đặt tài khoản. - **Trạng thái:** loading = spinner; error = sai thông tin đăng nhập, tài khoản bị khoá bởi Admin (thông báo rõ + hướng dẫn liên hệ hỗ trợ — liên quan FR-23). - **Validation chính:** tương tự SCR-08; nếu bật MFA, bắt buộc nhập mã OTP hợp lệ (6 số, hết hạn theo thời gian cấu hình) trước khi vào hệ thống. --- ### 7.1.3 Nhóm Quản trị viên sàn (Platform Admin) #### SCR-22 — Admin Dashboard (Tổng quan vận hành sàn) - **Mục đích:** tổng hợp số liệu vận hành toàn sàn để Admin theo dõi nhanh. - **Persona/Role:** PlatformAdmin. - **FR phục vụ:** *không gắn trực tiếp 1 FR cụ thể* — màn hình tổng hợp hỗ trợ giám sát chung (đơn hàng, GMV, seller chờ duyệt, tranh chấp mở). **Cần xác nhận với BA** nếu cần bổ sung FR riêng cho dashboard vận hành (xem `openQuestions`). - **Bố cục:** thẻ số liệu (tổng GMV, số đơn hôm nay, số seller chờ duyệt KYC, số tranh chấp đang mở, tổng payout kỳ này); danh sách việc cần xử lý (queue rút gọn, link nhanh tới SCR-23/25/28). - **Trạng thái:** loading = skeleton; empty = không áp dụng (luôn có số liệu, kể cả 0); error = lỗi tải số liệu tổng hợp + retry. - **Validation chính:** không áp dụng (read-only). #### SCR-23 — Duyệt/Khoá Seller (Quản lý KYC & tài khoản Seller) - **Mục đích:** Admin xét duyệt hồ sơ KYC của seller mới đăng ký và giám sát/khoá seller vi phạm. - **Persona/Role:** PlatformAdmin. - **FR phục vụ:** FR-17 (duyệt KYC), FR-23 (quản trị seller — duyệt/khoá, giám sát). - **Bố cục:** - Danh sách seller: filter theo trạng thái (Chờ duyệt, Đã duyệt, Bị khoá, Từ chối), tìm kiếm theo tên gian hàng/mã số thuế. - Chi tiết hồ sơ seller: thông tin đăng ký, xem `KYCDocument` (viewer ảnh/PDF), lịch sử vi phạm (nếu có), nút "Duyệt" / "Từ chối (nhập lý do)" / "Khoá tài khoản (nhập lý do)" / "Mở khoá". - **Trạng thái:** loading = skeleton danh sách/chi tiết; empty = "Không có seller nào chờ duyệt"; error = lỗi tải tài liệu KYC (file hỏng/không truy cập được) + thông báo. - **Validation chính:** bắt buộc nhập lý do khi Từ chối/Khoá tài khoản (để lưu vết và thông báo cho seller); không cho duyệt nếu thiếu tài liệu KYC bắt buộc. #### SCR-24 — Quản trị Catalog toàn sàn - **Mục đích:** Admin giám sát và can thiệp (ẩn/gỡ) sản phẩm vi phạm trên toàn sàn. - **Persona/Role:** PlatformAdmin. - **FR phục vụ:** FR-24. - **Bố cục:** bảng sản phẩm toàn sàn với filter (ngành hàng, seller, trạng thái, bị báo cáo vi phạm), xem chi tiết sản phẩm (giống SCR-03 nhưng có thêm khu vực hành động), nút "Ẩn sản phẩm" / "Gỡ vĩnh viễn" (yêu cầu nhập lý do) / "Khôi phục". - **Trạng thái:** loading = skeleton bảng; empty = "Không có sản phẩm bị báo cáo"; error = lỗi cập nhật trạng thái sản phẩm + retry. - **Validation chính:** bắt buộc nhập lý do khi ẩn/gỡ sản phẩm (đồng bộ hiển thị lý do lại cho seller ở SCR-18). #### SCR-25 — Cấu hình hoa hồng (Commission) theo ngành hàng - **Mục đích:** Admin cấu hình/chỉnh sửa bảng % hoa hồng áp dụng theo từng `Category`. - **Persona/Role:** PlatformAdmin. - **FR phục vụ:** FR-21. - **Bố cục:** bảng danh sách ngành hàng kèm % hoa hồng hiện hành, nút "Chỉnh sửa" mở form nhập % mới + ngày hiệu lực, lịch sử thay đổi (audit log rút gọn: ai đổi, khi nào, giá trị cũ/mới). - **Trạng thái:** loading = skeleton bảng; empty = không áp dụng (danh mục ngành hàng luôn tồn tại từ hệ thống catalog); error = lỗi lưu cấu hình + validation inline. - **Validation chính:** % hoa hồng trong khoảng hợp lệ (0-100%); ngày hiệu lực không được là ngày trong quá khứ; cảnh báo xác nhận trước khi lưu do ảnh hưởng trực tiếp tới thu nhập seller (liên quan FR-20). #### SCR-26 — Quản lý Khuyến mãi / Mã giảm giá - **Mục đích:** Admin tạo và quản lý chương trình khuyến mãi/coupon áp dụng toàn sàn hoặc theo ngành hàng/seller. - **Persona/Role:** PlatformAdmin. - **FR phục vụ:** FR-13. - **Bố cục:** danh sách chương trình khuyến mãi (tên, mã coupon, loại giảm giá — %/số tiền cố định, điều kiện áp dụng, thời gian hiệu lực, trạng thái Đang chạy/Sắp diễn ra/Đã kết thúc); form tạo/sửa chương trình. - **Trạng thái:** loading = skeleton danh sách; empty = "Chưa có chương trình khuyến mãi nào"; error = mã coupon trùng, khoảng thời gian không hợp lệ (kết thúc trước bắt đầu). - **Validation chính:** mã coupon duy nhất; ngày kết thúc > ngày bắt đầu; giá trị giảm giá > 0 và hợp lý (VD % không vượt 100). #### SCR-27 — Quản lý Payout - **Mục đích:** Admin giám sát và xử lý các đợt chi trả payout hàng tuần cho seller. - **Persona/Role:** PlatformAdmin. - **FR phục vụ:** FR-22. - **Bố cục:** danh sách đợt payout theo tuần (tổng số seller, tổng tiền, trạng thái tổng thể); chi tiết theo từng seller trong đợt (số tiền, trạng thái Đang giữ-hold/Sẵn sàng chi/Đã chuyển/Thất bại), nút "Chạy đối soát & tạo đợt payout", nút "Thử lại" cho payout thất bại. - **Trạng thái:** loading = trạng thái "Đang tính toán đối soát..."; empty = "Không có seller nào đủ điều kiện payout kỳ này"; error = payout thất bại (sai thông tin tài khoản ngân hàng seller, lỗi kết nối ngân hàng) + log chi tiết. - **Validation chính:** không cho chạy payout trùng kỳ đã xử lý; chỉ tính các đơn đã qua kỳ giữ tiền (hold) 3-7 ngày sau giao hàng thành công (theo giả định #3, mục 1.4) trước khi đưa vào đợt chi trả. #### SCR-28 — Xử lý Tranh chấp & Khiếu nại (Admin — escalation) - **Mục đích:** Admin xử lý các tranh chấp phức tạp giữa khách hàng và seller được CSR chuyển lên (escalate). - **Persona/Role:** PlatformAdmin (xử lý escalation); tham chiếu chung với SCR-32 (CSR). - **FR phục vụ:** FR-25. - **Bố cục:** danh sách `Dispute` (mã, khách hàng, seller, đơn hàng liên quan, mức độ ưu tiên, trạng thái); chi tiết tranh chấp — lịch sử trao đổi, minh chứng đính kèm (từ SCR-11), nút quyết định (Hoàn tiền khách hàng / Từ chối yêu cầu / Yêu cầu seller bồi hoàn) kèm ô nhập lý do/ghi chú quyết định. - **Trạng thái:** loading = skeleton danh sách/chi tiết; empty = "Không có tranh chấp cần Admin xử lý"; error = lỗi lưu quyết định + retry. - **Validation chính:** bắt buộc nhập lý do quyết định (lưu vết cho đối soát); không cho đóng tranh chấp nếu chưa chọn 1 trong các hướng xử lý. #### SCR-29 — Đăng nhập Admin (MFA bắt buộc) - **Mục đích:** đăng nhập khu vực quản trị sàn với xác thực đa yếu tố bắt buộc. - **Persona/Role:** PlatformAdmin. - **FR phục vụ:** FR-27. - **Bố cục:** form email/mật khẩu → bước bắt buộc nhập mã OTP (app authenticator) trước khi vào hệ thống, không có lựa chọn bỏ qua. - **Trạng thái:** loading = spinner; error = sai thông tin đăng nhập, mã OTP sai/hết hạn, tài khoản chưa cấu hình MFA (chặn đăng nhập, bắt buộc thiết lập MFA lần đầu). - **Validation chính:** MFA bắt buộc 100% (không có nút "Bỏ qua"); khoá tài khoản tạm thời sau nhiều lần nhập sai liên tiếp (theo chính sách mục 8). --- ### 7.1.4 Nhóm Nhân viên vận hành/kho (Ops/Warehouse) #### SCR-30 — Danh sách đơn cần xử lý/đóng gói - **Mục đích:** Ops xem danh sách đơn hàng (thuộc phạm vi được phân công theo sàn hoặc theo seller) cần đóng gói và bàn giao vận chuyển. - **Persona/Role:** OpsStaff. - **FR phục vụ:** FR-26. - **Bố cục:** bảng đơn hàng cần xử lý (mã đơn, seller, sản phẩm, hạn xử lý), filter theo trạng thái/kho, nút "Đánh dấu đã đóng gói" → chuyển bước tạo vận đơn. - **Trạng thái:** loading = skeleton bảng; empty = "Không có đơn nào cần xử lý"; error = lỗi tải danh sách + retry. - **Validation chính:** chỉ hiển thị/thao tác trên đơn thuộc phạm vi được phân công (theo phân quyền mục 1.2, không thiết kế lại ở đây). #### SCR-31 — Cập nhật trạng thái vận chuyển - **Mục đích:** tạo vận đơn với GHN/GHTK và cập nhật trạng thái giao hàng. - **Persona/Role:** OpsStaff. - **FR phục vụ:** FR-26. - **Bố cục:** form chọn đơn vị vận chuyển (GHN/GHTK), hiển thị phí ước tính, nút "Tạo vận đơn"; sau khi tạo — hiển thị mã vận đơn, trạng thái đồng bộ từ đơn vị vận chuyển (Đã lấy hàng/Đang giao/Giao thành công/Giao thất bại), nút cập nhật thủ công nếu cần đối soát. - **Trạng thái:** loading = "Đang tạo vận đơn..."; error = API vận chuyển lỗi/timeout (thông báo + nút thử lại/chọn đơn vị khác); empty = không áp dụng. - **Validation chính:** không cho tạo vận đơn trùng cho 1 đơn hàng đã có vận đơn hợp lệ; địa chỉ giao hàng phải hợp lệ với vùng phục vụ của đơn vị vận chuyển đã chọn. --- ### 7.1.5 Nhóm Nhân viên chăm sóc khách hàng (CSR) #### SCR-32 — Hàng đợi Khiếu nại/Đổi trả (CSR) - **Mục đích:** CSR tiếp nhận, xử lý các yêu cầu đổi trả/khiếu nại từ khách hàng; escalate lên Admin khi cần. - **Persona/Role:** CSR (read/xử lý theo quyền hạn được mô tả mục 1.2: có quyền xem thông tin đơn hàng liên quan để hỗ trợ, không chỉnh sửa cấu hình hệ thống). - **FR phục vụ:** FR-09 (tiếp nhận yêu cầu đổi trả), FR-25 (xử lý tranh chấp/khiếu nại). - **Bố cục:** danh sách hàng đợi (mã yêu cầu, khách hàng, seller, đơn hàng, lý do, mức độ ưu tiên, thời gian chờ xử lý — SLA); chi tiết yêu cầu — xem minh chứng, lịch sử trao đổi (thread), nút "Phản hồi khách hàng" (nhập tin nhắn), nút "Giải quyết trực tiếp" (nếu trong thẩm quyền CSR) hoặc "Chuyển lên Admin" (escalate tới SCR-28, kèm ghi chú lý do escalate). - **Trạng thái:** loading = skeleton danh sách/chi tiết; empty = "Không có yêu cầu nào đang chờ xử lý"; error = lỗi tải minh chứng đính kèm (file hỏng) + thông báo. - **Validation chính:** bắt buộc nhập nội dung phản hồi trước khi gửi; bắt buộc chọn lý do khi escalate lên Admin; không cho CSR chỉnh sửa cấu hình hoa hồng/catalog/seller (ngoài phạm vi quyền — tham chiếu mục 1.2). --- ## 7.2 User Flow Diagram (theo persona) ### 7.2.1 Khách hàng — Hành trình mua hàng đầy đủ (FR-04, FR-05, FR-06, FR-07, FR-08, FR-12, FR-13, FR-14) ```mermaid flowchart TD A["Vào trang chủ (SCR-01)"] --> B["Tìm kiếm / duyệt danh mục (SCR-02, SCR-03)"] B --> C{"Sản phẩm còn hàng?"} C -- "Không" --> B C -- "Có" --> D["Thêm vào giỏ hàng (SCR-04)"] D --> E{"Tiếp tục mua hay Checkout?"} E -- "Tiếp tục mua" --> B E -- "Checkout" --> F{"Đã đăng nhập?"} F -- "Chưa (Guest checkout)" --> G["Nhập thông tin Guest hoặc Đăng nhập/Đăng ký (SCR-08)"] F -- "Đã đăng nhập" --> H["Checkout: địa chỉ, tách đơn theo seller, áp coupon/điểm (SCR-05)"] G --> H H --> I{"Coupon/địa chỉ hợp lệ?"} I -- "Không" --> H I -- "Có" --> J["Chọn phương thức thanh toán (SCR-06)"] J --> K{"Thanh toán thành công?"} K -- "Thất bại" --> L["Hiển thị lỗi, chọn lại phương thức"] --> J K -- "Thành công" --> M["Xác nhận đơn hàng (SCR-07) + gửi email/SMS (FR-12)"] M --> N["Theo dõi đơn hàng (SCR-10)"] ``` ### 7.2.2 Khách hàng — Đổi trả/Khiếu nại (FR-08, FR-09, FR-12, FR-25) ```mermaid flowchart TD A["Lịch sử đơn hàng (SCR-10)"] --> B["Chọn đơn đã giao"] B --> C["Gửi yêu cầu đổi trả/khiếu nại (SCR-11)"] C --> D{"Trong thời hạn chính sách đổi trả?"} D -- "Không" --> E["Từ chối tự động + thông báo lý do (FR-12)"] D -- "Có" --> F["CSR tiếp nhận (SCR-32)"] F --> G{"Thuộc thẩm quyền CSR?"} G -- "Có" --> H["CSR giải quyết trực tiếp"] G -- "Không, cần escalate" --> I["Admin xử lý tranh chấp (SCR-28)"] I --> H H --> J["Cập nhật trạng thái + thông báo kết quả cho khách hàng (FR-12)"] ``` ### 7.2.3 Seller — Đăng ký, KYC, vận hành gian hàng (FR-17, FR-18, FR-19, FR-20, FR-26, FR-27) ```mermaid flowchart TD A["Đăng ký Seller (SCR-16)"] --> B["Upload hồ sơ KYC"] B --> C["Admin duyệt KYC (SCR-23)"] C --> D{"Hồ sơ hợp lệ?"} D -- "Từ chối" --> E["Thông báo lý do, seller bổ sung hồ sơ"] --> B D -- "Đồng ý" --> F["Đăng nhập Seller (SCR-21, MFA khuyến khích - FR-27)"] F --> G["Seller Dashboard (SCR-17)"] G --> H["Đăng sản phẩm & cập nhật tồn kho (SCR-18)"] G --> I["Nhận & xác nhận đơn hàng (SCR-19)"] I --> J["Bàn giao cho Ops đóng gói/vận chuyển (SCR-30, SCR-31 - FR-26)"] J --> K["Đơn hàng giao thành công"] K --> L["Xem báo cáo doanh thu/hoa hồng/payout (SCR-20)"] ``` ### 7.2.4 Platform Admin — Vận hành & quản trị sàn (FR-13, FR-17, FR-21, FR-22, FR-23, FR-24, FR-25, FR-27) ```mermaid flowchart TD A["Đăng nhập Admin, MFA bắt buộc (SCR-29)"] --> B["Admin Dashboard (SCR-22)"] B --> C["Duyệt/khoá Seller (SCR-23) - FR-17, FR-23"] B --> D["Cấu hình hoa hồng theo ngành hàng (SCR-25) - FR-21"] B --> E["Quản trị catalog toàn sàn (SCR-24) - FR-24"] B --> F["Quản lý khuyến mãi/coupon (SCR-26) - FR-13"] B --> G["Quản lý payout hàng tuần (SCR-27) - FR-22"] B --> H["Xử lý tranh chấp escalate từ CSR (SCR-28) - FR-25"] ``` ### 7.2.5 Ops/Warehouse — Xử lý đơn hàng & vận chuyển (FR-26) ```mermaid flowchart TD A["Danh sách đơn cần xử lý (SCR-30)"] --> B["Đóng gói sản phẩm"] B --> C["Tạo vận đơn qua GHN/GHTK (SCR-31)"] C --> D{"Tạo vận đơn thành công?"} D -- "Thất bại" --> E["Thử lại / chọn đơn vị vận chuyển khác"] --> C D -- "Thành công" --> F["Cập nhật trạng thái: Đã bàn giao vận chuyển"] F --> G["Đồng bộ trạng thái giao hàng (Đang giao/Giao thành công/Thất bại)"] G --> H["Gửi thông báo cập nhật cho khách hàng (FR-12)"] ``` --- ## 7.3 Ghi chú truy vết & khoảng trống - Tất cả FR-01 → FR-27 đã có ít nhất 1 màn hình hoặc luồng tham chiếu, trừ **SCR-22 (Admin Dashboard tổng quan)** — màn hình này không truy vết trực tiếp về 1 FR cụ thể, chỉ đóng vai trò tổng hợp giám sát; đã gắn cờ "cần xác nhận với BA" ngay tại mục mô tả màn hình. - **FR-12 (Thông báo email/SMS)** về bản chất là kênh giao tiếp ngoài giao diện web (không phải "màn hình"); SCR-15 (Trung tâm thông báo trong-app) là bổ sung giả định của thiết kế, cần BA/PO xác nhận có thực sự cần trung tâm thông báo trong-app ở MVP hay chỉ cần email/SMS thuần tuý. - Thiết kế không đề xuất màu sắc/typography cụ thể do project brief không có brand guideline (giả định #9, mục 1.4) — khi có brand guideline thực tế, cần cập nhật lại phần mockup trực quan (hiện tại chỉ ở dạng wireframe văn bản). --- # 8. Thiết kế bảo mật (Security Design) > **Vai trò của mục này:** rà soát chéo (cross-cutting review) trên các quyết định đã có ở mục 3 (kiến trúc), 4 (API), 5 (dữ liệu), 6 (luồng xử lý) — không thiết kế lại các mục đó. Mọi thiếu sót phát hiện được liệt kê ở §8.5 và trong `findings` của structured output để orchestrator cho chạy lại đúng mục. > > **Đầu vào:** `00-project-brief.md` (profile: `scale=large`, `hasPayment=true`, `hasPII=true`, `platforms=["web"]`, tuân thủ NĐ52/85, NĐ13/2023, PCI-DSS scope giảm, cloud=AWS, ngân sách/timeline chưa xác định), `02-phan-tich-yeu-cau.md` (NFR-04 Bảo mật, NFR-05 Tuân thủ), `03-kien-truc.md` (WAF/ALB/API Gateway, cô lập Payment Service, S3 mã hoá KYC, database-per-service), `04-api-design.md` **v3** (JWT Bearer, quy tắc ownership 4.1.1, mã lỗi `403 ERR_FORBIDDEN_OWNERSHIP`/`409 ERR_ACCOUNT_LINK_REQUIRED`, chống replay webhook, `Idempotency-Key` mở rộng), `05-thiet-ke-du-lieu.md` **v3** (cột **[PII]**/**[Payment]**, cột chống brute-force `user_account.failed_login_count/locked_until/last_failed_login_at`, bảng `audit_log` tại Audit & Compliance Service — 5.2.11), `06-luong-xu-ly.md` **v2** (luồng checkout/payment/KYC/payout/dispute/login đã cập nhật pre-signed URL KYC, kênh payout, sự kiện audit, nhánh khoá tài khoản). > > **Right-sizing:** vì `hasPayment=true` và `hasPII=true`, cả 4 mảng bảo mật (xác thực, bảo vệ dữ liệu, OWASP, tuân thủ) đều áp dụng đầy đủ, không có phần "không áp dụng" — riêng phạm vi PCI-DSS được **thu hẹp** (không lưu số thẻ, giao VNPay/Momo xử lý — xem §8.4). Không đề xuất công nghệ/ngân sách vượt ràng buộc mục 1 (cloud AWS, không SSO doanh nghiệp, ngân sách/timeline chưa xác định) — các đề xuất bên dưới đều dùng dịch vụ AWS chuẩn (KMS, Secrets Manager, WAF, GuardDuty, CloudTrail) hoặc thư viện mã nguồn mở; các hạng mục phát sinh chi phí đáng kể được ghi chú trade-off riêng. > > **(v2 — revision đồng bộ mục 4 v3/5 v3/6 v2, chỉ sửa tối thiểu):** (a) §8.1.1 bổ sung **chính sách khoá tài khoản (account lockout policy)** cụ thể — ngưỡng `failed_login_count`, thời lượng `locked_until` theo vai trò, cách reset — để mục 4 dùng khi bổ sung mã lỗi `423 ERR_ACCOUNT_LOCKED`; (b) §8.2 bổ sung mục 8.2.5 quyết định về redact PII trong `audit_log.before_json/after_json`, kiểm soát truy cập đọc `audit_log`, và retention; (c) §8.1.2/8.2/8.3 cập nhật tham chiếu sang mục 4 v3 (quy ước ownership 4.1.1, mã lỗi mới) và mục 5 v3 (cột mới, bảng `audit_log`); (d) §8.5 rà soát lại 10 finding v1 — đánh dấu finding đã giải quyết ở mục 4 v3/5 v3/6 v2, giữ lại finding chưa xử lý (F10 — MSK ACL, mục 3) và bổ sung 2-3 finding mới phát sinh từ mục 6 v2 (endpoint xem KYC document, mã lỗi khoá tài khoản — mục 4 đã hết vòng sửa, ghi nhận thủ công). ## 8.1 Xác thực & phân quyền (Authentication & Authorization) > Cơ chế JWT Bearer/OAuth2/scope theo actor đã được đặc tả ở mục 4.2 — **không lặp lại**, chỉ dẫn chiếu và bổ sung chi tiết triển khai (mục 4.2 v3 đã ghi rõ "cơ chế MFA chi tiết... chính sách khoá tài khoản thuộc mục 8"). ### 8.1.1 Xác thực (Authentication) | Hạng mục | Thiết kế | FR/Ghi chú | |---|---|---| | Mật khẩu | `bcrypt`/`argon2id` (đã có cột `password_hash` mục 5.2.1), độ dài tối thiểu 10 ký tự, kiểm tra chống mật khẩu rò rỉ (breach list, VD thư viện zxcvbn/HIBP k-anonymity API), không giới hạn ký tự đặc biệt | FR-01 | | Chống brute-force (account lockout) | **Đã triển khai đủ cột hỗ trợ ở mục 5 v3** (`user_account.failed_login_count`/`locked_until`/`last_failed_login_at`) và **luồng ở mục 6.1.6 v2** (tăng đếm khi sai, khoá khi vượt ngưỡng, reset khi đăng nhập thành công) — **chính sách cụ thể (ngưỡng/thời lượng theo vai trò) chốt tại §8.1.1a bên dưới**, kết hợp với rate-limit theo IP + captcha đã có ở mục 4.2 (defense-in-depth 2 lớp: theo tài khoản + theo IP) | FR-01, FR-27 | | JWT | Access token TTL 15-60 phút (đã chốt mục 4.2), refresh token TTL 7-30 ngày với **refresh token rotation** — mỗi lần refresh phát hành token mới, phát hiện tái sử dụng token cũ (reuse detection) → thu hồi toàn bộ chuỗi token của phiên đó (chống token bị đánh cắp dùng lại) | FR-01 | | Lưu trữ token phía client | Khuyến nghị: access token giữ trong bộ nhớ (memory) của SPA, refresh token trong cookie `HttpOnly; Secure; SameSite=Lax/Strict` (không dùng `localStorage` cho refresh token để giảm rủi ro XSS đánh cắp token dài hạn); nếu dùng cookie cho access token, bắt buộc thêm CSRF token (double-submit cookie) cho mọi request ghi | FR-01, FR-27 — **openQuestion:** mục 7 (UI) chưa xác nhận cơ chế lưu token cụ thể, cần đồng bộ khi thiết kế frontend | | MFA (FR-27) | TOTP (RFC 6238, ưu tiên hơn SMS OTP do rủi ro SIM-swap) bắt buộc cho `role=platform_admin`, khuyến khích cho `seller`; cấp 10 mã backup dùng một lần khi enroll; endpoint `/v1/auth/mfa/enroll`, `/v1/auth/mfa/challenge` đã có ở mục 4 — bổ sung: giới hạn 5 lần thử OTP sai/challenge token, challenge token TTL ngắn (≤5 phút) | FR-27, BR-12 | | OAuth2 Social login (FR-02) | **Đã triển khai ở mục 4 v3** (`POST /v1/auth/oauth/{provider}/callback`): xác thực tham số `state` (400 `ERR_OAUTH_STATE_INVALID` nếu thiếu/không khớp), xác minh `id_token` issuer/audience/expiry phía server trước khi tạo `OAuthIdentity`, và **không tự động liên kết (no auto-merge)** khi email trùng tài khoản email/password đã tồn tại — trả `409 ERR_ACCOUNT_LINK_REQUIRED`, yêu cầu xác minh sở hữu email trước khi merge tài khoản (chống account takeover) | FR-02 | | Session/logout | Refresh token bị thu hồi (đưa vào denylist Redis theo `jti` tới khi hết TTL) khi logout, đổi mật khẩu, hoặc Admin khoá tài khoản; đăng xuất tất cả thiết bị là hành động tuỳ chọn cho Customer (nice-to-have, không bắt buộc MVP) | FR-01 | ### 8.1.1a Chính sách khoá tài khoản (Account Lockout Policy) — v2 > Chốt theo yêu cầu người duyệt: quy định ngưỡng số lần đăng nhập sai và thời lượng khoá dựa trên cột `user_account.failed_login_count`/`locked_until`/`last_failed_login_at` (mục 5.2.1 v3), phục vụ luồng 6.1.6 v2 và để mục 4 bổ sung mã lỗi `423 ERR_ACCOUNT_LOCKED` khi có vòng sửa tiếp theo. | Vai trò (`user_account.role`) | Ngưỡng `failed_login_count` | Thời lượng khoá (`locked_until`) | Lý do khác biệt | |---|---|---|---| | `customer` | 5 lần sai liên tiếp | now + 15 phút | Số đông người dùng, ưu tiên trải nghiệp; kết hợp captcha sau 3 lần sai (đã có mục 4.2) giảm rủi ro trước khi chạm ngưỡng khoá | | `seller` | 5 lần sai liên tiếp | now + 15 phút | Cùng mức Customer; MFA khuyến khích (không bắt buộc) nên lockout theo mật khẩu là lớp phòng thủ chính | | `platform_admin` | **3 lần sai liên tiếp** | **now + 30 phút** | Quyền hạn cao nhất (scope `admin:*`) → ngưỡng thấp hơn, thời lượng khoá dài hơn Customer/Seller; rủi ro DoS (kẻ tấn công cố tình khoá tài khoản Admin đã biết email) được giảm thiểu vì Admin Backoffice chỉ truy cập qua VPN/IP allowlist (mục 3.3) — kẻ tấn công ngoài mạng nội bộ không gọi được `/v1/auth/login` với role Admin để kích hoạt khoá | | `ops_staff`, `csr` | 5 lần sai liên tiếp | now + 15 phút | Không có scope `admin:*` toàn cục; áp dụng như Customer/Seller là đủ, tránh phức tạp hoá chính sách không cần thiết | **Cơ chế cập nhật (áp dụng tại `POST /v1/auth/login`, khớp sequence 6.1.6 v2):** 1. Trước khi so khớp mật khẩu: nếu `locked_until` đã được đặt và `locked_until > now` → từ chối ngay, **không** so khớp mật khẩu (tránh side-channel timing), trả về mã lỗi tài khoản đang tạm khoá (đề xuất `423 ERR_ACCOUNT_LOCKED` — xem finding mục 4 ở §8.5) kèm thông tin thời điểm có thể thử lại (`retryAfter`), **không** tiết lộ email có tồn tại hay không trong thông báo lỗi. 2. Nếu `locked_until` đã qua (now ≥ `locked_until`) tại lần thử tiếp theo: coi như **tự động mở khoá** — reset `failed_login_count = 0` **trước khi** đánh giá mật khẩu của lần thử hiện tại (không cộng dồn từ chuỗi thất bại trước khi khoá), tránh khoá lặp vô hạn nhưng vẫn đánh giá công bằng lần thử mới. 3. Mật khẩu sai: `failed_login_count += 1`, `last_failed_login_at = now`; nếu `failed_login_count` vượt ngưỡng theo vai trò ở bảng trên → đặt `locked_until = now + thời lượng tương ứng`. 4. Mật khẩu đúng (dù trước đó có sai một vài lần chưa chạm ngưỡng): **reset `failed_login_count = 0`, `last_failed_login_at = NULL`** — không giữ lại lịch sử thất bại cũ sau khi xác thực thành công (đã khớp sequence 6.1.6 v2). 5. **Không có endpoint tự mở khoá sớm cho chính người dùng** ở MVP (đợi hết `locked_until`); trường hợp khẩn cấp (Customer/Seller liên hệ CSKH vì bị khoá do thao tác nhầm) xử lý thủ công qua nghiệp vụ vận hành nội bộ (CSR/Admin sửa trực tiếp `locked_until=NULL` qua công cụ nội bộ có kiểm soát, **không** qua API công khai) — không đề xuất thêm endpoint mới ở mục 4 cho luồng này vì tần suất thấp, tránh mở rộng bề mặt tấn công không cần thiết ở MVP. 6. **Khuyến nghị bổ sung (không bắt buộc)**: khi tài khoản chuyển sang `locked_until` lần đầu trong một khoảng thời gian, gửi thông báo email cho chủ tài khoản qua Notification Service (kênh sẵn có, chi phí không đáng kể) để cảnh báo khả năng bị dò mật khẩu — không chặn luồng chính nếu gửi thất bại. **Mã lỗi đề xuất cho mục 4** (chưa có ở mục 4 v3, xem finding §8.5): `423 ERR_ACCOUNT_LOCKED` — "Tài khoản tạm khoá do đăng nhập sai nhiều lần", response kèm `retryAfterSeconds` (tính từ `locked_until - now`), phân biệt với `401 ERR_AUTH_REQUIRED`/`ERR_AUTH_INVALID_TOKEN` (thiếu/sai token) và với thông báo sai email/mật khẩu thông thường (vẫn trả `401` chung chung không phân biệt "email không tồn tại" hay "sai mật khẩu" để tránh dò email hợp lệ — **chỉ** riêng lockout mới lộ trạng thái "đã bị khoá", chấp nhận đánh đổi UX vs. ẩn thông tin vì mức độ rủi ro thấp hơn lộ email tồn tại hay không). ### 8.1.2 Phân quyền (Authorization) — RBAC + kiểm soát ownership (ABAC nhẹ) - **RBAC theo scope**: giữ nguyên bảng scope/actor đã chốt ở mục 4.2 (`customer:*`, `seller:*`, `admin:*`, `ops:*`, `csr:*`) — Identity & Access Service là nguồn phát hành duy nhất, API Gateway/BFF enforce tại tầng biên trước khi route vào service nội bộ. - **Kiểm soát ownership (resource-level, bắt buộc ở tầng service, không chỉ ở Gateway)** — **đã chốt thành quy ước chính thức tại mục 4.1.1 v3** ("Quy tắc ownership (chống IDOR)"): mọi endpoint có tham số định danh tài nguyên gắn với một Customer/Seller cụ thể phải đối chiếu `sub`/`customerId`/`sellerId` trong JWT trước khi trả dữ liệu, vi phạm → `403 ERR_FORBIDDEN_OWNERSHIP` (phân biệt với `403 ERR_FORBIDDEN_SCOPE` khi thiếu quyền/scope). Mục 8 xác nhận và bổ sung chi tiết theo từng nhóm actor: - Customer: mọi truy vấn `order`, `cart`, `loyalty`, `wishlist`, `return-requests` phải so khớp `customerId` trong JWT `sub` claim với `customer_id` của resource — đã áp dụng đúng tại `GET/POST /v1/orders/{orderId}`, `GET /v1/payments/{paymentId}` (mục 4.1.5, 4.1.6 v3). - Seller: so khớp `sellerId` claim với `seller_id` của `product`, `order_seller`, `payout` — `GET /v1/seller/orders`, `GET /v1/seller/payouts` (mục 4.1.5, 4.1.8 v3) tự lọc theo `sellerId` trong JWT, không nhận `sellerId` qua query param. - CSR: chỉ thao tác `dispute` đã `assigned_csr_id` = chính mình hoặc chưa gán (`open`), không được sửa dispute đã gán cho CSR khác trừ khi Admin escalate — mục 4 hiện thiết kế truy cập `Dispute` toàn cục theo scope `csr:disputes:*` (không áp dụng ownership vì CSR xử lý tranh chấp toàn sàn theo phân công nội bộ); **khuyến nghị bổ sung ràng buộc `assigned_csr_id` ở tầng business logic** (không phải lỗi thiết kế API, mà là rule nghiệp vụ nội bộ — không tạo finding mới vì không phải IDOR giữa các Customer/Seller khác nhau). - Ops: giới hạn theo đơn hàng/khu vực được phân công (đã ghi nhận là "chi tiết RBAC ở mục 8" tại mục 4.2) — triển khai qua bảng phân công (assignment) tại Shipping & Fulfillment Service, kiểm tra trước khi cho phép `PATCH /v1/ops/orders/{orderId}/fulfillment`. - **Shipment tracking (`GET /v1/shipments/{shipmentId}/tracking`)**: **đã được vá ở mục 4.1.12 v3** — kiểm tra `customerId`/`sellerId` liên quan hoặc scope `ops:*`/`admin:*` toàn cục, trả `403 ERR_FORBIDDEN_OWNERSHIP` nếu không khớp (trước đây là Finding F1, nay đã giải quyết — xem §8.5). - **Admin/Ops Backoffice**: giới hạn mạng qua VPN/IP allowlist (đã quyết định ở mục 3.3) + bắt buộc MFA (role `platform_admin`) là 2 lớp phòng thủ độc lập (defense-in-depth); không cấp quyền truy cập DB Production trực tiếp trừ khẩn cấp có phê duyệt (đã ghi ở mục 3.3, giữ nguyên). - **Nguyên tắc chung**: mọi endpoint ghi dữ liệu (`POST`/`PUT`/`PATCH`/`DELETE`) đều phải qua middleware kiểm tra scope **và** ownership trước khi vào business logic — khuyến nghị triển khai như một lớp policy tập trung (VD OPA/Open Policy Agent hoặc middleware dùng chung trong BFF) để tránh mỗi service tự implement khác nhau và bỏ sót. ## 8.2 Bảo vệ dữ liệu (Data Protection) > Dựa trực tiếp trên danh sách cột **[PII]**/**[Payment]** đã đánh dấu ở mục 5.5 v3 — bảng dưới xác nhận biện pháp cụ thể cho từng nhóm, không lặp lại toàn bộ danh sách cột. ### 8.2.1 Mã hoá at-rest | Nhóm dữ liệu | Biện pháp | Ghi chú | |---|---|---| | Toàn bộ RDS PostgreSQL (database-per-service) | Mã hoá at-rest bằng AWS KMS (encryption at rest cấp instance/storage), khoá riêng theo service hoặc theo nhóm mức nhạy cảm (Payment/Commission/Seller/**Audit & Compliance** dùng CMK riêng, tách khỏi Review/Notification) | NFR-04, NFR-05 | | Cột nhạy cảm cao: `seller_bank_account.account_number`, `mfa_device.secret_encrypted`, `seller.tax_code`, `seller.business_license_number` | **Mã hoá tầng ứng dụng (application-level, AES-256-GCM)** bổ sung, khoá quản lý qua KMS envelope encryption — giảm rủi ro nếu bị SQL injection đọc thẳng DB hoặc nhân sự nội bộ (DBA) truy cập trực tiếp không qua ứng dụng | Khớp đề xuất mục 5.5; đây là control **bổ sung** so với mã hoá at-rest mặc định của RDS | | S3 (ảnh KYC, ảnh sản phẩm) | SSE-KMS, bucket KYC tách riêng, **không public**, versioning + cross-region replication (đã chốt mục 5.3.2); truy cập Admin xem tài liệu KYC qua **pre-signed URL TTL ≤5 phút** — **đã triển khai ở mục 6.1.5 v2** (Admin gọi Seller Management Service để sinh `viewUrl`, không truy cập trực tiếp object storage) | FR-17 — endpoint cụ thể (`GET .../kyc-documents/{documentId}/view-url`) chưa có ở mục 4 v3, xem finding §8.5 | | PII còn lại (`email`, `phone`, `full_name`, địa chỉ) | Mã hoá at-rest theo KMS mặc định của RDS là đủ (không cần application-level do tần suất truy vấn cao, đánh đổi hiệu năng) | Khớp mục 5.5 | | `user_account.failed_login_count`/`locked_until`/`last_failed_login_at` (mục 5.2.1 v3) | Không phải PII/Payment nhưng là dữ liệu bảo mật nhạy cảm — mã hoá at-rest mặc định của RDS là đủ; **kiểm soát ghi** chỉ qua luồng xác thực nội bộ (Identity & Access Service), không expose qua bất kỳ API đọc công khai nào (khớp ghi chú mục 5.5 v3) | FR-01, FR-27 | ### 8.2.2 Mã hoá in-transit & quản lý secret - **TLS 1.2+ bắt buộc** cho mọi kết nối: Client ↔ CDN/WAF/ALB, ALB ↔ API Gateway/BFF, BFF ↔ service nội bộ; bật HSTS ở tầng CDN/ALB. - **Secret/key management**: AWS Secrets Manager cho DB credentials, API key/secret VNPay/Momo/GHN/GHTK, OAuth client secret, SMTP/SMS provider key — không hard-code trong code/CI/CD; rotation tự động cho DB credentials, rotation thủ công có lịch (khuyến nghị 90 ngày) cho API key bên thứ ba (phụ thuộc khả năng rotate của từng đối tác). - **Payout batch file** (chứa `seller_bank_account.account_number`, `account_holder_name`): **đã có hướng dẫn kênh truyền ở mục 6.1.4 v2** (SFTP + mã hoá PGP hoặc API HTTPS của ngân hàng đối tác — ngân hàng cụ thể chưa chốt, ghi nhận là giả định/openQuestion tại mục 6, không phải finding bảo mật còn tồn đọng); không qua email trong mọi trường hợp. - **Message broker (Kafka/MSK)**: bật mã hoá in-transit (TLS) + ACL theo topic, đặc biệt các event chứa PII/tài chính (`OrderDelivered`, `PaymentConfirmed`, `PayoutScheduled`, và các domain event ghi `audit_log` như `KycDocumentVerified`/`DisputeResolved`/`PayoutRetried`/`SellerLocked` mục 5.2.11 v3) — chỉ consumer service liên quan được subscribe — **vẫn là finding chưa xử lý** vì mục 3 (kiến trúc) chưa cập nhật, xem **Finding F10** (§8.5, mục 3, low, không đổi so với v1). ### 8.2.3 Masking & giảm thiểu lộ dữ liệu - **Log/APM/tracing**: mọi log ứng dụng (CloudWatch Logs, APM traces) phải qua log-scrubber middleware để masking `email` (`c***@domain.com`), `phone` (ẩn 4 số giữa), `account_number` (chỉ hiện 4 số cuối), không log `password`, `secret_encrypted`, `gateway_transaction_ref` đầy đủ ở mức DEBUG trên môi trường Production. - **`payment.raw_gateway_response` (jsonb, mục 5.2.4)**: cần ràng buộc tại tầng ứng dụng chỉ lưu phần phản hồi phi thẻ (đã ghi chú ở mục 5) — bổ sung: whitelist field được lưu (không lưu nguyên payload thô nếu gateway trả kèm dữ liệu nhạy cảm ngoài dự kiến). - **Staging/Dev**: không chứa PII/KYC thật (đã chốt mục 3.3) — xác nhận lại quy trình anonymize dữ liệu khi sao chép Production → Staging (hash/mask `email`, `phone`, xoá `tax_code`/`account_number` thật, thay bằng dữ liệu giả lập nhất quán để giữ khả năng test). ### 8.2.4 Quyền của chủ thể dữ liệu (NĐ13/2023) - Quy trình xoá/ẩn danh (đã có ở mục 5.3.6) cần bổ sung: **xác thực danh tính người yêu cầu** trước khi xử lý (tránh giả mạo yêu cầu xoá tài khoản người khác), thời hạn phản hồi theo luật định, và log lại yêu cầu (ai yêu cầu, khi nào, xử lý bởi ai) vào `audit_log` (nay đã có bảng cụ thể ở mục 5.2.11 v3, xem §8.2.5). - **Quyền truy cập/xuất dữ liệu cá nhân ("right to access")**: brief/mục 2/5 chưa đề cập endpoint hoặc quy trình cho phép Customer/Seller yêu cầu xuất toàn bộ dữ liệu cá nhân của mình — đây là nghĩa vụ thường đi kèm NĐ13/2023, cần bổ sung (ít nhất là quy trình vận hành thủ công qua CSR ở giai đoạn đầu, không nhất thiết phải tự động hoá ngay). ### 8.2.5 Nhật ký kiểm toán (`audit_log`) — chính sách bảo mật (mới — v2) > Trả lời trực tiếp yêu cầu người duyệt (mục 3): quyết định cho bảng `audit_log` (mục 5.2.11 v3, Audit & Compliance Service). **(a) Redact/mask trường cực nhạy cảm trong `before_json`/`after_json` — QUYẾT ĐỊNH: có, bắt buộc.** - Nguyên tắc: giá trị nhạy cảm cao (`seller_bank_account.account_number`, `seller.tax_code`, `seller.business_license_number`/số CMND-CCCD trong `kyc_document`) **không bao giờ** được ghi ở dạng đầy đủ (raw) vào `audit_log`, kể cả khi đã mã hoá tầng ứng dụng ở nguồn (§8.2.1) — vì mục đích audit chỉ cần biết "đã thay đổi từ giá trị X sang Y", không cần giá trị đầy đủ. - Vị trí thực hiện masking: **tại service nguồn phát sự kiện** (Seller Management Service khi phát `KycDocumentVerified`/`SellerLocked`, Commission & Payout Service khi phát `CommissionRuleUpdated`/`PayoutRetried`), **trước khi** publish domain event lên Kafka/MSK — không để giá trị raw đi qua message broker dù chỉ tạm thời (khớp lưu ý ACL/mã hoá topic §8.2.2). Audit & Compliance Service chỉ ghi lại snapshot đã được masking từ nguồn, không tự giải mã/hiển thị lại giá trị gốc. - Quy tắc masking cụ thể: - `account_number`: chỉ giữ 4 ký tự cuối, còn lại thay bằng `*` (VD `**********1234`). - `tax_code`, `business_license_number`, số CMND/CCCD: giữ 3 ký tự đầu và 2 ký tự cuối, phần giữa thay bằng `*` (VD `079*******45`). - Các trường KYC dạng file (`file_url_s3`): **không** ghi đường dẫn S3 vào `audit_log` (tránh audit_log trở thành kênh truy cập gián tiếp tới object KYC) — chỉ ghi `document_type` và `verified_status` thay đổi. - Snapshot `before_json`/`after_json` bổ sung cờ `"_redacted": true` khi có trường bị masking, để người đọc audit biết dữ liệu đã qua xử lý, không phải thiếu sót ghi log. - Đây là quyết định của mục 8 nhưng **không** yêu cầu sửa lại schema `audit_log` ở mục 5 (kiểu cột `jsonb` đã đủ linh hoạt chứa giá trị đã masking) — chỉ là ràng buộc ở tầng ứng dụng khi ghi dữ liệu, không tạo finding hướng về mục 5. **(b) Kiểm soát truy cập đọc `audit_log` — QUYẾT ĐỊNH: chỉ scope `admin:audit:read` (Platform Admin), không cấp cho Ops/CSR.** - Lý do: `audit_log` chứa vết hành động nhạy cảm xuyên toàn sàn (duyệt KYC, khoá seller, cấu hình hoa hồng, quyết định dispute, retry payout) — phạm vi đọc rộng hơn phạm vi tác nghiệp thường nhật của Ops/CSR; giới hạn ở Platform Admin giảm bề mặt rủi ro lộ thông tin điều tra nội bộ. - Đề xuất scope mới `admin:audit:read` (không dùng chung `admin:*` để có thể tách nhỏ quyền sau này nếu marketplace cần vai trò "Security/Compliance Officer" riêng ở giai đoạn sau — hiện chưa có trong danh sách actor mục 1). - **Mục 4 chưa có endpoint đọc `audit_log`** (mục 5.5/5.2.11 v3 đã ghi chú giao cho `api-designer`, nhưng mục 4 v3 chưa bổ sung) — ghi nhận là finding mới hướng về mục 4 (xem §8.5), không tự thiết kế endpoint ở đây. **(c) Retention — QUYẾT ĐỊNH: giữ nguyên 5 năm cho phần lớn `audit_log`, khuyến nghị nâng lên 10 năm riêng cho nhóm hành động tài chính.** - Đa số hành động (KYC review, khoá/mở seller) phục vụ mục đích audit an ninh/vận hành — **5 năm** (như mục 5.3.6 đã chốt) là hợp lý và nhất quán với thông lệ audit an ninh. - **Riêng** các bản ghi `audit_log` có `resource_type` gắn trực tiếp tới nghiệp vụ tài chính (`commission_rule` khi thay đổi `hold_days`/`commission_percent`, `payout` khi retry, `dispute` khi quyết định là `refund`) nên áp dụng retention **10 năm**, khớp với retention của `payment`/`commission_transaction`/`payout` ở mục 5.3.6 (thông lệ chứng từ kế toán) — vì các bản ghi audit này là bằng chứng bổ trợ cho quyết định tài chính, tách rời hoặc xoá sớm hơn dữ liệu gốc có thể gây thiếu chứng cứ khi kiểm toán/thanh tra thuế. - Đây là **khuyến nghị điều chỉnh retention phân nhóm theo `resource_type`** khác với retention đơn nhất "5 năm" hiện có ở mục 5.3.6/5.2.11 — ghi nhận thành **finding hướng về mục 5** (§8.5, severity medium) vì đòi hỏi điều chỉnh chiến lược partition/archive (partition theo tháng đã có, chỉ cần logic archive job phân biệt theo `resource_type` khi tới mốc 5 năm), không tự sửa mục 5 ở đây. ## 8.3 Phòng chống rủi ro bảo mật (OWASP Top 10 — theo endpoint mục 4 & luồng mục 6) | OWASP 2021 | Endpoint/luồng cụ thể bị ảnh hưởng | Rủi ro | Biện pháp | |---|---|---|---| | **A01 – Broken Access Control** | `GET /v1/shipments/{shipmentId}/tracking` (4.1.12 v3) | IDOR — **đã vá ở mục 4 v3**: kiểm tra `customerId`/`sellerId` liên quan hoặc scope `ops:*`/`admin:*` toàn cục, `403 ERR_FORBIDDEN_OWNERSHIP` nếu không khớp (trước đây Finding F1, nay giải quyết) | Xác nhận giữ nguyên thiết kế hiện tại, không cần thay đổi thêm | | **A01 – Broken Access Control** | `PATCH /v1/admin/disputes/{disputeId}` (4.1.5), luồng 6.1.3 | CSR sửa dispute không do mình phụ trách | Kiểm tra `assigned_csr_id` = CSR hiện tại hoặc vai trò Admin — đây là rule nghiệp vụ nội bộ, không phải IDOR giữa khách hàng khác nhau, xem §8.1.2 | | **A02 – Cryptographic Failures** | `POST /v1/sellers/{sellerId}/kyc-documents`, `seller_bank_account`, `mfa_device.secret_encrypted` | Lộ dữ liệu tài chính/định danh nếu chỉ dựa mã hoá at-rest mặc định | Mã hoá tầng ứng dụng cho nhóm cột nhạy cảm cao (§8.2.1); áp dụng đồng thời cho snapshot ghi vào `audit_log` (redact — §8.2.5) | | **A03 – Injection** | `GET /v1/search/products?q=` (4.1.4) | OpenSearch query injection nếu ghép chuỗi trực tiếp từ `q` vào Query DSL | Dùng structured query builder (parameterize), không nối chuỗi thô; sanitize input, giới hạn độ dài `q` | | **A03 – Injection** | Toàn bộ endpoint ghi (checkout, KYC upload, commission rule) | SQL injection qua ORM lỏng lẻo, path traversal khi upload `multipart/form-data` KYC | Dùng ORM có parameterized query mặc định (không raw SQL nối chuỗi); validate MIME type/kích thước file KYC, quét virus (VD ClamAV/AWS trước khi lưu S3) | | **A04 – Insecure Design** | `POST /v1/checkout` (4.1.5) | Request không chứa giá — hệ thống tính giá server-side từ `Cart` (đã đúng thiết kế), tránh tamper giá phía client | Xác nhận giữ nguyên nguyên tắc "không tin dữ liệu giá từ client" cho mọi luồng tương lai (VD áp dụng cho `apply-coupon`, `loyalty/redeem`) | | **A04 – Insecure Design** | `PUT /v1/admin/commission-rules/{categoryId}` (4.1.8) | `holdDays` cho phép Admin override ngoài khoảng 3-7 (chỉ cảnh báo `422`, "vẫn cho phép... có xác nhận") | Bắt buộc log audit riêng (before/after + lý do) cho mọi lần override ngoài khoảng khuyến nghị — **đã có** qua event `CommissionRuleUpdated` ghi `audit_log` (mục 5.2.11/6.1.4 v2) | | **A05 – Security Misconfiguration** | API Gateway/BFF, mã lỗi chuẩn hoá (4.1.13) | Rò rỉ stack trace/chi tiết hệ thống qua `ERR_INTERNAL` | Response `500` không bao giờ trả chi tiết exception nội bộ ra client, chỉ `traceId` để tra log nội bộ (đã đúng thiết kế hiện tại, xác nhận giữ nguyên) | | **A05 – Security Misconfiguration** | Môi trường Dev/Staging (3.3) | Feature flag "mặc định bật" ở Dev có thể lộ tính năng chưa hoàn thiện nếu môi trường lộ ra ngoài | Xác nhận Dev/Staging không có DNS/IP public không cần thiết, chỉ qua VPN nội bộ | | **A06 – Vulnerable & Outdated Components** | Toàn bộ service (container hoá ECS Fargate/EKS) | Dependency có lỗ hổng đã biết | SCA scan (Trivy/Snyk/Dependabot) trong CI/CD — thuộc phạm vi mục 9, dẫn chiếu chéo, không thiết kế lại ở đây | | **A07 – Identification & Authentication Failures** | `/v1/auth/login`, `/v1/auth/mfa/challenge` (4.1.3) | Brute-force, credential stuffing | Rate limit (IP, mục 4.2) + account lockout theo vai trò (§8.1.1a, v2) + captcha — **đã có đủ cột hỗ trợ ở mục 5 v3, chỉ còn thiếu mã lỗi `423 ERR_ACCOUNT_LOCKED` ở mục 4 (finding §8.5)** | | **A08 – Software & Data Integrity Failures** | `/v1/payments/webhooks/{vnpay,momo}`, `/v1/webhooks/{ghn,ghtk}` (4.1.6, 4.1.12 v3) | Webhook giả mạo/replay nếu chỉ kiểm tra chữ ký mà không kiểm tra thời gian | **Đã triển khai ở mục 4 v3**: xác thực chữ ký + kiểm tra timestamp (từ chối nếu lệch quá 5 phút) + idempotency theo `gatewayTransactionRef` (trước đây Finding F3, nay giải quyết) | | **A09 – Security Logging & Monitoring Failures** | Toàn hệ thống, đặc biệt hành động Admin (KYC review, dispute resolution, commission override, payout retry, khoá/mở seller) | Thiếu audit trail tập trung để điều tra sự cố/gian lận | **Đã triển khai ở mục 5 v3/6 v2**: bảng `audit_log` tại Audit & Compliance Service, ghi qua domain event cho toàn bộ hành động nhạy cảm liệt kê (trước đây Finding F7, nay giải quyết); chính sách redact/access-control/retention chốt tại §8.2.5 (v2); giám sát/alerting realtime thuộc mục 9 (dẫn chiếu chéo) | | **A10 – SSRF** | Payment/Shipping Service gọi ra VNPay/Momo/GHN/GHTK (mục 3.4) | Rủi ro thấp vì URL đối tác cấu hình cứng (không nhận URL từ input người dùng); cần xác nhận không có endpoint nào nhận URL callback tuỳ ý từ client | Không phát hiện endpoint SSRF cụ thể trong mục 4/6 hiện tại; khuyến nghị giữ nguyên tắc "không bao giờ gọi ra ngoài theo URL do client cung cấp" khi mở rộng tính năng sau này | **CSRF**: vì API dùng JWT Bearer (không session cookie truyền thống) nên rủi ro CSRF thấp với access token lưu trong memory; nếu triển khai theo khuyến nghị §8.1.1 (refresh token trong cookie `HttpOnly`), bắt buộc bổ sung CSRF token (double-submit) cho các request ghi dùng cookie — cần đồng bộ với thiết kế frontend ở mục 7 (chưa có, xem `openQuestions`). **Rate limiting bổ sung**: `POST /v1/customers/me/loyalty/redeem` **đã yêu cầu `Idempotency-Key` bắt buộc ở mục 4.1.9 v3** (trước đây Finding F4, nay giải quyết); vẫn khuyến nghị rate limit theo user cho endpoint này và `POST /v1/cart/apply-coupon` để chống dò mã coupon/lạm dụng đổi điểm hàng loạt bằng script (khuyến nghị bổ sung, không phải lỗi thiết kế đã có). ## 8.4 Tuân thủ (Compliance) | Quy định | Trạng thái áp dụng | Ghi chú kỹ thuật | |---|---|---| | **PCI-DSS** | **Áp dụng, scope thu hẹp** (không lưu số thẻ — đã xác nhận kiến trúc mục 3.1, dữ liệu bảng mục 5.2.4) | Nếu VNPay/Momo tích hợp theo hình thức **redirect** (không nhúng iframe/form nhập thẻ trên domain của sàn), scope tương ứng **SAQ A** (đơn giản nhất) — cần xác nhận hình thức tích hợp cụ thể với 2 gateway (openQuestion); dù scope giảm vẫn khuyến nghị: WAF với OWASP Core Rule Set (đã có ở mục 3.2), quét lỗ hổng bên ngoài định kỳ (ASV scan hàng quý) nếu domain thanh toán thuộc phạm vi SAQ yêu cầu, và pentest ứng dụng hàng năm — các hạng mục này có chi phí, cần xác nhận ngân sách (ngân sách/timeline hiện "chưa xác định" theo brief) | | **NĐ13/2023 (Bảo vệ dữ liệu cá nhân)** | Áp dụng đầy đủ (hasPII=true) | Đã có: mã hoá, retention (mục 5.3.6), right-to-delete (mục 5.3.6 + bổ sung §8.2.4), audit trail cho yêu cầu xoá (§8.2.5, `audit_log`). Còn thiếu: DPIA (Data Protection Impact Assessment) chưa thực hiện — khuyến nghị thực hiện trước go-live; cơ chế consent quản lý (marketing email/SMS opt-in/opt-out) — đã có `notification-preferences` (FR-12) nhưng chưa rõ có tách riêng consent marketing vs giao dịch bắt buộc hay không — **openQuestion** | | **NĐ52/85 (thông báo website TMĐT marketplace)** | Áp dụng — chủ yếu là nghĩa vụ pháp lý/hành chính (đăng ký với Bộ Công Thương), không phải control kỹ thuật của mục 8 | Yêu cầu kỹ thuật liên quan duy nhất: hiển thị thông tin đăng ký/logo xác nhận ở footer — thuộc mục 7 (UI), không lặp lại ở đây | | **Tuân thủ nội bộ khác** | Không áp dụng SSO doanh nghiệp/IdP liên kết (đã chốt "không có khách hàng B2B enterprise" ở brief) | Giữ nguyên theo ràng buộc mục 1, không đề xuất bổ sung SAML/OIDC federation ở MVP | **Trade-off/chi phí cần lưu ý** (không vượt ràng buộc ngân sách mục 1, chỉ nêu để chủ dự án cân nhắc khi ngân sách được xác định): - Mã hoá tầng ứng dụng cho cột nhạy cảm cao (§8.2.1) + redact khi ghi `audit_log` (§8.2.5) làm tăng độ phức tạp phát triển/vận hành (quản lý key rotation, chi phí CPU giải mã, logic masking tại nhiều service nguồn) — chấp nhận được ở quy mô "large" có PII/Payment, nhưng cần thời gian dev bổ sung so với chỉ dùng mã hoá at-rest mặc định. - ASV scan quý + pentest năm + AWS GuardDuty/Security Hub/Macie (phát hiện PII ngoài ý muốn) là chi phí vận hành liên tục, không bắt buộc về mặt kỹ thuật để hệ thống chạy nhưng khuyến nghị mạnh cho quy mô/loại dữ liệu hiện tại — cần xác nhận ngân sách bảo mật vận hành hàng năm (hiện brief chưa có con số). - OPA/policy-as-code cho kiểm soát ownership tập trung (§8.1.2) là lựa chọn kiến trúc bổ sung có thể triển khai đơn giản hơn bằng middleware tự viết nếu muốn giảm chi phí học/vận hành thêm một thành phần mới — nêu như một lựa chọn, không bắt buộc. - Retention 10 năm riêng cho nhóm `audit_log` tài chính (§8.2.5c) làm tăng chi phí lưu trữ dài hạn (dù đã partition theo tháng) — chi phí storage lạnh (S3 Glacier archive sau khi hết hạn truy vấn nhanh) là hợp lý, cần chủ dự án xác nhận khi có ngân sách vận hành cụ thể. ## 8.5 Rủi ro phát hiện & khuyến nghị > **(v2)** Rà soát lại toàn bộ 10 finding của v1: 9/10 đã được giải quyết ở mục 4 v3 / 5 v3 / 6 v2 (liệt kê tại bảng "Finding đã giải quyết" bên dưới, giữ lại để truy vết lịch sử — không tính vào `findings` của structured output). 1 finding cũ (F10 — MSK ACL) và 3 finding mới phát sinh từ mục 6 v2 vẫn còn tồn đọng, được liệt kê ở bảng "Finding còn tồn đọng" — đây là các finding đã được **người duyệt xác nhận không chạy lại mục nguồn**, gom vào Document Control §0.4 của bản ráp SAD này thay vì đưa vào `findings` của structured output. ### Finding đã giải quyết (lịch sử, không còn hành động cần thiết) | # | Mục đã sửa | Vấn đề gốc (v1) | Trạng thái v2 | |---|---|---|---| | F1 | 04 v3 | `GET /v1/shipments/{shipmentId}/tracking` thiếu ràng buộc sở hữu → IDOR | **Đã giải quyết** — mục 4.1.12 v3 bổ sung kiểm tra ownership, `403 ERR_FORBIDDEN_OWNERSHIP` | | F2 | 04 v3 | `X-Guest-Session-Id` chưa quy định CSPRNG/cookie flags | **Đã giải quyết** — mục 4.1.1 v3: CSPRNG ≥128-bit, cookie `HttpOnly/Secure/SameSite=Lax`, rate-limit riêng theo IP cho endpoint ghi Cart Guest | | F3 | 04 v3 | Webhook thiếu chống replay (timestamp/nonce) | **Đã giải quyết** — mục 4.1.6 v3: kiểm tra timestamp lệch ≤5 phút + idempotency theo `gatewayTransactionRef` | | F4 | 04 v3 | `loyalty/redeem` thiếu `Idempotency-Key` | **Đã giải quyết** — mục 4.1.9 v3 bổ sung `Idempotency-Key` bắt buộc | | F5 | 04 v3 | OAuth callback thiếu kiểm tra `state`/xử lý trùng email | **Đã giải quyết** — mục 4.1.3 v3: `state` bắt buộc (`400 ERR_OAUTH_STATE_INVALID`), `409 ERR_ACCOUNT_LINK_REQUIRED` khi trùng email, không auto-merge | | F6 | 05 v3 | `user_account` thiếu cột chống brute-force | **Đã giải quyết** — mục 5.2.1 v3 bổ sung `failed_login_count`/`locked_until`/`last_failed_login_at`; chính sách ngưỡng/thời lượng chốt tại §8.1.1a (v2) | | F7 | 05 v3 | Thiếu bảng audit log tập trung | **Đã giải quyết** — mục 5.2.11 v3 bổ sung `audit_log` tại Audit & Compliance Service; chính sách redact/access/retention chốt tại §8.2.5 (v2) | | F8 | 06 v2 | KYC document chưa có cơ chế xem an toàn (pre-signed URL) | **Đã giải quyết** — sequence 6.1.5 v2 bổ sung bước sinh pre-signed URL TTL ≤5 phút; **lưu ý phụ**: endpoint tương ứng chưa có ở mục 4 v3 → xem finding F11 (gom tại §0.4b) | | F9 | 06 v2 | Payout batch file thiếu kênh truyền/mã hoá cụ thể | **Đã giải quyết (ở mức thiết kế)** — sequence 6.1.4 v2 nêu kênh SFTP+PGP hoặc API HTTPS ngân hàng đối tác; ngân hàng cụ thể vẫn là giả định/openQuestion tại mục 6 (không phải finding bảo mật còn tồn đọng) | ### Finding còn tồn đọng (đã gom vào Document Control §0.4 theo quyết định người duyệt — không lặp lại trong `findings` của bản ráp này) | # | Mục cần sửa | Vấn đề | Mức độ | Khuyến nghị | |---|---|---|---|---| | F10 | 03 | Sơ đồ kiến trúc (3.2) chưa đề cập ACL/mã hoá theo topic cho Message Broker (Kafka/MSK), trong khi nhiều event mang dữ liệu tài chính/PII gián tiếp (`PaymentConfirmed`, `PayoutScheduled`, `OrderDelivered`, và nay thêm các domain event ghi `audit_log`) | Low (không đổi so với v1) | Bổ sung: bật TLS in-transit cho MSK, ACL theo topic giới hạn consumer là service liên quan, không cho mọi service subscribe toàn bộ topic | | F11 | 04 | Mục 4 chưa có endpoint cho Admin lấy pre-signed URL xem một `KYCDocument` cụ thể (sequence 6.1.5 v2 đã mô tả cơ chế nhưng thiếu endpoint tương ứng, VD `GET /v1/admin/sellers/{sellerId}/kyc-documents/{documentId}/view-url`) | Low — mục 4 đã hết vòng sửa (v3, approved), ghi nhận để xử lý thủ công/vòng sau | Bổ sung endpoint trả `{ viewUrl, expiresInSeconds<=300 }`, không trả `file_url_s3` trực tiếp | | F12 | 04 | Mục 4 chưa có mã lỗi cho trường hợp tài khoản bị khoá tạm do vượt ngưỡng đăng nhập sai (`user_account.locked_until`, mục 5.2.1 v3) — chính sách ngưỡng/thời lượng đã chốt tại §8.1.1a (v2) | Low — mục 4 đã hết vòng sửa (v3, approved), ghi nhận để xử lý thủ công/vòng sau | Bổ sung mã lỗi `423 ERR_ACCOUNT_LOCKED` kèm `retryAfterSeconds`, áp dụng tại `POST /v1/auth/login` | | F13 | 04 | Mục 4 chưa có endpoint đọc `audit_log` (mục 5.2.11/5.5 v3 đã ghi chú giao cho `api-designer` nhưng chưa được bổ sung ở mục 4 v3) | Low — cùng lý do F11/F12, ghi nhận thủ công/vòng sau | Bổ sung endpoint dạng `GET /v1/admin/audit-logs` (scope `admin:audit:read` — xem §8.2.5b), hỗ trợ filter theo `resource_type`/`resource_id`/`actor_id`/khoảng thời gian | | F14 | 05 | Retention `audit_log` hiện đồng nhất 5 năm (mục 5.2.11/5.3.6) — khuyến nghị phân nhóm theo `resource_type`: giữ 5 năm cho hành động vận hành (KYC, khoá seller), nâng lên 10 năm cho hành động gắn trực tiếp tài chính (`commission_rule`, `payout`, `dispute` quyết định refund) để nhất quán với retention `payment`/`payout` (mục 5.3.6) | Medium | Điều chỉnh logic archive/xoá của `audit_log` theo `resource_type` thay vì một mốc retention duy nhất; không cần đổi schema (cột `jsonb`/`resource_type` đã đủ) | --- # 9. Kế hoạch vận hành & Kiểm thử (Testing & Deployment) > **Đầu vào:** `00-project-brief.md` (profile: `scale=large`, `hasPayment=true`, `hasPII=true`, cloud AWS, Dev/Staging/Production, on-call giờ hành chính + escalation 24/7); `02-phan-tich-yeu-cau.md` (FR-01..FR-27, NFR-01..NFR-08); `03-kien-truc.md` (11 service, môi trường 3.3, tích hợp bên thứ ba 3.4); `05-thiet-ke-du-lieu.md` v3 (backup/RTO-RPO 5.3.2, retention 5.3.6); `06-luong-xu-ly.md` v2 (Business Rules BR-01..BR-15, sequence checkout/payout/KYC/dispute/login); `08-bao-mat.md` v2 (§8.1–8.5, OWASP, findings F10/F11/F12/F14 tồn đọng). > > **Right-sizing:** `scale=large` + `hasPayment=true` + `hasPII=true` → áp dụng đầy đủ pipeline CI/CD nhiều bước (build → test → scan → deploy theo môi trường), monitoring/alerting chi tiết theo NFR, và kế hoạch DR có RTO/RPO phân nhóm theo mức độ nghiêm trọng của service — không có mục nào được rút gọn thành "không áp dụng" ở phần này. ## 9.1 Chiến lược kiểm thử (Test Strategy) ### 9.1.1 Unit Testing | Hạng mục | Nội dung | |---|---| | Phạm vi | Business logic thuần trong từng service — đặc biệt các công thức/quy tắc phức tạp: `BR-01` (tách đơn theo seller), `BR-02` (giữ tồn kho), `BR-03` (tính hoa hồng), `BR-04/BR-05` (kỳ giữ tiền/điều kiện release payout), `BR-06/BR-07/BR-08` (loyalty), `BR-09` (điều kiện coupon), `BR-10` (điều kiện huỷ đơn), `BR-11` (điều kiện review), `BR-12` (chính sách MFA/lockout — §8.1.1a), `BR-13` (duyệt KYC), `BR-14` (dispute), `BR-15` (fallback vận chuyển) | | Trách nhiệm | Đội phát triển sở hữu từng service (Identity, Catalog, Cart & Order, Payment, Seller Management, Commission & Payout, Promotion & Loyalty, Review, Notification, Shipping & Fulfillment, Audit & Compliance) — mỗi PR bắt buộc kèm unit test cho logic mới/sửa | | Công cụ | JUnit/Jest/PyTest tuỳ stack thực thi (kiến trúc sư chưa ràng buộc ngôn ngữ cụ thể ở mục 3 — giả định stack backend phổ biến cho microservices, VD Node.js/Java/Go); coverage tối thiểu khuyến nghị **70%** cho module business logic của Cart & Order, Payment, Commission & Payout (service tài chính/giao dịch cốt lõi); **50%** cho service ít rủi ro hơn (Review, Notification) | | Ngưỡng chặn merge | Build fail nếu coverage giảm so với baseline hoặc unit test đỏ — enforce ở bước "test" của pipeline CI/CD (9.3) | ### 9.1.2 Integration Testing | Hạng mục | Nội dung | |---|---| | Phạm vi | (a) Giao tiếp đồng bộ REST giữa BFF ↔ service nội bộ (VD Cart & Order ↔ Catalog khi reserve tồn kho — BR-02); (b) luồng bất đồng bộ qua Message Broker (Kafka/MSK) — `OrderPlaced`, `PaymentConfirmed`, `OrderDelivered`, `CommissionCalculated`, `PayoutScheduled`, `SellerApproved`, các domain event ghi `audit_log`; (c) tích hợp bên thứ ba ở môi trường Staging dùng sandbox: VNPay/Momo (sandbox), GHN/GHTK (sandbox), Google/Facebook OAuth (test app), email/SMS provider (test mode) | | Trách nhiệm | QA + đội backend liên quan; test theo ranh giới bounded-context (mục 3.1) — không kiểm thử xuyên transaction DB vật lý (vì database-per-service, chỉ có FK logic qua event) | | Công cụ | Postman/Newman hoặc REST-assured cho API; Testcontainers (Kafka, PostgreSQL) hoặc môi trường Staging thực để test contract giữa producer/consumer event; contract testing (Pact) khuyến nghị cho các cặp service có API nội bộ thay đổi thường xuyên (VD Cart & Order ↔ Commission & Payout) | | Trọng tâm rủi ro cao | Idempotency của webhook thanh toán (chống replay, mục 4/8 v3), saga đặt hàng → thanh toán → trừ kho → hoa hồng (BR-01/02/03), fallback vận chuyển GHN→GHTK (BR-15) | ### 9.1.3 UAT (User Acceptance Testing) | Hạng mục | Nội dung | |---|---| | Phạm vi | Toàn bộ FR **Must** (FR-01, 03–09, 12, 17–19, 21–26) theo kịch bản nghiệp vụ đầu-cuối trên môi trường Staging (dữ liệu ẩn danh hoá, không PII/KYC thật — theo mục 3.3); FR **Should**/**Could** (FR-02, 10, 11, 13–16, 20, 27) kiểm thử nếu đã hoàn thành trong phạm vi release | | Trách nhiệm | Product Owner + đại diện nghiệp vụ (vận hành sàn, CSR, đại diện seller nếu có) xác nhận; QA chuẩn bị kịch bản, môi trường, dữ liệu test | | Kịch bản tiêu biểu | Checkout đa seller trọn vẹn (duyệt → giỏ hàng → thanh toán → theo dõi đơn → nhận hàng → đánh giá); seller onboarding từ đăng ký đến payout đầu tiên; CSR xử lý một khiếu nại từ đầu đến khi payout bị loại/được release | | Điều kiện thoát (exit criteria) | 100% kịch bản UAT cho FR Must đạt "Pass"; các FR Should/Could không đạt được ghi nhận là known-issue có kế hoạch khắc phục trước go-live hoặc lùi sau go-live theo quyết định Product Owner | ### 9.1.4 Performance Testing (gắn NFR cụ thể) | NFR | Kịch bản tải | Ngưỡng chấp nhận | Công cụ | |---|---|---|---| | **NFR-01** | Duyệt catalog/tìm kiếm sản phẩm (FR-04) ở tải bình thường và tải đỉnh mô phỏng flash sale | p95 response time **< 2 giây** | k6/JMeter/Gatling, chạy trên môi trường Staging có cấu hình gần Production (mục 3.3) | | **NFR-01** | Checkout & thanh toán (FR-06, FR-07) ở tải đỉnh | p95 hoàn tất checkout **< 3 giây**, kể cả khi Catalog/Search đang chịu tải đỉnh song song | k6/JMeter, kịch bản kết hợp đồng thời checkout + browse | | **NFR-02** | Load test mô phỏng flash sale: tăng dần từ tải bình thường lên **hàng chục nghìn concurrent users**, đo khả năng cache (Redis)/CDN hấp thụ tải đọc và message queue hấp thụ đột biến ghi (đặt hàng) | Không tăng lỗi 5xx đáng kể; queue lag (thời gian xử lý event `OrderPlaced`→`PaymentConfirmed`→`CommissionCalculated`) không vượt ngưỡng cảnh báo (xem 9.4); không xảy ra oversell (BR-02) dưới tải đồng thời cao | k6 (ramping-arrival-rate), theo dõi qua APM/dashboard mục 9.4 | | **NFR-03** | Chaos/failover test: chủ động tắt 1 instance của service giao dịch cốt lõi (Cart & Order, Payment, Identity) trong lúc có tải | Auto-scaling/Multi-AZ tự phục hồi, downtime cảm nhận bởi client tối thiểu, không vi phạm mục tiêu uptime 99.9% trong cửa sổ kiểm thử | AWS Fault Injection Simulator hoặc kịch bản thủ công (dừng task ECS) | | Trách nhiệm | Đội DevOps/SRE chủ trì kịch bản và hạ tầng đo; đội backend hỗ trợ phân tích bottleneck theo service | | | | Tần suất | Trước mỗi lần go-live/major release, và định kỳ trước mùa cao điểm (VD trước các đợt khuyến mãi lớn dự kiến) | | | > Ghi chú: NFR-01/NFR-03 là **giả định mặc định đã chốt** ở brief (chưa có SLA hợp đồng thực tế xác nhận) — nếu số liệu tải thực tế sau go-live khác biệt đáng kể so với giả định "large" ở mục 1/5, cần điều chỉnh lại kịch bản/ngưỡng performance test (đã ghi trong `openQuestions`). ### 9.1.5 Security Testing (dựa trên findings mục 8) | Hạng mục | Nội dung | Nguồn | |---|---|---| | SAST (Static Application Security Testing) | Quét mã nguồn mỗi lần build trong CI/CD (SonarQube hoặc Semgrep) — tập trung vào các endpoint ghi dữ liệu (checkout, KYC upload, commission rule) theo rủi ro A03 Injection đã nêu ở mục 8.3 | §8.3 A03 | | SCA/Dependency scanning | Snyk/Trivy/Dependabot quét lỗ hổng thư viện của mọi service (container ECS Fargate/EKS) — chặn build nếu phát hiện lỗ hổng mức Critical/High chưa có bản vá | §8.3 A06 | | DAST/Penetration test | Pentest ứng dụng hàng năm + ASV scan hàng quý nếu phạm vi PCI-DSS SAQ A yêu cầu (mục 8.4) — ưu tiên các luồng thanh toán, KYC upload, webhook | §8.4 | | Kiểm thử theo finding tồn đọng mục 8 | **F10** (MSK ACL/TLS — kiểm tra service không liên quan không subscribe được topic PII/tài chính); **F11** (khi endpoint pre-signed URL KYC được bổ sung ở mục 4 — kiểm tra TTL ≤5 phút, không lộ `file_url_s3` trực tiếp); **F12** (khi mã lỗi `423 ERR_ACCOUNT_LOCKED` được bổ sung — kiểm tra hành vi khoá/mở khoá đúng theo bảng §8.1.1a); **F13** (khi endpoint `GET /v1/admin/audit-logs` được bổ sung — kiểm tra chỉ scope `admin:audit:read` truy cập được, dữ liệu nhạy cảm đã redact theo §8.2.5a) | §8.5 F10, F11, F12, F13 | | Account lockout / brute-force | Test chủ động: đăng nhập sai liên tiếp theo ngưỡng từng vai trò (Customer/Seller 5 lần → khoá 15 phút; Platform Admin 3 lần → khoá 30 phút — §8.1.1a); xác minh không lộ "email có tồn tại hay không" ở thông báo lỗi thông thường | §8.1.1a | | OAuth/social login | Test giả mạo `state` (kỳ vọng `400 ERR_OAUTH_STATE_INVALID`), test email trùng tài khoản có sẵn (kỳ vọng `409 ERR_ACCOUNT_LINK_REQUIRED`, không auto-merge) | §8.1.1, §8.3 | | Webhook replay/idempotency | Gửi lại IPN VNPay/Momo với timestamp quá hạn hoặc `gatewayTransactionRef` trùng lặp — kỳ vọng bị từ chối/không lặp side-effect | §8.3 A08 | | PII masking trong log | Kiểm tra log CloudWatch/APM không lộ `email`/`phone`/`account_number` đầy đủ ở môi trường Production | §8.2.3 | | Trách nhiệm | Security champion trong mỗi đội (do chưa có đội Security/Compliance Officer riêng theo brief) phối hợp DevOps chạy scan tự động trong pipeline; pentest/ASV scan thuê ngoài định kỳ | | ## 9.2 Kịch bản kiểm thử (Test Cases) > Quy ước: mỗi `TC-xx` gắn đúng **1** `FR-xx`. Chỉ viết test case khi FR có acceptance criteria đủ rõ để suy ra Given-When-Then; trường hợp FR/BR còn thiếu số liệu cụ thể (VD ngưỡng VND hạng thành viên, công thức hoàn tiền dispute), test case nêu rõ phần **chưa kiểm thử được** và dẫn sang `openQuestions`. ### 9.2.1 Khách hàng & tài khoản | TC | FR | Priority | Given | When | Then | |---|---|---|---|---|---| | TC-01 | FR-01 | Must | Guest chưa có tài khoản, nhập email hợp lệ chưa tồn tại | Gửi `POST /v1/auth/register` với email/mật khẩu hợp lệ (≥10 ký tự) | Tài khoản `Customer` được tạo, `password_hash` lưu bằng bcrypt/argon2id, trả `201` | | TC-02 | FR-01 | Must | Tài khoản Customer tồn tại, nhập sai mật khẩu 5 lần liên tiếp trong thời gian ngắn | Gửi `POST /v1/auth/login` lần thứ 6 | `user_account.locked_until = now + 15 phút` (§8.1.1a); phản hồi không tiết lộ email có tồn tại hay không; lần đăng nhập tiếp theo trong 15 phút bị từ chối ngay không so khớp mật khẩu | | TC-03 | FR-02 | Could | Email `a@x.com` đã có tài khoản email/password, chưa liên kết OAuth | Đăng nhập Google bằng cùng email `a@x.com` | Hệ thống **không** tự merge tài khoản; trả `409 ERR_ACCOUNT_LINK_REQUIRED`, yêu cầu xác minh sở hữu email trước khi liên kết | | TC-04 | FR-03 | Must | Customer đã đăng nhập, có 1 địa chỉ mặc định | Thêm địa chỉ giao hàng mới và đặt làm mặc định | Địa chỉ mới được lưu với `is_default=true`; địa chỉ cũ tự động chuyển `is_default=false` | ### 9.2.2 Catalog, giỏ hàng & checkout | TC | FR | Priority | Given | When | Then | |---|---|---|---|---|---| | TC-05 | FR-04 | Must | Catalog có sản phẩm thuộc nhiều seller/category | Guest tìm kiếm theo từ khoá + lọc theo category | Kết quả trả về đúng sản phẩm khớp bộ lọc, thời gian phản hồi p95 < 2s (NFR-01) | | TC-06 | FR-05 | Must | Giỏ hàng trống của Guest (theo `session_id`) | Thêm sản phẩm từ Seller A và Seller B vào cùng giỏ hàng | `Cart` chứa `CartItem` với `seller_id` khác nhau trong cùng một `Cart` | | TC-07 | FR-06 | Must | Giỏ hàng có sản phẩm từ 2 seller, đủ tồn kho | Gọi `POST /v1/checkout` | Hệ thống tạo 1 `Order` cha và 2 `OrderSeller` con tương ứng 2 seller (BR-01); `Order.totalAmount` = tổng `OrderSeller.subtotalAmount` | | TC-08 | FR-06 | Must | Sản phẩm trong giỏ hàng có `quantity_available - quantity_reserved < quantity` yêu cầu | Gọi `POST /v1/checkout` | Trả `409 ERR_CONFLICT`, không tạo `Order`, không tăng `quantity_reserved` (BR-02) | | TC-09 | FR-07 | Must | Đơn hàng ở trạng thái `pending_payment`, khởi tạo thanh toán VNPay | VNPay gửi IPN với chữ ký hợp lệ, timestamp trong 5 phút | `Payment.status=success`; event `PaymentConfirmed` được publish; `Order/OrderSeller.status=confirmed` | | TC-10 | FR-07 | Must | Một giao dịch VNPay đã được xác nhận thành công (`gatewayTransactionRef` đã ghi nhận) | VNPay gửi lại IPN trùng `gatewayTransactionRef` (retry tự nhiên của gateway) hoặc timestamp lệch > 5 phút | Hệ thống trả `200 OK` không lặp side-effect (idempotent) cho retry hợp lệ; từ chối `400 ERR_VALIDATION` cho timestamp quá hạn — không tạo `PaymentConfirmed` lần 2 | | TC-11 | FR-08 | Must | `OrderSeller.status=pending` | Customer gọi huỷ đơn | Đơn chuyển `cancelled` (BR-10) | | TC-11b | FR-08 | Must | `OrderSeller.status=packed` | Customer gọi huỷ đơn | Bị từ chối — Customer phải dùng luồng đổi trả/khiếu nại (FR-09) thay vì huỷ trực tiếp (BR-10) | | TC-12 | FR-09 | Must | `OrderSeller.status=delivered` | Customer gửi `POST /v1/orders/{orderId}/return-requests` | Tạo `ReturnRequest(status=requested)`; nếu `PayoutHold` liên quan đang `holding`, chuyển `disputed_frozen` (BR-14a) | | TC-13 | FR-10 | Should | Customer đã đăng nhập | Thêm sản phẩm vào wishlist, sau đó xoá | `WishlistItem` được tạo rồi xoá; không cho trùng lặp (UNIQUE customer_id, product_id) | | TC-14 | FR-11 | Should | Customer đã mua `order_item` X, `OrderSeller.status=delivered` | Gửi đánh giá rating 5 sao cho sản phẩm trong `order_item` X | `Review` được tạo thành công (BR-11) | | TC-14b | FR-11 | Should | `OrderSeller.status=confirmed` (chưa giao hàng) | Gửi đánh giá cho sản phẩm chưa nhận | Bị từ chối — không cho phép đánh giá trước khi `delivered` (BR-11) | | TC-15 | FR-12 | Must | Đơn hàng vừa chuyển `PaymentConfirmed` | Notification Service consume event | Email/SMS xác nhận đơn hàng được gửi tới Customer trong thời gian hợp lý (không chặn luồng checkout chính) | | TC-16 | FR-13 | Should | Coupon `SALE10` đang `active`, còn lượt dùng, đơn hàng đạt `min_order_amount` | Áp coupon tại checkout | Giảm giá đúng theo `type`/`value` (BR-09); ghi `PromotionUsage` UNIQUE theo `(promotion_id, order_id)` | | TC-16b | FR-13 | Should | Coupon đã hết `usage_limit` | Áp coupon tại checkout | Bị từ chối, không áp dụng giảm giá (BR-09) | ### 9.2.3 Loyalty, đa ngôn ngữ/tiền tệ | TC | FR | Priority | Given | When | Then | |---|---|---|---|---|---| | TC-17 | FR-14 | Should | `OrderSeller` với `subtotalAmount = 250,000đ` chuyển `delivered` | Loyalty Service consume event `OrderDelivered` | `LoyaltyTransaction(type=earn, points=25)` theo BR-06 (`floor(250000/10000)=25`) — **lưu ý:** cách tính trên `subtotalAmount` từng `OrderSeller` là giả định của mục 6, cần xác nhận chủ dự án trước go-live (xem `openQuestions`) | | TC-17b | FR-14 | Should | `LoyaltyAccount.points_balance = 500` | Customer đổi 300 điểm lấy giảm giá | Giảm giá 30,000đ được áp dụng, `points_balance` còn 200, ghi `LoyaltyTransaction(type=redeem, points=-300)` theo bội số 100 (BR-08) | | TC-18 | FR-15 | Should | Sản phẩm có `product_i18n` cho `vi`, `en`, `ja` | Customer chuyển ngôn ngữ hiển thị sang `en` rồi `ja` | Tên/mô tả sản phẩm hiển thị đúng bản dịch tương ứng; ngôn ngữ chưa có bản dịch fallback về `vi` (mặc định) | | TC-19 | FR-16 | Could | Sản phẩm giá `500,000 VND`, `exchange_rate` USD đã cấu hình | Customer xem trang sản phẩm với hiển thị tiền tệ `USD` | Giá quy đổi tham khảo hiển thị đúng theo `rate_to_vnd`; giao dịch checkout vẫn thực hiện bằng VND (không thanh toán trực tiếp ngoại tệ) | ### 9.2.4 Seller, KYC, commission & payout | TC | FR | Priority | Given | When | Then | |---|---|---|---|---|---| | TC-20 | FR-17 | Must | Seller mới đăng ký, upload đủ 3 tài liệu bắt buộc (`business_license`, `id_card_front`, `id_card_back`) | Admin duyệt `verified` cho cả 3 tài liệu | `Seller.status=active`, event `SellerApproved` publish (BR-13) | | TC-20b | FR-17 | Must | Seller đã upload đủ tài liệu | Admin từ chối 1 tài liệu (`rejected`) | `Seller.status=rejected` kèm `reason`; Seller có thể nộp lại (quay về `pending_kyc`) | | TC-21 | FR-18 | Must | Seller sở hữu `ProductVariant` với `quantity_available=10` | Seller cập nhật tồn kho thành `20` và đổi giá bán | `inventory_stock.quantity_available=20`, `product_variant.price_amount` cập nhật; sản phẩm khác của seller khác không bị ảnh hưởng | | TC-22 | FR-19 | Must | Seller A có đơn `order_seller` X; Seller B không liên quan tới X | Seller B gọi `GET /v1/seller/orders/{X}` | Trả `403 ERR_FORBIDDEN_OWNERSHIP` (kiểm soát ownership §8.1.2); Seller A gọi cùng endpoint → trả dữ liệu thành công | | TC-23 | FR-20 | Should | Seller có `CommissionTransaction` và `Payout` trong kỳ gần nhất | Seller xem `GET /v1/seller/payouts` | Hiển thị đúng doanh thu, hoa hồng, trạng thái payout (`scheduled`/`processing`/`paid`/`failed`) chỉ của chính seller đó | | TC-24 | FR-21 | Must | Category "Điện tử" chưa có `CommissionRule` hiệu lực | Admin cấu hình `commission_percent=8%`, `effective_from=hôm nay` | `CommissionRule` mới được tạo, có hiệu lực từ ngày chỉ định; đơn hàng phát sinh sau đó tính hoa hồng theo BR-03; action ghi `audit_log` (`CommissionRuleUpdated`) | | TC-25 | FR-22 | Must | `PayoutHold.hold_until_date` đã qua, không có `Dispute` mở cho `order_seller` liên quan | Job payout hàng tuần chạy | `PayoutHold.release_status=released`, `CommissionTransaction.net_amount` được gộp vào `Payout` mới của seller (BR-04/BR-05) | | TC-25b | FR-22 | Must | `Payout.status=processing` được gửi ngân hàng | Ngân hàng từ chối batch (lỗi định dạng) | `Payout.status=failed`; hệ thống **không** tự động thử lại; Admin gọi `POST /v1/admin/payouts/{payoutId}/retry` thủ công sau xác minh; hành động ghi `audit_log` | | TC-26 | FR-23 | Must | Seller đang `active`, bị phát hiện vi phạm | Admin khoá seller (`PATCH /v1/admin/sellers/{sellerId}/status`) | `Seller.status=suspended`; seller không thể đăng sản phẩm/nhận đơn mới cho tới khi được Admin mở khoá lại; action ghi `audit_log` | | TC-27 | FR-24 | Must | Sản phẩm đang `active`, bị báo cáo vi phạm | Admin ẩn sản phẩm toàn sàn | `Product.status=hidden_by_admin`; sản phẩm không còn hiển thị ở Catalog/Search (kể cả khi seller vẫn `active`) | ### 9.2.5 Tranh chấp, vận chuyển, MFA | TC | FR | Priority | Given | When | Then | |---|---|---|---|---|---| | TC-28 | FR-25 | Must | `Dispute.status=investigating`, CSR đã được gán | CSR quyết định `refund` qua `PATCH /v1/admin/disputes/{disputeId}` | `Dispute.status=resolved`; `Payment.status=refunded`; `PayoutHold` liên quan chuyển `reversed` (loại vĩnh viễn khỏi payout, không bao giờ `released` — BR-14); action ghi `audit_log` | | TC-28b | FR-25 | Must | `Dispute.status=investigating` | CSR quyết định `reject` | `ReturnRequest.status=rejected`; `PayoutHold` quay lại `holding`, chờ `hold_until_date` release bình thường | | TC-29 | FR-26 | Must | `OrderSeller.status=confirmed`, Ops đóng gói xong | Shipping Service gọi GHN tạo vận đơn thành công | `Shipment` tạo với `tracking_number`; `OrderSeller.status=shipped`; webhook GHN cập nhật `delivered` → publish `OrderDelivered` | | TC-29b | FR-26 | Must | GHN timeout sau 3 lần retry, khu vực giao hàng được GHTK hỗ trợ | Shipping Service fallback | Vận đơn được tạo qua GHTK thay thế (BR-15); nếu cả hai lỗi, đưa vào hàng đợi Ops xử lý thủ công, `OrderSeller.status` không bị chặn | | TC-30 | FR-27 | Should | `role=platform_admin`, `mfa_enabled=false` | Đăng nhập bằng email/password đúng | Đăng nhập thành công nhưng bị chặn hoàn toàn scope `admin:*` cho tới khi hoàn tất `mfa/enroll` (BR-12) | | TC-30b | FR-27 | Should | `role=platform_admin`, `mfa_enabled=true` | Đăng nhập đúng mật khẩu | Hệ thống yêu cầu OTP (`mfaRequired:true`); nhập đúng OTP → nhận `accessToken`; nhập sai OTP quá 5 lần → challenge token bị vô hiệu | ### 9.2.6 Bảng tổng hợp Test Case → FR (dùng để điền Traceability Matrix mục 2.4) | FR | Test Case | |---|---| | FR-01 | TC-01, TC-02 | | FR-02 | TC-03 | | FR-03 | TC-04 | | FR-04 | TC-05 | | FR-05 | TC-06 | | FR-06 | TC-07, TC-08 | | FR-07 | TC-09, TC-10 | | FR-08 | TC-11, TC-11b | | FR-09 | TC-12 | | FR-10 | TC-13 | | FR-11 | TC-14, TC-14b | | FR-12 | TC-15 | | FR-13 | TC-16, TC-16b | | FR-14 | TC-17, TC-17b | | FR-15 | TC-18 | | FR-16 | TC-19 | | FR-17 | TC-20, TC-20b | | FR-18 | TC-21 | | FR-19 | TC-22 | | FR-20 | TC-23 | | FR-21 | TC-24 | | FR-22 | TC-25, TC-25b | | FR-23 | TC-26 | | FR-24 | TC-27 | | FR-25 | TC-28, TC-28b | | FR-26 | TC-29, TC-29b | | FR-27 | TC-30, TC-30b | > Toàn bộ FR-01..FR-27 đều có ít nhất 1 test case. Một số test case (TC-17, TC-25) có phần "chưa kiểm thử được đầy đủ" vì thiếu số liệu chốt (ngưỡng VND hạng thành viên, công thức hoàn tiền dispute, ngân hàng đối tác cụ thể) — xem `openQuestions`/`findings`. ## 9.3 CI/CD & Bảo mật pipeline ### 9.3.1 Pipeline build → test → scan → deploy ```mermaid flowchart LR Commit["Commit / Pull Request"] --> Build["Build\n(container image per service)"] Build --> UnitTest["Unit Test\n(coverage gate 9.1.1)"] UnitTest --> SAST["SAST\n(SonarQube/Semgrep)"] SAST --> SCA["Dependency scan\n(Snyk/Trivy/Dependabot)"] SCA --> Integration["Integration Test\n(Staging sandbox 9.1.2)"] Integration --> DeployDev["Deploy → Dev\n(auto, mọi merge vào nhánh dev)"] DeployDev --> DeployStaging["Deploy → Staging\n(auto sau QA sign-off)"] DeployStaging --> UAT["UAT + Performance/Security test\n(9.1.3, 9.1.4, 9.1.5)"] UAT --> Approval["Phê duyệt thủ công\n(Product Owner + Kiến trúc sư trưởng)"] Approval --> DeployProd["Deploy → Production\n(canary/phần trăm rollout — theo feature flag mục 3.3)"] ``` - **Build:** mỗi service đóng gói container riêng (khớp mục 3.2 — ECS Fargate/EKS), gắn tag theo commit SHA + semantic version cho release chính thức. - **Test:** unit test bắt buộc pass + coverage gate (9.1.1); build fail nếu không đạt. - **Scan:** SAST (SonarQube/Semgrep) + SCA (Snyk/Trivy/Dependabot) chạy trên mọi image trước khi cho phép deploy; **chặn deploy** nếu phát hiện lỗ hổng Critical/High chưa có ngoại lệ được phê duyệt. - **Deploy theo môi trường (khớp mục 3.3):** - **Dev:** tự động sau mỗi merge vào nhánh phát triển; feature flag mặc định bật. - **Staging:** tự động sau khi Dev pass, dùng dữ liệu ẩn danh hoá/giả lập (không PII/KYC thật) để chạy Integration/UAT/Performance/Security test. - **Production:** chỉ deploy sau khi UAT pass và có phê duyệt thủ công (Product Owner + Kiến trúc sư trưởng); rollout theo canary/phần trăm người dùng cho tính năng rủi ro cao (thay đổi luồng thanh toán/commission — đã chốt ở mục 3.3). ### 9.3.2 Phân quyền Production & quản lý secret trong pipeline | Hạng mục | Thiết kế | |---|---| | Truy cập hạ tầng Production | Chỉ đội Ops và Admin được cấp quyền qua IAM role có audit log (đã chốt mục 3.3); không truy cập DB Production trực tiếp trừ khẩn cấp có phê duyệt | | CI/CD → AWS | Ưu tiên **OIDC federation** (GitHub Actions/GitLab CI ↔ AWS IAM role tạm thời) thay vì access key/secret key dài hạn nhúng trong pipeline — giảm rủi ro lộ credential vĩnh viễn | | Secret trong pipeline | Toàn bộ secret (DB credentials, API key VNPay/Momo/GHN/GHTK, OAuth client secret) lấy từ **AWS Secrets Manager** tại thời điểm chạy, không lưu trong biến môi trường CI dạng plaintext lâu dài, không commit vào repository (đã chốt mục 8.2.2) | | Phê duyệt deploy Production | Bắt buộc bước phê duyệt thủ công (manual gate) trong pipeline trước khi deploy Production, tách biệt người phê duyệt và người thực hiện deploy (tách vai trò — segregation of duties) | | Rollout rủi ro cao | Thay đổi luồng thanh toán/commission bắt buộc dùng canary/feature flag rollout theo phần trăm người dùng tăng dần (đã chốt mục 3.3), không deploy 100% ngay lập tức | | Audit CI/CD | Log lại ai trigger deploy, phiên bản nào, thời điểm nào — phục vụ điều tra sự cố; không bắt buộc ghi vào bảng `audit_log` nghiệp vụ (mục 5.2.11) vì đây là audit trail hạ tầng/vận hành, khác phạm vi audit nghiệp vụ | ## 9.4 Giám sát & Nhật ký (Monitoring & Logging) ### 9.4.1 Metrics theo NFR | NFR | Metric | Ngưỡng cảnh báo (alert threshold) | Kênh cảnh báo | |---|---|---|---| | NFR-01 | p95 latency `GET /v1/catalog/search`, `POST /v1/checkout` | Cảnh báo khi p95 > 2s (catalog/search) hoặc > 3s (checkout) liên tục 5 phút | PagerDuty/OpsGenie → on-call giờ hành chính, escalation nếu ảnh hưởng giao dịch (mục 3.3/NFR-08) | | NFR-02 | Concurrent connections, Redis cache hit ratio, Kafka/MSK consumer lag theo topic (`OrderPlaced`, `PaymentConfirmed`...) | Cảnh báo khi cache hit ratio < 80% mùa cao điểm, hoặc consumer lag > ngưỡng xử lý trong 2 phút (VD > 1000 message chưa xử lý) | Cảnh báo đội vận hành domain tương ứng (Catalog/Search, Cart & Order) | | NFR-03 | Uptime/health check theo service (đặc biệt Cart & Order, Payment, Identity — service giao dịch cốt lõi mục 3.1) | Cảnh báo ngay khi health check fail liên tục > 1 phút cho service cốt lõi; escalation 24/7 nếu ảnh hưởng checkout/thanh toán (NFR-08) | Escalation 24/7 cho sự cố nghiêm trọng, giờ hành chính cho sự cố thường | | NFR-04 | Số lần đăng nhập sai/khoá tài khoản bất thường (`failed_login_count` tăng đột biến theo IP/khoảng thời gian), số request bị WAF chặn | Cảnh báo khi phát hiện pattern brute-force/credential stuffing (nhiều tài khoản bị khoá cùng lúc từ cùng dải IP) | Security alert riêng, không lẫn với alert vận hành thông thường | | NFR-05 | Tỷ lệ ghi `audit_log` thành công cho hành động nhạy cảm (KYC review, commission update, dispute resolve, payout retry, khoá/mở seller — mục 5.2.11) | Cảnh báo nếu phát hiện hành động nhạy cảm không có bản ghi `audit_log` tương ứng (event bị mất/consumer lỗi) | Cảnh báo đội vận hành Audit & Compliance Service | | NFR-08 | SLA phản hồi on-call (thời gian từ alert đến acknowledge) | Cảnh báo leo thang (escalate) nếu on-call không acknowledge trong 15 phút cho sự cố nghiêm trọng ảnh hưởng giao dịch | PagerDuty/OpsGenie escalation chain | ### 9.4.2 Log tập trung & masking PII - **Log tập trung:** toàn bộ service ghi log qua CloudWatch Logs (hoặc ELK/OpenSearch dùng chung cluster đã có ở mục 3.2 cho search subsystem, cân nhắc tách index riêng cho log vận hành để không ảnh hưởng hiệu năng search nghiệp vụ); tracing phân tán (distributed tracing, VD AWS X-Ray/OpenTelemetry) cho các luồng xuyên nhiều service (checkout, payout) để debug latency. - **Masking PII trong log** (khớp mục 8.2.3): bắt buộc log-scrubber middleware ở tầng ứng dụng trước khi ghi log Production — mask `email`, `phone`, `account_number`, không log `password`/`secret_encrypted`/`gateway_transaction_ref` đầy đủ. Áp dụng đồng nhất cho mọi service, kiểm tra lại bằng security test định kỳ (9.1.5). - **Retention log vận hành:** khớp mục 5.3.6 — `notification_log`/`shipment_event` 90 ngày trước khi archive lạnh; log APM/tracing đề xuất giữ 30-90 ngày (không quy định trong brief, đây là **giả định** — xem `assumptions`). - **`audit_log` (mục 5.2.11/§8.2.5):** không thuộc phạm vi log kỹ thuật ở đây — là dữ liệu nghiệp vụ có retention/kiểm soát truy cập riêng (5 năm/10 năm theo `resource_type`, chỉ scope `admin:audit:read`). ## 9.5 Kế hoạch rollback & khôi phục thảm hoạ (Rollback & DR) ### 9.5.1 Điều kiện rollback | Điều kiện | Hành động | |---|---| | Tỷ lệ lỗi 5xx tăng vượt ngưỡng (VD > 1% request) ngay sau khi rollout canary tính năng mới | Tự động dừng rollout, revert về phiên bản trước đó qua feature flag (không cần rollback toàn bộ deploy nếu tính năng được cô lập bằng flag — mục 3.3) | | Phát hiện lỗi nghiêm trọng ảnh hưởng thanh toán/commission sau khi deploy Production | Rollback thủ công ngay lập tức (revert image về version trước), thông báo escalation 24/7 (NFR-08); **không** rollback dữ liệu tài chính đã ghi nhận — xử lý bằng nghiệp vụ điều chỉnh (adjustment) nếu cần, không xoá/sửa trực tiếp bản ghi `payment`/`payout` đã hoàn tất | | Job payout hàng tuần thất bại hàng loạt (VD lỗi kết nối ngân hàng) | Không rollback dữ liệu — giữ nguyên `Payout.status=failed`, cảnh báo Admin, retry thủ công qua endpoint đã có (mục 6.1.4), **không** tự động replay để tránh double-payout (đã chốt ở mục 3.4/6.1.4) | | Migration schema DB gây lỗi ở Staging/Production | Áp dụng chiến lược migration tương thích ngược (backward-compatible, VD expand-contract pattern) cho mọi thay đổi schema service tài chính/PII; rollback code trước, rollback schema sau nếu bắt buộc (tránh mất dữ liệu mới ghi trong lúc rollback) | ### 9.5.2 RTO/RPO (kế thừa từ mục 5.3.2, không thiết kế lại) | Nhóm service | RPO | RTO | Ghi chú | |---|---|---|---| | Payment, Cart & Order, Identity & Access (giao dịch cốt lõi) | ≤ 15 phút | ≤ 1 giờ | Ưu tiên phục hồi đầu tiên — ảnh hưởng trực tiếp doanh thu/uptime NFR-03 | | Commission & Payout, Seller Management (tài chính nhạy cảm) | ≤ 1 giờ | ≤ 4 giờ | Payout không realtime nhưng cần audit trail đầy đủ khi khôi phục | | Catalog & Inventory, Promotion & Loyalty, Shipping & Fulfillment | ≤ 1 giờ | ≤ 8 giờ | | | Review, Notification, Audit & Compliance | ≤ 24 giờ | ≤ 24 giờ | Không ảnh hưởng giao dịch trực tiếp | > Các con số RTO/RPO trên là **giả định** đã chốt ở mục 5.3.2 (brief không có SLA hợp đồng cụ thể) — mục 9 kế thừa nguyên trạng, không thay đổi. ### 9.5.3 Quy trình khôi phục (tham chiếu mục 5 — Backup & Recovery) 1. **Xác định phạm vi sự cố:** service nào bị ảnh hưởng, dữ liệu mất từ thời điểm nào (dựa trên PITR — Point-in-Time Recovery đã bật cho toàn bộ RDS theo mục 5.3.2). 2. **Khôi phục RDS:** dùng Automated Backup + PITR (retention 35 ngày cho service tài chính/PII cốt lõi, 14 ngày cho service ít quan trọng — mục 5.3.2) để restore về thời điểm trước sự cố; với sự cố quy mô lớn (mất cả region), dùng snapshot cross-region đã cấu hình cho Payment/Commission & Payout/Seller Management. 3. **Khôi phục S3 (KYC, ảnh sản phẩm):** dùng versioning + cross-region replication đã bật cho bucket KYC (mục 5.3.2) để khôi phục object bị xoá/ghi đè ngoài ý muốn. 4. **Đồng bộ lại dữ liệu phái sinh:** sau khi RDS của Catalog & Inventory được khôi phục, replay lại event `ProductUpdated`/`ProductCreated` (nếu còn lưu trong Kafka/MSK retention window) để đồng bộ lại chỉ mục OpenSearch; nếu event đã hết retention, chạy job re-index toàn bộ từ RDS. 5. **Xác minh tính toàn vẹn tài chính:** với Payment/Commission & Payout, đối chiếu (reconciliation) dữ liệu khôi phục với `payment_reconciliation_log`/log đối soát VNPay/Momo trước khi mở lại giao dịch cho service đó — **không** mở lại luồng thanh toán cho tới khi xác minh xong (ưu tiên đúng đắn dữ liệu tài chính hơn tốc độ khôi phục). 6. **Thông báo & escalation:** theo NFR-08 — escalation 24/7 cho sự cố nghiêm trọng, cập nhật trạng thái cho stakeholder (Product Owner, Admin) theo chu kỳ đã thống nhất trong runbook vận hành (runbook chi tiết theo từng service là tài liệu vận hành riêng, ngoài phạm vi SAD). ## 9.6 Assumptions - Đội phát triển dùng stack ngôn ngữ phổ biến cho microservices (Node.js/Java/Go...) chưa được chốt cụ thể ở mục 3 — công cụ unit test (9.1.1) là ví dụ minh hoạ, cần điều chỉnh theo stack thực tế khi chọn. - Ngưỡng coverage unit test (70%/50%) là đề xuất của mục 9, không có trong brief/mục 2 — cần đội kỹ thuật xác nhận khi thiết lập pipeline thực tế. - Ngưỡng cảnh báo cache hit ratio, consumer lag, tỷ lệ lỗi 5xx cho rollback (9.4, 9.5.1) là giá trị đề xuất dựa trên thông lệ vận hành hệ thống quy mô lớn, chưa được xác nhận bởi SLA/KPI cụ thể của chủ dự án. - Retention log APM/tracing (30-90 ngày) là giả định của mục 9, không có trong brief. - Công cụ SAST/SCA/APM cụ thể (SonarQube/Semgrep, Snyk/Trivy, X-Ray/OpenTelemetry, PagerDuty/OpsGenie) là đề xuất minh hoạ theo best practice AWS — không phải ràng buộc bắt buộc từ brief (brief không chỉ định công cụ cụ thể). ## 9.7 Open Questions - FR-14 (loyalty): điểm thưởng tính trên `Order` cha hay từng `OrderSeller`, có gồm phí vận chuyển/thuế hay không (đã nêu ở mục 6.8) — ảnh hưởng trực tiếp kỳ vọng kết quả của TC-17; ngưỡng chi tiêu VND cho từng hạng Bạc/Vàng/Kim Cương chưa có số liệu (ảnh hưởng test hạng thành viên chưa được viết ở mục 9.2 vì thiếu acceptance criteria). - FR-25/BR-14 (dispute): công thức/mức hoàn tiền (toàn phần hay theo tỷ lệ), ai chịu phí vận chuyển hoàn trả — TC-28 chỉ kiểm thử được luồng trạng thái (refund/reject), chưa kiểm thử được số tiền hoàn cụ thể vì chưa có công thức. - FR-22/BR-04: số ngày hold payout mặc định (5 ngày) và giới hạn cấu hình theo category (có cho phép Admin đặt ngoài khoảng 3-7 ngày hay không) cần chủ dự án xác nhận trước khi chốt bộ test case performance/payout đầy đủ; ngân hàng đối tác và chuẩn kết nối batch file (SFTP+PGP hay API HTTPS) chưa chốt — ảnh hưởng khả năng viết integration test thực tế cho luồng payout (TC-25b hiện chỉ kiểm thử được nhánh "thất bại + retry thủ công", chưa kiểm thử được kết nối ngân hàng thật). - Mục 4 (API design) đã "hết vòng sửa" theo ghi chú mục 8 — các finding F11 (endpoint pre-signed URL KYC), F12 (mã lỗi `423 ERR_ACCOUNT_LOCKED`), F13 (endpoint đọc `audit_log`) chưa có endpoint chính thức ở mục 4; test case liên quan (phần trong 9.1.5) chỉ mô tả **kỳ vọng khi được bổ sung**, chưa thể viết test case thực thi được cho tới khi mục 4 cập nhật. - SLA phản hồi của Seller trước khi hệ thống tự động mở `Dispute` từ một `ReturnRequest` bị từ chối/không phản hồi chưa có số ngày cụ thể (mục 6.8) — chưa thể viết test case timeout cho luồng leo thang tự động. - Ngân sách/công cụ monitoring cụ thể (PagerDuty/OpsGenie hay giải pháp nội bộ) chưa được xác nhận — ảnh hưởng chi tiết runbook escalation thực tế. ## 9.8 Findings | targetSection | issue | severity | suggestion | |---|---|---|---| | 02-phan-tich-yeu-cau (FR-14) | FR-14 không có acceptance criteria đủ chi tiết để viết test case xác nhận số điểm/ngưỡng hạng thành viên chính xác (chỉ kiểm thử được công thức giả định của BR-06/BR-07) | medium | Bổ sung acceptance criteria cụ thể (cách tính trên Order hay OrderSeller, ngưỡng VND từng hạng) ở mục 2 sau khi chủ dự án xác nhận | | 06-luong-xu-ly (BR-14) | Không có công thức hoàn tiền dispute cụ thể → test case TC-28 chỉ xác minh được chuyển trạng thái, không xác minh được số tiền hoàn đúng/sai | medium | Bổ sung công thức hoàn tiền (toàn phần/theo tỷ lệ) ở mục 6 sau khi có quyết định nghiệp vụ | | 04-api-design | 3 finding bảo mật (F11, F12, F13 — mục 8) chưa có endpoint tương ứng vì mục 4 đã ở trạng thái approved/hết vòng sửa | low | Đã gom vào Document Control §0.4b của bản ráp SAD này theo quyết định người duyệt — cần một vòng cập nhật mục 4 (bổ sung endpoint pre-signed URL KYC, mã lỗi 423, endpoint đọc audit_log) trước khi có thể viết security test case thực thi được | | 08-bao-mat (F10) | ACL/mã hoá theo topic cho Kafka/MSK chưa được cập nhật ở mục 3 (kiến trúc) — chưa thể viết test case xác minh cụ thể ACL nào áp dụng cho topic nào | low | Đã gom vào Document Control §0.4a — cần mục 3 bổ sung chi tiết ACL trước khi security test (9.1.5) có thể specify chính xác kịch bản kiểm tra | | 09 (mục này) | NFR-01/NFR-02/NFR-03 dùng số liệu "giả định mặc định đã chốt" từ brief (chưa có SLA hợp đồng thực tế) — ngưỡng performance test có thể cần điều chỉnh sau go-live | low | Rà soát lại ngưỡng performance/alert sau khi có dữ liệu tải thực tế 1-3 tháng đầu vận hành |