Bỏ qua nội dung

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, providerOrderId và 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

  • 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.
  • client app, tôi muốn đồng bộ bằng providerOrderId vì đây là mã nhận được từ checkout response.
  • 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.
  • hệ thống ví, tôi muốn chỉ cộng credit sau khi SePay trả CAPTURED và amount khớp.
  • 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
    end

Acceptance Criteria

  • AC-1: API GET /payments/orders/{providerOrderId}/detail yêu cầu user đã đăng nhập và quyền payment:read:own.
  • AC-2: API có alias GET /v1/payments/orders/{providerOrderId}/detail.
  • AC-3: providerOrderId trên path là bắt buộc và không được blank.
  • AC-4: Backend chỉ tìm order provider SEPAY thuộ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 EXPIRED nếu order đã quá hạn.
  • AC-9: Nếu SePay trả order, backend phải validate order_invoice_number khớp providerOrderId local.
  • 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, CANCELED hoặc trạng thái tương đương, backend sync order local sang CANCELLED.
  • AC-14: Response là WalletTopupOrderResponse.
  • AC-15: Nếu detail sync làm order chuyển SUCCESS, backend publish PaymentOrderConfirmedEvent.
  • AC-16: PaymentOrderRealtimeListener chỉ 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-orders với payload PaymentOrderRealtimePayload.
  • AC-18: FE/Mobile cần subscribe /user/queue/payment-orders trê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.
  • CAPTURED là 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 CREATED cho đến khi hết hạn.
  • Order quá hạn phải chuyển EXPIRED nế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 event STATUS_CHANGED/SUCCESS và 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}/detail
  • GET /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 khi CONNECT.

Endpoint user đề xuất nhưng chưa có trong controller hiện tại:

  • GET /api/payments/orders/{providerOrderId}

Liên quan