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-Idphả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.
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)
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)
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)
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ặcsellerId 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
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.
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.