Bỏ qua nội dung

Huỷ đơn hàng nạp ví theo providerOrderId

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 huỷ một đơn nạp ví còn pending bằng providerOrderId. API giúp user dừng đơn chưa thanh toán, đồng bộ trạng thái huỷ với SePay khi provider đã có order và lưu lại snapshot cuối của payment order.

Phạm vi

Trong phạm vi (In scope):

  • API POST /wallet/topups/{providerOrderId}/cancel.
  • Xác thực user và quyền wallet:topup.
  • Tìm order theo provider SEPAY, providerOrderId và owner hiện tại.
  • Từ chối huỷ order đã thanh toán thành công.
  • Gọi SePay cancel khi provider có order và order chưa settled.
  • Chuyển order local sang CANCELLED và trả WalletTopupOrderResponse.

Ngoài phạm vi (Out of scope):

  • Tạo order top-up, xem feature wallet-topup-create.
  • Hoàn tiền cho order đã SUCCESS.
  • Huỷ hàng loạt nhiều order.
  • Huỷ order của user khác hoặc bởi admin.
  • Xử lý webhook IPN sau khi user huỷ.

User Stories

  • user đã tạo top-up, tôi muốn huỷ đơn chưa thanh toán để không còn thấy đơn pending.
  • client app, tôi muốn gọi huỷ bằng providerOrderId vì đây là mã được dùng xuyên suốt checkout.
  • hệ thống, tôi muốn đảm bảo order đã thanh toán không thể bị huỷ nhầm.
  • ops/support, tôi muốn history order ghi nhận việc huỷ để có thể audit.

Luồng chức năng

sequenceDiagram
    actor User
    participant App as FE/Mobile
    participant BE
    participant SePay

    User->>App: Bấm huỷ đơn nạp
    App->>BE: POST /wallet/topups/{providerOrderId}/cancel
    BE->>BE: Xác thực user + tìm order owner-only

    alt Order SUCCESS
        BE-->>App: 409 conflict
    else Order đã CANCELLED hoặc EXPIRED
        BE-->>App: Snapshot hiện tại
    else Order còn huỷ được
        BE->>SePay: Cancel provider order nếu tồn tại
        BE->>BE: Set status CANCELLED + append history
        BE-->>App: WalletTopupOrderResponse
    end

Acceptance Criteria

  • AC-1: API POST /wallet/topups/{providerOrderId}/cancel yêu cầu user đã đăng nhập và quyền wallet:topup.
  • AC-2: providerOrderId trên path là bắt buộc và không được blank.
  • AC-3: Backend chỉ tìm order provider SEPAY thuộc user hiện tại.
  • AC-4: Nếu không tìm thấy order thuộc user hiện tại, API trả lỗi not found.
  • AC-5: Nếu order đang SUCCESS, API trả conflict và không đổi trạng thái.
  • AC-6: Nếu order đã CANCELLED hoặc EXPIRED, API trả snapshot hiện tại, không gọi lại luồng huỷ gây side effect mới.
  • AC-7: Nếu order còn huỷ được, backend gọi provider cancel khi provider order tồn tại.
  • AC-8: Nếu provider order chưa tồn tại trên SePay, backend vẫn có thể huỷ local với metadata ghi rõ local cancel only.
  • AC-9: Sau khi huỷ thành công, order local có status CANCELLED.
  • AC-10: Response là WalletTopupOrderResponse gồm id, provider, providerOrderId, amount, status, histories, expiredAt, createdAt.
  • AC-11: Mọi lần huỷ hợp lệ phải append history mô tả trạng thái CANCELLED.
  • AC-12: User không thể huỷ order của user khác dù biết providerOrderId.

Quy tắc nghiệp vụ

  • providerOrderId là định danh duy nhất client dùng để huỷ checkout SePay.
  • Chỉ owner của order được phép huỷ order của mình.
  • Order SUCCESS là trạng thái đã settled, không được huỷ qua API này.
  • Order CANCELLEDEXPIRED là trạng thái cuối, trả snapshot hiện tại để API idempotent ở mức user action.
  • Provider cancel failure do upstream phải được phân loại là lỗi integration, không được âm thầm đổi trạng thái sai.
  • Huỷ local khi provider chưa có order là hợp lệ để user không còn tiếp tục checkout order đó.
  • Huỷ top-up không tạo wallet ledger cộng/trừ tiền.

Dữ liệu & Trạng thái

Entity nghiệp vụ:

  • PaymentOrder: order cần huỷ, lookup theo provider, providerOrderId và user id.
  • PaymentProviderCancelResult: metadata kết quả huỷ ở SePay hoặc local cancel.
  • WalletTopupOrderResponse: snapshot trả về sau khi huỷ.

Trạng thái user-facing:

  • CREATED: order còn có thể huỷ.
  • CANCELLED: order đã huỷ, không cộng credit.
  • EXPIRED: order hết hạn, không cần huỷ thêm.
  • SUCCESS: order đã thanh toán, không được huỷ.
  • FAILED: order lỗi, không còn là checkout bình thường.

Endpoint BE hiện có:

  • POST /api/wallet/topups/{providerOrderId}/cancel

Liên quan