ACT 1
Diagnosis
We understand the billing platform deeply enough to name its structural flaw. Internal models mirror Stripe's models. PayPal is shimmed. Every seam leaks.
- 196
- rollbacks / day
- 48
- bad debt / day (peak)
- 691
- disagreeing accounts
- 19%
- invoice coverage
- +44%
- CUSTESC escalation
Every webhook handler assumes Stripe's payload shape. No abstraction layer between event ingestion and domain logic. A PayPal equivalent event must be translated into Stripe's vocabulary first.
The core pricing engine speaks fluent Stripe. 15 packages touch the Stripe API directly. No provider-agnostic interface exists. Business logic calls stripe.PaymentIntents.Create and handles Stripe-specific return types.
Every customer and payment method surface is a Stripe surface. Account creation, payment method attachment, billing profiles — all shaped by Stripe's object model. No indirection layer.
Accounts, customers, mandates, invoices, dunning state — all five database tables model Stripe's ontology. Adding a non-Stripe provider means shimming it into Stripe-shaped columns or bolting on parallel tables.
These aren't just numbers. They're the surface area of a single assumption: that the payment provider is Stripe. When that assumption breaks — and PayPal proves it already has — the failure modes cascade through every area.
The root cause isn't technical debt or missing features. It's a modeling decision made early: internal objects mirror Stripe's objects. Subscriptions look like Stripe subscriptions. Invoices look like Stripe invoices. Payment methods carry Stripe's type taxonomy.
Stripe isn't just a provider — it's the schema. The five shared database tables model Stripe's ontology, not the billing domain. The 15 Stripe packages don't encapsulate Stripe — they propagate it. Domain logic speaks PaymentIntent, SetupIntent, Invoice — Stripe nouns, not billing nouns. Every handler, every pipeline stage, every data store assumes the world is Stripe-shaped.
PayPal enters a Stripe-shaped world and takes the wrong path at every seam. This isn't a PayPal problem — it's an existence proof that the architecture can only serve one provider.
PayPal failures enter the same retry logic as Stripe card failures. But PayPal has different failure semantics — different auth models, different dispute flows, different retry likelihood. The dunning engine can't distinguish them.
PayPal payment method collection is shimmed into Stripe's SetupIntent flow. PayPal vaulting works differently — billing agreements, not card tokens. The abstraction doesn't fit.
PDE's always-pay ordering makes the PayPal path safe by construction. But the underlying model is still Stripe-shaped. PDE routes around the problem; it doesn't solve it.
| Metric | Value | Root Cause | Area |
|---|---|---|---|
| Rollbacks / day | 196 | Commit-before-pay ordering — mutations persisted before payment confirmed | Area 2 |
| Bad debt from upgrades / day | 48 (peak) | Uncollectible invoices from failed rollbacks that left orphaned charges | Area 2 → 3 |
| Disagreeing accounts | 691 | Handlers writing subsets of shared state, no component reads all five stores | Area 3 |
| Invoice coverage | 19% | Each handler covers one Stripe event type — 64 of 342 paths covered | Area 3 |
| CUSTESC escalation increase | +44% | Customers hitting silent failures with no dashboard visibility | All areas |
The cascade starts in Area 2: a subscription upgrade commits state before payment confirms. If 3DS authentication is required and the customer abandons, the payment outcome is lost. The system has already provisioned entitlements and published events. Rollback is attempted but incomplete — the invoice becomes uncollectible, the account enters bad debt, and the customer is stuck with no self-service path to resolution. 196 times per day.
The structural flaw isn't in any single component. It's in the modeling decision that shaped every component: internal objects mirror Stripe's objects. Fix the models, and the failure modes disappear.