Files
2026-09-08 12:35:40 +07:00

304 KiB
Raw Permalink Blame History

document, version, briefVersion, status
document version briefVersion status
SAD 0.2 3 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

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)

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.<domain>/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

// 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

// 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

// 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}

// 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):

{
  "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: <date> 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)

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

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

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

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

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

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

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

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

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)

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)

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)

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)

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)

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)

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)

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)

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)

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)

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)

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)

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)

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)

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)

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)
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)

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)

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)

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)

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)

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

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