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