xforge · tài liệu FE

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)
  1. 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.
  2. Chốt — confirmMyCheckout là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 } — order là MyOrderSchema của module order.
  3. Trả tiền — mở trang trả tiền của module payments bằng order.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ật order.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 #

FieldBắt buộcGhi chú
shippingAddresscó{ name, line1, country, phone?, line2?, ward?, district?, province?, postalCode? }; country mã ISO 3166-1 alpha-2 ("VN")
namekhôngtên người nhận trên đơn; vắng = tên tài khoản khách
phonekhôngđiện thoại liên hệ; vắng = shippingAddress.phone
notekhôngghi chú của khách (≤ 2000 ký tự)
paymentMethodkhôngprepaid (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ý #

codeStatusFE nên
checkout_cart_empty409giỏ 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_unavailable409một dòng không còn bán: error.at = "items.<id dòng giỏ>" → tô dòng đó, mời xoá
stock_insufficient_available409hế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_recognized400mã giảm giá không còn dùng được → bỏ mã khỏi giỏ (setMyCartCodes) rồi chốt lại
checkout_cod_disabled409store 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_limit409tổng đơn vượt trần COD details.max: mời trả trước
order_no_warehouse409store 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_failed400lỗ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.

Nguồn: xforge-modules: modules/sales/checkout/FE.md