Files
baya-monorepo/dev/post-phase/refinement/refinement-phase-6-money-correctness.md
T
2026-07-10 20:59:47 +03:30

5.9 KiB
Raw Blame History

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 money rails) · Unlocks: ledger⇄bank reconciliation Before you start, read ../../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 § 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 § post-phase-2 (items 2.12.6) — the spec, with every file/line.
  • server/CLAUDE.md "Refunds, clawbacks & invoices" + "Payments core" — the invariants you must preserve.
  • The Phase 3 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.12.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.

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