Appearance
Type Deep Dive — Negative Payment Boundary
1. Tại sao phải tách file này?
Trong UI, negative payment thường bị nhìn như “một dạng refund”. Trong code thực tế, điều đó sai.
Negative payment:
- không tạo
wallet.transaction_request, - không đi qua action
changeStatusTransaction, - không dùng state machine
R / S / C / Reject, - không dùng page stack
WithdrawRequest*.
Nó là một flow riêng:
- tạo từ payment flow của order/prepaid,
- lưu ở
hrm.request_working_schedule, - sinh
invoice.negative = true, - duyệt qua approval flow của HRM/ecommerce.
2. FE Entry Points
2.1 Tạo request âm
Không tạo từ page refund/withdraw.
Nó được gọi từ payment flows:
OrderAddMultipleInvoiceDialog.tsxAddPrepaidPaymentForm.tsx
thông qua action createNegativePayment.
2.2 List / detail approval
| Page | Vai trò |
|---|---|
NegativePaymentRequests.tsx | List request âm |
NegativePaymentRequestDetail.tsx | Detail + approve/reject |
GraphQL chính:
negative_payment_request.graphqluseChangeApprovalStatusMutation
3. Backend Flow
text
Payment flow phát hiện cần negative payment
-> ecommerce-api action createNegativePayment
-> insert hrm.request_working_schedule (type = negative_payment)
-> insert negative invoice (negative = true, request_negative_id = ...)
-> approver xử lý request
-> ecommerce-api changeStatusRequestWorkingSchedule
-> ecommerce-api event request_working_schedule_update4. Dữ liệu cốt lõi
| Object | Vai trò |
|---|---|
hrm.request_working_schedule | Approval container chính |
ecommerce.invoice | Negative invoice, link ngược bằng request_negative_id |
wallet.transaction_request | Chỉ là nguồn đọc commission đã success, không phải request engine của flow này |
5. Vì sao flow này vẫn liên quan refund docs?
Vì negative payment có coupling thực tế với vùng refund/commission:
- cùng đụng order và invoice,
- cùng có ngữ nghĩa “điều chỉnh âm”,
- đọc commission transaction đã success của order từ wallet,
- dễ bị team hiểu nhầm là một subtype của refund.
Vì vậy feature deep dive này phải nhắc tới nó, nhưng nhắc như boundary riêng, không như một subtype.
6. Logic đáng chú ý trong createNegativePayment
Action negative_payment.go làm các việc chính:
- kiểm tra order tồn tại,
- chặn nếu order đã
paid_at, - chặn nếu đã có
request_working_schedulepending cùng order, - query commission transaction đã success của order,
- phân nhánh theo
order_kind:- cosmetic
- service
- prepaid
- tạo request âm + negative invoice.
Điểm quan trọng:
- flow này đọc dữ liệu commission từ wallet,
- nhưng approval / lifecycle lại nằm ngoài wallet.
7. Status Model
Khác refund family, negative payment dùng status của request_working_schedule:
pendingapprovedrejectedcanceledrecalledapproved_step_one
FE hiện chủ yếu xử lý pending / approved / rejected.
8. Boundary Rules
NP-001
Không được mô tả negative payment là transaction_request.
NP-002
Không được join report/debug theo transaction_request.reference_id khi đang phân tích negative payment.
NP-003
Nếu cần lần theo invoice của negative payment, phải đi qua invoice.request_negative_id.
NP-004
Nếu cần lần theo approval object, phải bắt đầu từ hrm.request_working_schedule, không phải wallet tables.
9. Rủi ro / Findings
| ID | Mức | Finding |
|---|---|---|
| NP-01 | P1 | request_working_schedule_update có dấu hiệu mark invoice paid_at/payment_verified_at trên mọi status change cho negative payment, không đợi approved. |
| NP-02 | P1 | FE list/detail cũng có dấu hiệu check sai actor approve tương tự refund flow. |
| NP-03 | P1 | Vì cùng đụng commission/order/invoice, team rất dễ gộp nhầm negative payment vào refund docs nếu không tách boundary ngay từ đầu. |