# 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).