FE: module checkout — chốt đơn từ giỏ (ADR-0073) #
Phần chung (khởi tạo client, lỗi, danh sách, ngôn ngữ) ở tài liệu FE của Core:
docs/fe/common.md (bản đọc: xforge-documentation, ADR-0102 của xforge-core).
Sửa file này CÙNG PR khi đổi hành vi FE thấy được của module.
Chỉ có ở storefront (StorefrontClient), cần khách ĐÃ đăng nhập
(customer_auth_required khi chưa). Giỏ vãng lai phải gộp vào tài khoản trước
(mergeMyCart, trang module cart). Không có màn hình CMS: đơn tạo ra xem / xử
lý ở module order.
Luồng #
giỏ (cart) ──previewMyCheckout()──► màn xác nhận (phí giao, tổng tiền)
──confirmMyCheckout({ body: { shippingAddress, name?, phone?, note?, paymentMethod? } })
└► { order, paymentCode }
├─ paymentCode = null → đơn 0 đồng, xong
├─ order.paymentMethod = "cod" → báo "trả khi nhận hàng", xong
└─ trả tiền: getStorefrontPaymentIntent({
params: { id: order.paymentIntentId },
query: { code: paymentCode } }) (module payments)
→ hỏi lại getMyOrder({ params: { id: order.id } })
tới khi paymentStatus = "paid" (đơn tự sang confirmed)
- Xem trước —
previewMyCheckout()trả CÙNG dạng với giỏ (CartSchema), đã cộng phí giao của store. Không giữ hàng / mã. Dòng ngừng bán, mã không nhận được tách riêng (unavailable,rejectedCodes) như ở giỏ — hiện để khách sửa trước khi chốt. - Chốt —
confirmMyCheckoutlàm trong MỘT transaction: báo giá lại, giữ kho (kho mặc định của store), tạo đơn (status: placed,paymentStatus: unpaid), tạo ý định thanh toán, đóng giỏ. Trả 201{ order, paymentCode }—orderlàMyOrderSchemacủa moduleorder. - Trả tiền — mở trang trả tiền của module
paymentsbằngorder.paymentIntentId+paymentCode(QR chuyển khoản / MoMo). Tiền về → đơn tựconfirmed. Ý định hết hạn (mặc định 60 phút) mà chưa trả → đơn tự huỷ, nhả kho, nếu store bậtorder.auto_cancel(mặc định bật). Đơn COD không có bước này và không tự huỷ: chờ nhân viên xác nhận.
Body của confirmMyCheckout #
| Field | Bắt buộc | Ghi chú |
|---|---|---|
shippingAddress | có | { name, line1, country, phone?, line2?, ward?, district?, province?, postalCode? }; country mã ISO 3166-1 alpha-2 ("VN") |
name | không | tên người nhận trên đơn; vắng = tên tài khoản khách |
phone | không | điện thoại liên hệ; vắng = shippingAddress.phone |
note | không | ghi chú của khách (≤ 2000 ký tự) |
paymentMethod | không | prepaid (mặc định) hoặc cod, chỉ khi store bật COD (xem dưới) |
Email trên đơn luôn là email của tài khoản khách (không nhận từ body).
Phí giao #
Cấu hình CÔNG KHAI của store checkout.shipping (getStorefrontConfigs()):
{ flatFee, freeFrom } — phí cố định mỗi đơn; tiền hàng (sau giảm, gồm thuế) từ
freeFrom trở lên thì miễn phí (freeFrom: null = không miễn). Dùng để hiện
"mua thêm X để được miễn phí giao"; số thật vẫn lấy từ previewMyCheckout. Nhân
viên đặt cấu hình này ở CMS (setConfig, core.manage_settings).
Thanh toán khi giao (COD) #
Cấu hình CÔNG KHAI của store checkout.payment_methods
(getStorefrontConfigs()): { cod, codMaxTotal }. Chỉ hiện lựa chọn "Thanh
toán khi nhận hàng" khi cod: true, và ẩn hoặc khoá nó khi tổng đơn vượt
codMaxTotal (null = không trần). Đơn 0 đồng luôn là prepaid (đã trả xong),
không cần lựa chọn. Mặc định COD tắt. Nhân viên bật ở CMS (setConfig,
core.manage_settings).
Lỗi cần xử lý #
code | Status | FE nên |
|---|---|---|
checkout_cart_empty | 409 | giỏ trống — hoặc đã chốt rồi (bấm hai lần / tab khác): về trang đơn, không tạo đơn thứ hai |
price_variant_unavailable | 409 | một dòng không còn bán: error.at = "items.<id dòng giỏ>" → tô dòng đó, mời xoá |
stock_insufficient_available | 409 | hết hàng ở một dòng: error.at = "items.<id dòng giỏ>" → tô dòng, gợi ý giảm số lượng |
price_code_not_recognized | 400 | mã giảm giá không còn dùng được → bỏ mã khỏi giỏ (setMyCartCodes) rồi chốt lại |
checkout_cod_disabled | 409 | store không nhận COD (hoặc vừa tắt): bỏ lựa chọn COD, mời trả trước |
checkout_cod_over_limit | 409 | tổng đơn vượt trần COD details.max: mời trả trước |
order_no_warehouse | 409 | store chưa đặt kho mặc định — lỗi cấu hình của store, báo "tạm thời không đặt được hàng" |
validation_failed | 400 | lỗi theo ô của shippingAddress / name / phone (details[].path) |
error.at ở chốt đơn trỏ theo id dòng giỏ (items.<id>), không theo chỉ
số. Module mở rộng (khuyến mãi, hạn mức…) có thể chặn chốt bằng 409 mã riêng —
hiện error.message (server đã dịch).
Bấm "Đặt hàng" hai lần không tạo hai đơn: lần sau trả 409 checkout_cart_empty.
Vẫn nên khoá nút khi đang gửi.