Tạo đơn hàng nạp tiền ví V-credit (HTML)
Feature URL
Module
payment
Status
shipped
Priority
P0
Platforms
be
AC progress
12 / 12
Last reviewed
2026-05-22
Mục tiêu
Cho phép user đã đăng nhập tạo một phiên checkout để nạp V-Credits qua SePay. API trả đủ dữ liệu để client chuyển user sang cổng thanh toán, đồng thời hỗ trợ tiếp tục thanh toán một đơn pending bằng providerOrderId cũ.
Phạm vi
Trong phạm vi (In scope):
- API
POST /wallet/topupsđể tạo mới hoặc resume checkout. - Validate số tiền nạp và ngưỡng nạp tối thiểu.
- Tạo
PaymentOrderproviderSEPAY, statusCREATED, có hạn thanh toán. - Trả
WalletTopupCheckoutResponsegồm checkout URL, hidden form fields và HTML form auto-submit. - Khi request có
providerOrderId, đồng bộ trạng thái provider trước khi resume checkout. - Chỉ cho owner hiện tại resume order của chính họ.
Ngoài phạm vi (Out of scope):
- Huỷ đơn top-up, xem feature
wallet-topup-cancel. - Đồng bộ detail chủ động từ màn chờ thanh toán, xem feature
payment-order-detail-sync. - Webhook IPN SePay.
- UI ví/nạp tiền trên frontend.
- Nạp qua provider khác ngoài SePay.
User Stories
- Là user đã đăng nhập, tôi muốn chọn số tiền nạp để nhận checkout SePay và thanh toán.
- Là user bị gián đoạn checkout, tôi muốn tiếp tục thanh toán đơn cũ bằng
providerOrderIdđể không tạo đơn trùng. - Là client app, tôi muốn nhận
checkoutFormHtmlhoặcformFieldsđể chuyển user sang SePay đúng dữ liệu đã ký. - Là hệ thống kế toán ví, tôi muốn mỗi đơn top-up có
PaymentOrdertrước khi thanh toán để có thể đối soát với provider.
Luồng chức năng
sequenceDiagram
actor User
participant App as FE/Mobile
participant BE
participant SePay
User->>App: Chọn số tiền nạp
App->>BE: POST /wallet/topups
BE->>BE: Validate amount và min payment amount
alt Không có providerOrderId
BE->>BE: Tạo PaymentOrder status CREATED
else Có providerOrderId
BE->>SePay: Query order detail
BE->>BE: Kiểm tra owner, amount, status CREATED, chưa hết hạn
end
BE->>BE: Build signed checkout fields
BE-->>App: WalletTopupCheckoutResponse
App->>SePay: Submit checkout formAcceptance Criteria
- AC-1: API
POST /wallet/topupsyêu cầu user đã đăng nhập và quyềnwallet:topup. - AC-2: Body nhận
amountbắt buộc;providerOrderId,paymentMethod,successUrl,errorUrl,cancelUrllà tuỳ chọn. - AC-3:
amountphải là số nguyên dương. - AC-4:
amountphải lớn hơn hoặc bằng cấu hình runtimeapp.vcredit.min-payment-amount-vnd. - AC-5: Khi tạo mới, backend sinh
providerOrderIddạngTOPUP_<20 ký tự>. - AC-6: Khi tạo mới, backend tạo
PaymentOrdervới providerSEPAY, amount, payment method nếu có, statusCREATED,expiredAttheoapp.sepay.topup-expiry-minutes. - AC-7: Response trả
paymentOrderId,providerOrderId,amount,checkoutUrl,formFields,checkoutFormHtml,expiredAt. - AC-8:
formFieldsphải có chữ ký SePay và các field cần thiết như amount, merchant, currency, operation, invoice number, customer id. - AC-9: Khi resume bằng
providerOrderId, backend phải gọi flow detail provider trước khi trả checkout. - AC-10: Resume chỉ thành công nếu order thuộc user hiện tại, amount khớp, status là
CREATEDvà chưa hết hạn. - AC-11: Resume order
SUCCESS,CANCELLED,EXPIRED,FAILEDhoặc amount mismatch phải bị từ chối bằng lỗi conflict/business phù hợp. - AC-12: Nếu thiếu principal hoặc user không hợp lệ, API trả lỗi auth/security thay vì tạo order.
Quy tắc nghiệp vụ
- Một top-up checkout luôn gắn với đúng một user owner.
- Mỗi order dùng một
providerOrderIdđể đối soát với SePay. - Số tiền nạp VND cũng là số V-Credits sẽ được cộng khi thanh toán thành công.
- URL redirect trong request được ưu tiên hơn cấu hình default của server.
paymentMethodclient gửi lên chỉ là lựa chọn checkout, không chứng minh payment đã hoàn tất.- Checkout response chưa đồng nghĩa ví đã được cộng tiền; ví chỉ được cộng khi provider xác nhận
CAPTURED. - Backend phải escape HTML khi build
checkoutFormHtml. - Không tạo order mới khi client đang resume order hợp lệ bằng
providerOrderId.
Dữ liệu & Trạng thái
Entity nghiệp vụ:
CreateWalletTopupRequest:amount,providerOrderId,paymentMethod,successUrl,errorUrl,cancelUrl.PaymentOrder: order nội bộ ghi user, amount, provider, providerOrderId, paymentMethod, status, expiredAt, histories.WalletTopupCheckoutResponse: payload checkout trả cho client.
Trạng thái user-facing:
CREATED: order mới tạo và có thể checkout hoặc resume.EXPIRED: order quá hạn, không được resume checkout.SUCCESS: order đã thanh toán, không được resume checkout.CANCELLED: order đã huỷ, không được resume checkout.FAILED: order lỗi, không được resume checkout.
Endpoint BE hiện có:
POST /api/wallet/topups
Liên quan
- Phụ thuộc: auth-login-phone, auth-login-google
- Ảnh hưởng: wallet-topup-cancel, payment-order-detail-sync