# Refinement Phase 6 — Money-path correctness completion

> **Mission:** close the one genuine money-correctness hole and the referential-integrity gaps left around the
> (verified-clean) ledger. The load-bearing invariants — balanced ledger groups, the four money DB CHECKs,
> webhook idempotency, forward-only status machines — are all verified in code. This phase fixes the holes
> *around* them.
>
> **Track:** backend (money path) · **Depends on:** nothing hard (do **before** any real BNPL/manual refund —
> i.e. before [Phase 8](refinement-phase-8-external-rails.md) money rails) · **Unlocks:** ledger⇄bank
> reconciliation
> **Before you start, read [../../phases/_shared/agent-operating-rules.md](../../phases/_shared/agent-operating-rules.md).**

## 1. Context

This phase **is** the server audit's **post-phase-2**. Full file/line evidence and fixes:
**[../server/post-phase-backend-plan.md](../server/post-phase-backend-plan.md) § post-phase-2**. Read it — it is
the spec.

The headline is one real defect: **the BNPL/manual refund settlement path is unreachable.** A card refund posts
its `refund_payable ↔ escrow_held` clearing immediately, but a BNPL-revert / manual-bank refund is left in
`processing` with the clearing "deferred to reconciliation" — and **no reconciliation path exists anywhere**:
`Refund.MarkSucceededAsync` has **zero callers**, no endpoint/webhook/job performs `processing → succeeded`.
So every BNPL/manual refund permanently overstates `escrow_held` and strands `refund_payable`; the ledger will
never reconcile with the bank. This must be fixed before real BNPL/manual refunds exist.

## 2. Required reading

- **[../server/post-phase-backend-plan.md](../server/post-phase-backend-plan.md)** § post-phase-2 (items
  2.1–2.6) — the spec, with every file/line.
- `server/CLAUDE.md` "Refunds, clawbacks & invoices" + "Payments core" — the invariants you must preserve.
- The [Phase 3](refinement-phase-3-contract-batch.md) refund REQs (019/020/021/035) — coordinate: the customer
  cancel command + admin preview/approve you may build there interact with the settlement fix here.

## 3. Scope — deliver plan items 2.1–2.6

- **6.1 (top fix) — wire the unreachable refund settlement.** Add a `ConfirmRefundSettlementCommand` (admin
  `POST admin_refunds/{id}/confirm_settlement` + a BNPL-callback branch when the provider confirms customer
  cash-back) that transitions `processing → succeeded`, stamps `settled_at`, and posts
  `LedgerPosting.RefundPayableClearing` in the same commit; plus a `mark_failed` counterpart. Tests for both
  channels.
- **6.2 — add the promised forward-dep FKs** (one additive migration + an index): `refunds.ticket_id`,
  `nurse_clawbacks.original_payout_id` / `recovered_in_payout_id`, `invoices.partner_center_id` (+ index). The
  target tables all shipped and the values are wired; only the constraints are missing. Fix the now-false config
  comments too.
- **6.3 — extend `IAuditable`** to the admin-decided money & trust entities (`Refund`, `NurseClawback`,
  `NursePayout`, `NursePayoutBatch`, `NurseVerification`) so approve/reject/process and the `is_verified` flip
  leave an audit-diff row. Confirm `[AuditRedacted]` covers `iban_snapshot` first.
- **6.4 — close the refund channel-execute-before-commit crash window** (persist a `pending` refund row before
  the external channel call, then execute and update — the two-phase intent/confirm shape the webhook handler
  already uses).
- **6.5 — test the untested admin money paths** (`WriteOffClawbackCommand` / bad-debt group; a true racing
  webhook-insert; messaging `is_internal` handler-level tests).
- **6.6 — retire the orphaned `refund_ticket_required` config key** (b15 superseded it) or repurpose it; fix the
  false description.

## 4. Mocks & seams

None new. This is domain/ledger code over existing seams; the BNPL/PSP mocks stay until
[Phase 8](refinement-phase-8-external-rails.md).

## 5. Critical rules

- **The ledger stays append-only and balanced.** Every new posting is a balanced group under one
  `transaction_group_id`; the settlement clearing must reconcile (`Σdebit = Σcredit`).
- **Idempotency preserved.** The settlement confirm and the crash-window fix must be safe to replay (the
  idempotency key + status machine are the backstop).
- **Do 6.1 before real BNPL/manual refunds** ([Phase 8](refinement-phase-8-external-rails.md)) — otherwise real
  money strands ledger state.
- Don't regress the four DB CHECKs (`CK_Bookings_AmountSplit`, `CK_NursePayouts_NetSplit`,
  `CK_Refunds_LegSplit`, `CK_BnplTransactions_SettleSplit`).

## 6. Definition of Done

- [ ] A BNPL/manual refund can reach `succeeded` (via admin confirm + BNPL callback) and posts the
      `refund_payable ↔ escrow_held` clearing — proven by a test that the ledger reconciles.
- [ ] The three forward-dep FK sets + the invoice index exist (additive migration); config comments corrected.
- [ ] The five listed entities are `IAuditable` and produce audit-diff rows on admin decisions.
- [ ] The refund crash-window is closed; the previously-untested admin money paths have tests.
- [ ] `dotnet build` zero new warnings; `dotnet test` green (incl. the new tests).

## 7. How to test

- Create a BNPL refund → it lands `processing` → admin `confirm_settlement` → status `succeeded`, `settled_at`
  set, and `GetNursePayableBalance`/escrow reconcile (no stranded `refund_payable`).
- `WriteOffClawback` posts a balanced `bad_debt` group; a replayed settlement confirm is an idempotent no-op.
- Inspect `audit_logs` after an admin refund approve / a verification `is_verified` flip → a diff row exists.

## 8. Hand off & document

- Update `server/CLAUDE.md` refunds/payments sections + the contract docs for the new admin refund route. Flip
  the mocks-registry / audit notes for the settlement seam. Save a memory note that the unreachable BNPL/manual
  refund clearing is now wired (the audit's top code fix).
