# Handoff — after backend-phase-11 (Refunds, clawbacks & invoices)

**The reversal leg of the payments arc is live.** Money can now flow *backwards*: an admin reverses a captured
booking payment across both fee legs, the balanced reversal posts to the append-only ledger, and the flow forks
on whether the nurse was already paid (clean `nurse_payable` reversal vs a first-class `nurse_clawbacks`
receivable). The minimal commission **invoice** (VAT on the commission line, sequential number, mocked مودیان)
ships too.

## What the frontend (f10-b11) can now build
- **Admin refund console** — create a refund (`POST admin_refunds`: full/partial by percentage or explicit legs),
  the refund worklist (`GET admin_refunds` — channel, decomposed legs, status, ETA, policy snapshot), and the
  clawback write-off (`POST admin_clawbacks/{id}/write_off`).
- **Admin invoice issuance** — `POST admin_invoices` (sequential number, VAT on commission, مودیان pending).
- **Customer cancellation/refund-status view** — `GET refunds/{id}/status` (tenancy-scoped; status, channel,
  amount, **`expected_customer_refund_eta`** for the BNPL 7–10-business-day window, masked reference). This is the
  **only** customer-visible refund surface — refunds are admin-initiated, there is no self-service initiation.
- **Customer/admin invoice view** — `GET invoices/{booking_id}` (number, gross, commission, VAT, مودیان status,
  PDF URL when present).

## Live endpoints / contract
- Contract: **`dev/contracts/domains/refunds-invoices.md`** (enums, DTO shapes, IRR digit-strings, masked
  references, failure codes, side-effects). Machine schema: `dev/contracts/openapi/swagger.v1.json` **refreshed**.
- All money is IRR integer, on the wire as a **digit-string**. `expected_customer_refund_eta` is a **date**.
- Admin endpoints are behind the admin policy and **rate-limited** (sensitive). Refunds are **never** customer
  self-service.

## What is mocked / waiting
- **سامانه مودیان** is mocked behind **`IMoadianClient`** (pending/no-ref by default; a config switch forces a
  registered 22-digit ref). See `reports/mocks-registry.md`.
- **BNPL revert** runs against **`IBnplProvider`** — a **thin local stub** now; **b12 owns the real seam** and its
  adapter. The `bnpl_revert` ledger legs are already identical to the card path.
- **"Was the nurse paid?"** is derived from the booking's dispute-window close (`INursePayoutStatus`) until **b13**
  ships `nurse_payouts`; **clawback recovery/netting is b13** (this phase only opens the `pending` receivable +
  supports write-off).
- **`tickets`** arrives in **b15**: `refunds.ticket_id` is a nullable column (no FK yet); the "ticket required"
  rule is config-gated off (`refund_ticket_required = false`).
- **Deferred crons** (thin/manual today): the مودیان reconciliation job (`pending → registered`) and the
  BNPL-revert reconciliation job that clears `refund_payable ↔ escrow_held` for a `processing` refund.

## Notes for the next backend phases
- **b12 (BNPL):** owns the real `IBnplProvider`; the refund `bnpl_revert` path already calls it through the seam.
- **b13 (payouts):** implement the real `INursePayoutStatus` (`nurse_payout_booking_links`) and net `pending`
  `nurse_clawbacks` out of a payout batch (set `recovered_in_payout_id` / `original_payout_id`, post `DEBIT
  nurse_payable / CREDIT nurse_clawback_receivable`).
- **b15 (tickets/partner centers):** wire the real `refunds.ticket_id` FK + flip `refund_ticket_required` on;
  wire `invoices.partner_center_id` + `nurse_clawbacks` payout FKs.
