Files
sys-analysis-design/docs/sections/04-api-design.md
Leonard-ThindPad-P50 c81f249920 init git
2026-09-08 10:26:21 +07:00

34 KiB
Raw Blame History

section, title, status, version, reviewer_notes
section title status version reviewer_notes
04 Thiết kế API approved 3

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)

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ý

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