# Handoff — after backend-phase-13 (Weekly nurse payouts)

**The payout engine is live — a nurse's earnings are now real money.** This is the last money-out leg of the
payments arc (b10 ledger → b11 refunds/clawbacks → b12 BNPL → **b13 payouts**). An admin previews eligible
earnings, opens a weekly batch, and submits it to a **mocked PAYA/SATNA bank rail**; each booking is paid in
**exactly one** payout across all batches (a `nurse_payout_booking_links.booking_id` UNIQUE), pending clawbacks are
netted, the verified primary IBAN is snapshotted, and the outbound `nurse_payable → escrow_held` ledger movement is
posted. Everything is **holiday-aware** (a Nowruz-landing batch shifts off bank-closed days). No `payout_released`
boolean exists — paid-ness is derived from a link row + the ledger.

## What the frontend (f12-b13) can now build
- **Nurse earnings / payout history** — `GET nurse_payouts/history` (authenticated nurse, own payouts only,
  paginated): `status` (`pending|submitted|paid|failed`), `netAmountIrr`, `grossEarningsIrr`, `clawbackAppliedIrr`,
  the **masked** IBAN, `transferReference`, `paidAt`, and the batch window. Money is a **digit string**. Show the
  clawback line when `clawbackAppliedIrr > "0"` ("earnings held to recover a prior overpayment").
- **Admin payout console:**
  - `GET admin_payouts/eligible?periodStart=&periodEnd=` — dry-run preview per nurse; flags any nurse with
    `hasVerifiedPrimaryIban=false` (they won't be paid until they register a verified primary account).
  - `POST admin_payouts/batches {periodStart, periodEnd}` — open a `draft` batch → returns the batch + payouts +
    the `skipped` nurses (with reasons). No money moves yet.
  - `POST admin_payouts/batches/{id}/process` — submit to the rail → `completed` / `partially_failed`.
  - `POST admin_payouts/{payoutId}/retry` and `POST admin_payouts/{payoutId}/mark_failed {failureReason}`.
  - `GET admin_payouts/batches/{id}` (header + payouts + linked bookings) and `GET admin_payouts/batches?status=`.

## Contracts
- **`dev/contracts/domains/payouts.md`** — all 8 endpoints, the `PayoutBatchStatus`/`PayoutStatus` enums, and the
  batch/payout/link/history DTO shapes (IRR digit strings, **masked** IBAN). Query params are **camelCase**
  (`page`/`pageSize`/`status`/`periodStart`/`periodEnd`).
- **`dev/contracts/openapi/swagger.v1.json`** refreshed — the 7 payout paths are in the snapshot.

## What is mocked (and how it becomes real)
- **`IBankTransferProvider`** (new) — the PAYA/SATNA rail. `MockBankTransferProvider` moves no money: it returns a
  deterministic `transfer_reference` and settles every instruction `paid`, honouring the PAYA/SATNA method the
  handler picked by `payout_satna_threshold_irr`. Config forces failures for testing (`Seams:BankTransfer:ForceFailure`
  whole-batch, `Seams:BankTransfer:FailIban` one row). Make it real → a Jibit/Vandar/Sadad payout adapter with a
  registered source settlement account + the async reconciliation callback (see reports/mocks-registry.md).
- **`INursePayoutStatus`** — b13 shipped the authoritative `NursePayoutLinkStatusService` (paid iff a link ties the
  booking to a `paid` payout); the interim dispute-window derivation was deleted. **The b11 refund fork now forks on
  the true paid-state** — no refund-side change needed.

## Load-bearing rules (don't regress)
- **One payout per booking, ever** — the `booking_id` UNIQUE is unconditional (not soft-delete-filtered).
- **Eligibility ≠ completed** — needs `dispute_window_ends_at < now`, no active refund, not already linked.
- **Clawback netting recovers *whole* clawbacks up to earnings** (never a negative net, never a partial single row);
  the recovery is a real `DEBIT nurse_payable / CREDIT nurse_clawback_receivable` posting + `recovered` status.
- **Process is idempotent** — forward-only `PayoutStatus` + a batch idempotency key + the ledger-exists guard.

## Deferred (flagged, not built)
- The weekly **cron scheduler** — batches are admin-triggered; cadence in `nurse_payout_interval_days` (default 7).
- **On-demand / instant withdrawal**, **per-nurse payout frequency**, **automated clawback recovery beyond netting**.
- The **BNPL `settled_at` guard** — exposed as `require_bnpl_settlement_for_payout` (config, default off).
