SEMANTIC BILLING
Dunning Brain
Five independent code paths across two services decide dunning with no synchronization. Drift remediation (applystripe) is the closest thing to a brain today. Brain Expansion wires this pattern to webhook events, creating a single decision point.
per-type treatment specializes the brain's decision function
brain reads Stripe invoice status — accuracy is a prerequisite
applystripe/plan.go is the brain's existing foundation
| Path | Name | Trigger | What it does | Problem |
|---|---|---|---|---|
| A | Webhook recording | billing-webhooks forwards Stripe events | Writes to subs_dunning table | Nobody reads subs_dunning — orphaned audit log |
| B | Drift remediation | Admin / cron trigger | Gather-compute-apply from Stripe invoices → flag/unflag bad debt, ban/unban | Not wired to webhook events |
| C | Subscription cancellation | billing-webhooks calls subs-api | Cancels PayGo subscriptions | Only PayGo; no coordination with Path B |
| D | Pay bad debt | Customer-facing endpoint | Creates consolidated invoice, processes payment | Does NOT unflag account or reactivate subscriptions |
| E | Admin flag/unflag | Admin endpoint | Directly mutates subs_account, subs_customer, Stripe metadata | Manual escape hatch, no automation |
flagAsBadDebt handler cancels, flags, bans, updates Stripe. subs-api is a passive executor.
Written by Path A, read by nobody. Drift remediation reads Stripe invoices directly.
| Source | Read? | Notes |
|---|---|---|
| Stripe invoices | Yes | Uncollectible status + metadata (bad_debt_amount, bad_debt_paid) |
| subs_account | Yes | Account type, flags (bit 15 = AcctFlagBadDebt) |
| subs_customer | Yes | bad_debt timestamp |
| subs_product_probation | Yes | Whether customer failed payment probation |
| subs_dunning | No | Never read — orphaned audit log |
| subs_mandate | No | Not referenced in drift remediation |
| subs_stuck_events | No | Not referenced in drift remediation |
| Category | Types | Treatment |
|---|---|---|
| Supported (12) | pay_go, startup, ad_hoc_contract, election, employee, galileo, ibm, intern, jdc, pangea, partners_basic, partners_ent | Full drift remediation: flag/unflag, ban/unban, Stripe metadata, sub item deletion |
| Unsupported (4) | cloudflare_ent, msp, partners_pay_go, sfcc | Entire drift plan skipped — RestrictionUnsupportedAccountType |
- 625
- inconsistent debt flags
- accounts where racing paths produced contradictory state
- 4,825
- subs not reactivated
- after customer paid bad debt — Path D doesn't unflag
- 300+
- stuck dunning events
- no retry/dead-letter mechanism in webhook path
Cancel + flag + ban + update Stripe without wrapping. Partial failures leave inconsistent state.
Payment succeeds but customer still gets flagged — handler doesn't check current invoice status.
Stripe voids immediately after marking uncollectible. Flag + ban + cancel fires before void arrives.
Five paths → one function. Read all state, compute correct, apply delta.
applystripe already does gather-compute-apply. Brain Expansion wires it to webhook events.
Same event twice → same result. Reads current state, applies only the diff.
Replace binary gates with graduated consequences. Requires business signoff.
Moving decisions to the brain is not a port of billing-webhooks code. The old code reads partial state and makes isolated decisions across scattered handlers. The brain reads everything and makes one coordinated decision.
No dependencies. Tests must prove parity with billing-webhooks.
Replaces the gap where Path D doesn't unflag. Depends on Phase 1.
Replaces binary gates with graduated consequences. Depends on Phase 2 + segment discovery.
| Ticket | Impact | Root cause |
|---|---|---|
| BILLACCT-288 | 4,825 subscriptions not reactivated after payment | Path D does NOT unflag. Unflagging depends on drift running later. |
| BILLACCT-756 | Multiple payments for same bad debt invoice | No dedup between Path D and Path B. No atomic check-and-update. |
| BILLACCT-1076 | Invoices uncollectible then voided within seconds | Flag fires before void arrives. Customer flagged for voided invoice. |
| BILLSUB-229 | 300+ stuck dunning events | No retry/dead-letter in webhook dunning path. |
| BILLACCT-1025 | Failed payment invoices not voided | Unvoided invoices inflate bad debt calculations in drift. |
Both old and new code run during dual-write. Brain must handle every scenario before deletion.
Per-type rules don't exist yet. Finance and product must define them.
Five paths, zero coordination, increasing customer harm. The brain consolidates decisions into one function that drift remediation already partially implements. Ship it before deleting the old code.