SYSTEM ARCHITECTURE
PayPal: The Braintree Bridge
PayPal uses a separate processor (Braintree) while piggybacking Stripe for subscription management. Five lifecycle stages — each diverges from the Stripe path.
PayPal is the only payment method using a completely separate processor. The "Braintree Bridge" pattern creates unique handling across the entire lifecycle. Every other payment method flows natively through Stripe. PayPal is the existence proof that internal models mirror Stripe's models — shimmed into a Stripe-shaped world, it takes the wrong path at every seam.
| Aspect | Stripe-Native Path | PayPal / Braintree Path |
|---|---|---|
| Token storage | Store PM in Stripe as pm_XXXXXX | Create Braintree customer, vault PayPal nonce → Billing Agreement |
| Stripe metadata | Native PM attached to customer | Store braintree_id + external_payment_methods in customer metadata |
| Collection method | charge_automatically | Switch ALL subs to send_invoice + days_until_due |
| Supported methods | Cards, Apple Pay, Google Pay, ACH, CashApp, Link, SEPA Debit | PayPal only |
Critical gotcha: adding PayPal changes the collection method on every subscription the customer has, even if they also have a credit card on file. Removing PayPal reverses this — collection_method reverts to charge_automatically.
| Aspect | Stripe (Card) | PayPal (Braintree) |
|---|---|---|
| On-session routing | gateOnSessionFlow() returns true — direct charge | gateOnSessionFlow() returns false — always off-session path |
| Payment call | InvoicesPay — direct charge, funds land in Stripe | Braintree ChargePayPalAccount (GraphQL) → InvoicesPay with PaidOutOfBand |
| Failure handling | Void invoice, rollback subscription changes | Skip rollback, leave invoice unpaid for dunning |
| Dedup mechanism | Stripe idempotency keys | braintree_transaction_id check before charge |
| Aspect | Stripe (Card) | PayPal (Braintree) |
|---|---|---|
| Collection mode | charge_automatically — Stripe handles payment entirely | send_invoice — Stripe creates invoice but does NOT collect |
| subs-api role | Receives webhook, no collection action needed | Receives webhook, initiates Braintree charge, marks PaidOutOfBand |
| Pipeline | Stripe Event → Cloud Functions → Pub/Sub → billing-webhooks → done | Same pipeline → subs-api → Braintree charge → mark paid |
| Aspect | Stripe (Card) | PayPal (Braintree) |
|---|---|---|
| Trigger event | invoice.payment_failed | invoice.overdue |
| Retry mechanism | Stripe Smart Retries (automatic) | Custom retry via subs-api + Braintree |
| Handler code | Stripe internal retry logic | endpoints_stripe_dunning.go |
| Terminal state | invoice.marked_uncollectible → cancel subscriptions → ban account | invoice.marked_uncollectible → cancel subscriptions → ban account |
Refund routing is determined by checking braintree_transaction_id in the invoice metadata. If present, the payment went through Braintree and must be refunded there — Stripe only gets a credit note. If absent, standard Stripe Refund API. Two systems updated for PayPal refunds, one transaction.
| Method | Gateway | Stripe PM Type | Collection Mode | On-Session |
|---|---|---|---|---|
| Credit Card | Stripe | card | charge_automatically | Yes |
| Apple Pay | Stripe | card (wallet) | charge_automatically | Yes |
| Google Pay | Stripe | card (wallet) | charge_automatically | Yes |
| ACH Direct Debit | Stripe | us_bank_account | charge_automatically | Yes |
| CashApp | Stripe | cashapp | charge_automatically | Yes |
| Link | Stripe | link | charge_automatically | Yes |
| SEPA Debit | Stripe | sepa_debit | charge_automatically | Yes |
| PayPal | Braintree | N/A | send_invoice | No |
PayPal is the only payment method that uses a completely separate processor (Braintree) while piggybacking on Stripe for subscription management, invoicing, and record-keeping. This "Braintree Bridge" pattern creates unique handling across the entire lifecycle. Every other payment method flows natively through Stripe.