The main spec: problem, proposal, brain expansion, credit note implementation, and the plan.

Four handlers in billing-webhooks that move to subscriptions-api. Each handler, its trigger, what it does, and the RPCs it calls back into subs-api.

HandlerTriggerWhat It DoesRPCs to subs-api
FlagAsBadDebtinvoice.marked_uncollectibleSets bad_debt flag, writes metadata, bans accountFlagAsBadDebt RPC
CheckFlagAsBadDebtinvoice.payment_succeededChecks if payment clears bad debt, lifts flagCheckFlagAsBadDebt RPC
badDebtLiftcustomer.subscription.updatedLifts bad debt on subscription reactivationbadDebtLift RPC
CalculateInvoiceBadDebtAmountCalled by FlagAsBadDebtProrates invoice to compute bad debt amountNone (local)

20 scenarios across three domains. Each scenario must pass in the brain before the corresponding handler is deleted from billing-webhooks.

FlagAsBadDebt scenarios
#ScenarioExpected
1Single invoice, PayGo, in-advance onlyFlag set, metadata written, ban applied
2Single invoice, PayGo, mixed in-advance + usageFlag set, proration on in-advance only
3Already flagged accountIdempotent — no duplicate flag
4Enterprise accountSkip — excluded type
5Partner accountSkip — excluded type
6Multiple uncollectible invoicesFlag set, amounts summed
Lifting scenarios
#ScenarioExpected
7Full payment on single invoiceFlag lifted, ban removed, metadata updated
8Partial payment (less than bad_debt_amount)Flag NOT lifted — partial insufficient
9Payment on consolidated invoiceFlag lifted (legacy path)
10Payment on credit-note-adjusted originalFlag lifted (new path)
11Payment on unrelated invoiceFlag NOT lifted — wrong invoice
12Multi-invoice, pay latest onlyFlag lifted (progressive policy)
13Subscription reactivation without paymentFlag NOT lifted — payment required
Amount calculation scenarios
#ScenarioExpected
14$200/mo, uncollectible after 5 days$200 × (26/31) = $167.74 credit → $32.26 debt
15Usage-only invoice$0 credit — usage owed in full
16Mixed: $20 in-advance + $5.47 usageCredit on $20 portion only
17Mid-month uncollectible (day 15 of 30)$200 × (15/30) = $100 credit → $100 debt
18Last-day uncollectible (day 30 of 31)$200 × (1/31) = $6.45 credit → $193.55 debt
19Multiple line items, different periodsEach prorated independently
20Zero-amount invoiceNo credit note needed

Each deletion is gated on the corresponding test suite passing in the brain. No deletion without coverage.

WhatInGated On
FlagAsBadDebt handlerbilling-webhooksTests 1–6 passing in brain
CheckFlagAsBadDebt handlerbilling-webhooksTests 7–13 passing in brain
badDebtLift handlerbilling-webhooksTests 7–13 passing in brain
CalculateInvoiceBadDebtAmountbilling-webhooksTests 14–20 passing in brain
FlagAsBadDebt RPCsubscriptions-apiDirect call replaces RPC
CheckFlagAsBadDebt RPCsubscriptions-apiDirect call replaces RPC
badDebtLift RPCsubscriptions-apiDirect call replaces RPC

Account types determine dunning treatment. The brain must handle every type explicitly — no silent fallthrough to PayGo defaults.

Supported types (full dunning treatment)
TypeTreatment
PayGoStandard dunning: flag → ban → cancel.
PayGo (Business)Standard dunning with business account nuances.
ProStandard dunning.
BusinessStandard dunning.
Enterprise TrialStandard dunning with trial conversion handling.
Self-Serve EnterpriseStandard dunning.
Excluded types (need explicit rules)
TypeCurrent Handling
Enterprise (contract)Excluded from automated dunning. Manual process.
PartnerExcluded. Partner billing handled separately.
Cloudflare InternalExcluded. Internal accounts.
FreeNo billing. Skip.
Channel PartnerExcluded. Third-party billing.

The credit note machinery already exists in subscriptions-api. Three issuance paths, an approval pipeline, and a revenue recognition processor. What's missing is one new path — uncollectible invoice adjustment.

ComponentWhat It DoesStatus
Registrar refund pathIssues credit notes for registrar-related refundsProduction — existing
Bad debt write-off pathIssues credit notes as part of bad debt write-offProduction — existing
BrainTree webhook pathIssues credit notes from BrainTree payment eventsProduction — existing
Approval pipeline6 dollar tiers, daily cron for approvalsProduction — existing
credit_note_processor.goRevenue recognition pipeline, 1060+ linesProduction — existing
Uncollectible adjustmentCredit note at uncollectible time for unused serviceNEW — this is the work