5.5 KiB
5.5 KiB
refunds — cancellation, reversal, clawbacks, invoices
Client seam
client/src/services/refunds/·USE_REFUNDS_MOCK = true(mock is primary) · 8 server ops Last verified: 2026-07-30 against commitd3ec723and swagger.v1.json (2026-07-29).
Money going back. The customer half is thin and real; the admin half is largely deferred, which is why the seam is mocked.
Endpoints
| Method | Path | Auth | Verdict |
|---|---|---|---|
| GET | /api/v1/refunds/by_booking/{bookingId} |
[Authorize] |
wired — the customer's refund for a booking |
| GET | /api/v1/refunds/{id}/status |
[Authorize] |
wired |
| GET | /api/v1/admin_refunds |
admin · sensitive |
wired · paginated |
| POST | /api/v1/admin_refunds |
admin · sensitive |
unwired — creates and executes in one call; the client wants preview → approve (REQ-035) |
| POST | /api/v1/admin_refunds/{id}/confirm_settlement |
admin · sensitive |
unwired — the manual-channel settlement confirm |
| POST | /api/v1/admin_refunds/{id}/mark_failed |
admin · sensitive |
unwired |
| POST | /api/v1/admin_clawbacks/{id}/write_off |
admin · sensitive |
unwired — no console screen |
| POST | /api/v1/admin_invoices |
admin · sensitive |
unwired — owner-issue an invoice (REQ-018's fallback path) |
Also wired by this domain, documented with their owner (bookings.md):
POST bookings/{id}/cancel (cancel and refund, REQ-019) and
GET bookings/{id}/cancellation_policy (the pre-cancel preview, REQ-020).
Phantom — 4
| Client call | REQ | Note |
|---|---|---|
POST /api/v1/admin_refunds/preview |
REQ-035 | Deferred. Today's single POST admin_refunds creates+executes with no preview step |
POST /api/v1/admin_refunds/{id}/approve |
REQ-035 | Deferred |
POST /api/v1/admin_refunds/{id}/reject |
REQ-035 | Deferred |
GET /api/v1/refunds/my |
REQ-048 | The customer's "all my refunds" list. The wallet «استردادها» tab renders empty on the real path |
The money rules
Four, and none of them are the client's to compute:
- A refund is a reversal leg, not a deletion.
ledger_entriesis append-only; every posting group balances. Nothing is ever edited or removed. - Fee-leg decomposition is served, not derived.
RefundStatusDtocarriesplatformFeeRefundedIrrandnursePayoutRefundedIrrseparately (REQ-021, delivered) — a partial refund does not necessarily refund the commission and the payout in the same proportion. Never split a total. - Pre-payout and post-payout fork. If the nurse has not been paid, the payout leg is simply reduced. If they have, the platform raises a clawback, which the payout engine nets against the nurse's next batch — whole-clawback greedy netting, never a partial. See payouts.md.
- VAT is on commission only, so a refund's VAT leg follows the commission leg, never the payout.
Shape rules the JSON does not express
refundChanneldecides the mechanics and is not a display detail:psp_cardreverses through the acquirer,bnpl_revertcalls the BNPL provider's revert (see bnpl.md),manualis a bank transfer an admin confirms withconfirm_settlement. Each has a different ETA, andexpectedCustomerRefundEtais server-computed per channel — the client displays it verbatim.CancellationPolicyPreviewDtois the complete pre-cancel answer (REQ-020, delivered):cancellable,cancellationPolicyCode,refundPercentageApplied,feePercentage,refundAmountIrr,feeAmountIrr,refundableAmountIrr, the two fee legs,appliesTo,leadTimeLabel,refundChannel,expectedCustomerRefundEta, and a per-session breakdown. The client shows it and asks for confirmation; it computes none of it.- Cancellation tiers are config rows, not code —
cancellation_tier1/2/3_refund_ratein admin.md — and the applied rate is snapshotted onto the refund at compute time, so a later tier change is not retroactive. refundPercentageonCancellationPolicyDtois a rate,refundPercentageAppliedon the refund is the snapshot. They can legitimately differ; that is the point.- The crash-window fix in refinement-phase-6 wired the previously unreachable BNPL and manual settlement paths — a refund created against those channels now actually clears.
Enums
| Vocabulary | Values |
|---|---|
RefundStatus |
requested approved processing succeeded failed rejected |
RefundChannel |
psp_card bnpl_revert manual |
ClawbackStatus |
pending recovered written_off |
CancellationPolicyCode |
free_24h partial_under_24h customer_no_show |
CancellationLeadTime |
gt_24h lt_24h started |
CancellationScope |
whole_booking remaining_sessions |
CancelReasonCategory (client) |
changed_mind schedule_conflict found_other_care other |
The first three are verified identical to Entities/Refunds/RefundStatus.cs and ClawbackStatus.cs.
Open REQs
| REQ | Status | Effect |
|---|---|---|
| REQ-035 | deferred | No admin preview / approve / reject → 3 phantom routes. The one live route creates and executes together, which the console will not call |
| REQ-048 | open | No customer "all my refunds" list → 1 phantom. The wallet tab renders empty on the real path |
| REQ-033 | partially delivered | totalIrr is on InvoiceDto; the partner per-booking commission invoice list is deferred. See partner-center.md |