Lấy chi tiết đơn hàng thanh toán
Feature URL
Module
payment
Status
shipped
Priority
P0
Platforms
fe · be
AC progress
17 / 18
Last reviewed
2026-05-29
Mục tiêu
Cho phép user đã đăng nhập đồng bộ trạng thái mới nhất của một top-up order từ SePay bằng providerOrderId. API dùng khi client cần kiểm tra sau checkout, sau redirect hoặc trước khi resume thanh toán, và đảm bảo ví chỉ được cộng credit khi provider xác nhận order đã CAPTURED.
Phạm vi
Trong phạm vi (In scope):
- API hiện có
GET /payments/orders/{providerOrderId}/detail. - Alias hiện có
GET /v1/payments/orders/{providerOrderId}/detail. - Xác thực user và quyền
payment:read:own. - Tìm order theo provider
SEPAY,providerOrderIdvà owner hiện tại. - Gọi SePay order detail để lấy trạng thái mới nhất.
- Validate invoice number và amount giữa local order và provider order.
- Sync status local, xử lý hết hạn và cộng V-Credits khi provider trả
CAPTURED. - Khi detail sync làm order chuyển
SUCCESS, backend publish realtime update tới owner qua WebSocket/user/queue/payment-orders.
Ngoài phạm vi (Out of scope):
- Endpoint rút gọn
GET /payments/orders/{providerOrderId}vì backend hiện chưa expose path này. - Tạo checkout top-up, xem feature
wallet-topup-create. - Huỷ top-up, xem feature
wallet-topup-cancel. - Public IPN/webhook từ SePay, xem feature
payment-sepay-ipn-webhook-realtime. - Xem danh sách tất cả payment orders.
- Hạ tầng STOMP/WebSocket nền tảng, xem system doc
WebSocket / STOMP.
User Stories
- Là user vừa thanh toán, tôi muốn app kiểm tra trạng thái order để biết ví đã được cộng credit chưa.
- Là client app, tôi muốn đồng bộ bằng
providerOrderIdvì đây là mã nhận được từ checkout response. - Là client app, tôi muốn nhận realtime event khi detail sync xác nhận order thành công để cập nhật màn chờ nhanh hơn polling.
- Là hệ thống ví, tôi muốn chỉ cộng credit sau khi SePay trả
CAPTUREDvà amount khớp. - Là ops/support, tôi muốn mỗi lần gọi detail được ghi vào history để trace luồng thanh toán.
Luồng chức năng
sequenceDiagram
actor User
participant App as FE/Mobile
participant BE
participant SePay
participant Wallet
participant WS as WebSocket
User->>App: Mở màn trạng thái thanh toán
App->>BE: GET /payments/orders/{providerOrderId}/detail
BE->>BE: Tìm PaymentOrder owner-only
BE->>BE: Append history DETAIL_API
BE->>SePay: GET /v1/order/detail/{providerOrderId}
alt SePay chưa có order
BE->>BE: Giữ local state, sync expired nếu quá hạn
BE-->>App: WalletTopupOrderResponse
else SePay trả CAPTURED
BE->>BE: Validate invoice number + amount
BE->>Wallet: Confirm payment order, cộng credit idempotent
BE-->>App: status SUCCESS
WS-->>App: MESSAGE /user/queue/payment-orders (status SUCCESS)
else SePay trả trạng thái khác
BE->>BE: Sync CANCELLED/EXPIRED nếu phù hợp
BE-->>App: WalletTopupOrderResponse
endAcceptance Criteria
- AC-1: API
GET /payments/orders/{providerOrderId}/detailyêu cầu user đã đăng nhập và quyềnpayment:read:own. - AC-2: API có alias
GET /v1/payments/orders/{providerOrderId}/detail. - AC-3:
providerOrderIdtrên path là bắt buộc và không được blank. - AC-4: Backend chỉ tìm order provider
SEPAYthuộc user hiện tại. - AC-5: Nếu không tìm thấy order thuộc user hiện tại, API trả lỗi not found.
- AC-6: Mỗi lần gọi detail phải append history target
DETAIL_API. - AC-7: Backend gọi SePay detail qua provider client và lưu request/response vào history khi có response hợp lệ.
- AC-8: Nếu SePay chưa có order, backend trả local snapshot và sync
EXPIREDnếu order đã quá hạn. - AC-9: Nếu SePay trả order, backend phải validate
order_invoice_numberkhớpproviderOrderIdlocal. - AC-10: Nếu amount provider khác amount local, API trả conflict và không cộng credit.
- AC-11: Nếu provider status là
CAPTURED, backend xác nhận payment order và cộng V-Credits đúng một lần. - AC-12: Nếu order đã
SUCCESS, gọi detail lại không được cộng credit lần hai. - AC-13: Nếu provider status là
CANCELLED,CANCELEDhoặc trạng thái tương đương, backend sync order local sangCANCELLED. - AC-14: Response là
WalletTopupOrderResponse. - AC-15: Nếu detail sync làm order chuyển
SUCCESS, backend publishPaymentOrderConfirmedEvent. - AC-16:
PaymentOrderRealtimeListenerchỉ push WebSocket sau transaction commit, để client có thể gọi REST reconcile ngay khi nhận message. - AC-17: Client nhận realtime status tại
/user/queue/payment-ordersvới payloadPaymentOrderRealtimePayload. - AC-18: FE/Mobile cần subscribe
/user/queue/payment-orderstrên màn chờ thanh toán và vẫn giữ fallback polling/detail sync khi WebSocket mất kết nối.
Quy tắc nghiệp vụ
- Detail sync là API owner-only, không được lộ trạng thái order của user khác.
- Provider detail chỉ là nguồn xác nhận sau khi đã đối chiếu invoice number và amount.
CAPTUREDlà trạng thái provider cho phép cộng V-Credits.- Cộng credit phải idempotent theo payment order/provider order.
- Detail sync dùng cùng confirm path với các callback payment; realtime WebSocket chỉ phát khi order thực sự chuyển sang
SUCCESS. - WebSocket là tín hiệu realtime, không thay thế REST detail sync; client cần reconcile bằng API nếu reconnect hoặc bỏ lỡ message.
- Nếu SePay chưa nhận biết order, local order vẫn có thể còn
CREATEDcho đến khi hết hạn. - Order quá hạn phải chuyển
EXPIREDnếu chưa ở trạng thái terminal. - Endpoint rút gọn
GET /payments/orders/{providerOrderId}cần backend alias mới trước khi trở thành contract public.
Dữ liệu & Trạng thái
Entity nghiệp vụ:
PaymentOrder: order local cần đồng bộ với SePay.PaymentProviderOrderState: snapshot provider gồm transaction id, invoice number, amount, status, request/response raw.UserWallet: ví được cộng khi order thành công.WalletTransaction: ledger chống double credit.WalletTopupOrderResponse: snapshot trả về cho client.PaymentOrderConfirmedEvent: domain event phát ra khi detail sync confirm payment order thành công.PaymentOrderRealtimePayload: message realtime gửi tới owner qua/user/queue/payment-orders.
Trạng thái user-facing:
CREATED: order local còn pending hoặc SePay chưa có trạng thái captured/cancelled.SUCCESS: SePay đã captured, ví đã cộng credit.REALTIME_NOTIFIED: client nhận WebSocket eventSTATUS_CHANGED/SUCCESSvà nên refresh order/wallet state.CANCELLED: provider/user đã huỷ hoặc provider trả trạng thái cancel.EXPIRED: order quá hạn thanh toán.FAILED: order thất bại, không cộng credit.
Endpoint BE hiện có:
GET /api/payments/orders/{providerOrderId}/detailGET /api/v1/payments/orders/{providerOrderId}/detail- WebSocket destination client subscribe:
/user/queue/payment-orders
Cấu hình realtime liên quan:
- WebSocket/STOMP: xem system doc
WebSocket / STOMP, client cần access token khiCONNECT.
Endpoint user đề xuất nhưng chưa có trong controller hiện tại:
GET /api/payments/orders/{providerOrderId}