PAYGO PLATFORM
Architecture
Three services, clear boundaries. The billing intelligence concentrates in the engine. The pipe feeds it events. The facade displays the result.
subscriptions-api is where billing intelligence lives. Every subscription change — create, upgrade, downgrade, cancel — flows through changes_calculator. Four entry points converge at one mutation pipeline.
Gather → Validate → Plan → Sync+Save. Four entry points (Dashboard, iAPI, Catalog, CF Queues) converge at one path. changes_calculator solves WHAT — plans, quantities, prices, line items.
The recompute engine reads all six stores, computes what dunning status should be, and applies the diff. Five Stripe webhook events feed the engine. One code path for all decisions.
PaymentIntent execution. On/off-session distinction. Confirm-before-commit on growing paths. Live payment controls the payment behavior parameter on every Stripe API call.
Typed actions — ban, cancel, flag, notify — dispatched by dunning decisions. The execution layer is clean. The decision layer is what Q2 consolidates.
billing-webhooks processes 50K–100K Stripe events daily with sub-1s median latency. 27 handlers resolve Stripe events to Cloudflare accounts via subs_customer. At-least-once delivery via Pub/Sub.
customer.subscription.updated, customer.subscription.deleted route to the engine's subscriptions webhook.
invoice.paid, invoice.payment_failed, invoice.finalized route to the engine's dunning endpoint.
mandate.updated, setup_intent.succeeded route to the engine's mandates webhook.
customer.updated, customer.source.updated route to the engine's customers webhook.
accounts is the customer-facing surface. Identity, payment method view, account status. It reads from the engine and Stripe. No billing logic.
Account state is fragmented across six stores. Three domains mutate them. This is the root cause of drift — and the reason the dunning brain reads all six before making any decision.
| Store | Owns | Writers | Drift Risk |
|---|---|---|---|
subs_dunning | Retry count, escalation level, dunning status | engine only | Low — single writer |
subs_account | Billing profile, flags (bit 15 = bad_debt), account type | engine + facade | High — bad_debt flag is primary drift vector |
subs_customer | Stripe↔CF account mapping, checkout data | engine + facade | Medium — shared writes |
billing_prod | Invoice and charge history — financial audit trail | engine | Low — single writer |
cf_prod | Entitlements, product access | accounts + entitlements | Medium — multi-writer |
| Stripe metadata | bad_debt, account_type, bad_debt_amount on invoices | engine + pipe | High — 5 metadata keys govern bad-debt state |
39 Dataform definitions (all views, no materializations) provide the observability layer. Queries always read fresh data from mirrors. Drift detection, account health, and billing metrics all flow from here into the knowledge graph.
The intelligence concentrates in the engine. The pipe delivers events. The facade shows results. Q2 makes this separation cleaner — not by adding services, but by removing decisions from the wrong places.