FE: module payments — thu tiền người mua (ADR-0051) #
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.
Store nhận tiền bằng tài khoản SePay / MoMo CỦA CHÍNH STORE.
- Cấu hình (admin store,
core.manage_settings):setConfigmãpayments.sepay(enabled,webhookKeybí mật ≥ 16 ký tự,bankBin/bankAccount/bankHoldercho VietQR) vàpayments.momo(enabled,partnerCode,accessKey,secretKeybí mật,endpointsandbox / production,requestType,returnUrlcó{id}). Màn cấu hình hiện URL từgetPaymentWebhookUrls()để admin dán vào SePay (null = server chưa đặtPUBLIC_API_URL). - Nhân viên (
payments.read/payments.write/payments.reconcile):createPaymentIntent({ body: { amount, description?, referenceType?, referenceId?, expiresInMinutes? } })→code(nội dung chuyển khoản),transfer(ngân hàngqrUrlVietQR, null khi chưa cấu hình / không còn chờ),momo(có trả bằng MoMo được không). Chỉ VND. Danh sáchlistPaymentIntents(lọcstatus,reference_type,reference_id),cancelPaymentIntent,createPaymentIntentMomoCheckout(→payUrl/deeplink/qrCodeUrl). Hàng chờ đối soát:listPaymentTransactions({ query: { status: "unmatched" } })→unmatchedReason(no_code,underpaid,not_payable…) → khớp taymatchPaymentTransaction({ params: { id }, body: { intentId } })hoặcignorePaymentTransaction. IPN MoMo thất lạc:queryPaymentIntentMomo({ params: { id } }).
- Tiền vào hàng chờ đối soát: thông báo
payments.unmatched(kèm Web Push) cho người cópayments.reconcile:link.key=payments.payments,link.id= id giao dịch, tham sốamount,currency,provider; đến trễ vài giây. Topicpayments.unmatched(người cópayments.read,docs/fe/cms.md§6),data: { transactionId }: màn hàng chờ đang mở đọc lạilistPaymentTransactions. Cổng gửi lại giao dịch đã ghi thì không báo lần hai. - Người mua (storefront, không cần đăng nhập): FE gửi link chứa
id+code; trang trả tiền gọigetStorefrontPaymentIntent({ params: { id }, query: { code } })để hiện số tiền, QR chuyển khoản (nội dung =code, đừng cho sửa) và nút MoMo (createStorefrontPaymentMomoCheckout({ params: { id }, body: { code } })→ chuyển tớipayUrl). Hỏi lại (poll vài giây) tới khistatus=paid;expired/cancelled→ báo hết hạn. Trả thiếu / chuyển sai nội dung → vẫnpending, nhân viên đối soát. - Tắt
paymentsở cửa hàng (màn "Module của cửa hàng",docs/fe/cms.md§3.2 của Core): store còn ý địnhpending, hoặc ý định mới hết hạn chưa quá 24 giờ (chuyển khoản trễ) → 409payment_intents_pending(details.count) — tắt lúc này thì cổng gọi về bị từ chối, tiền khách đã trả không được ghi nhận (kể cả chuyển khoản tự do không gắn ý định). Hiệnmessage. Người cópayments.read/payments.write: gợi ý mởlistPaymentIntents({ query: { status: "pending" } })và huỷ bằngcancelPaymentIntent; thiếu quyền thì chỉ hiệnmessage(đừng dẫn tới màn sẽ 403).details.count(tối đa 1000 — hiện "1000+") gồm cả ý định mới hết hạn và có thể lệch danh sách tới ~1 phút (việc định kỳ đánh dấu hết hạn chạy mỗi 60 giây). Kiểm tra này không bảo đảm tuyệt đối (ý định tạo đúng lúc tắt vẫn lọt): hướng dẫn người dùng tắt các module tạo ý định (checkout,order…) TRƯỚC, đợi hết ý định chờ, rồi mới tắtpayments.