create mvp path
This commit is contained in:
@@ -0,0 +1,88 @@
|
||||
# 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 commit `d3ec723` and 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](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:
|
||||
|
||||
1. **A refund is a reversal leg, not a deletion.** `ledger_entries` is append-only; every posting group
|
||||
balances. Nothing is ever edited or removed.
|
||||
2. **Fee-leg decomposition is served, not derived.** `RefundStatusDto` carries
|
||||
`platformFeeRefundedIrr` and `nursePayoutRefundedIrr` separately (REQ-021, delivered) — a partial refund
|
||||
does not necessarily refund the commission and the payout in the same proportion. Never split a total.
|
||||
3. **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](payouts.md).
|
||||
4. **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
|
||||
|
||||
- **`refundChannel` decides the mechanics and is not a display detail**: `psp_card` reverses through the
|
||||
acquirer, `bnpl_revert` calls the BNPL provider's revert (see [bnpl.md](bnpl.md)), `manual` is a bank
|
||||
transfer an admin confirms with `confirm_settlement`. Each has a different ETA, and
|
||||
`expectedCustomerRefundEta` is server-computed per channel — the client displays it verbatim.
|
||||
- **`CancellationPolicyPreviewDto` is 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_rate` in
|
||||
[admin.md](admin.md) — and the applied rate is **snapshotted onto the refund** at compute time, so a
|
||||
later tier change is not retroactive.
|
||||
- **`refundPercentage` on `CancellationPolicyDto` is a rate, `refundPercentageApplied` on 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](partner-center.md) |
|
||||
Reference in New Issue
Block a user