PayPal breaks the confirm-before-commit pattern

Dual-processor: Stripe manages subscriptions, Braintree collects PayPal
pattern

Stripe handles subscriptions, invoicing, customer records, and webhook events. Braintree handles PayPal transactions, Billing Agreement lifecycle, and dispute resolution. subs-api orchestrates between them.

Adding PayPal changes ALL subscriptions to send_invoice
edge-case

Critical gotcha: adding PayPal changes the collection method on every subscription the customer has to send_invoice + days_until_due, even if they also have a credit card on file. Removing PayPal reverts to charge_automatically.

PayPal renewal: send_invoice mode, subs-api initiates Braintree charge
mechanism

PayPal renewals use send_invoice collection mode — Stripe creates invoice but does NOT collect. subs-api receives webhook, initiates Braintree charge, then marks PaidOutOfBand. Card renewals use charge_automatically where Stripe handles payment entirely.

All Stripe-native PMs use charge_automatically; PayPal uses send_invoice
fact

Credit Card, Apple Pay, Google Pay, ACH, CashApp, Link, and SEPA Debit all use charge_automatically via Stripe. PayPal is the only PM using send_invoice via Braintree.

PayPal redirects = requires_action = 3DS = 'payment needs user interaction'
thesis

In the deferred collection model, PayPal mandates and redirects ARE requires_action. The frontend handles them identically to 3DS — redirect the user, wait for confirmation, activate. No special PayPal code path needed.

PayPal dedup: check braintree_transaction_id before charge
mechanism

PayPal deduplication uses braintree_transaction_id check before charge. Card payments use Stripe idempotency keys.

Open: does client-secret-from-subscription-change work for PayPal?
question

Does the client-secret-from-subscription-change pattern work for PayPal? When default_incomplete creates a PaymentIntent for a PayPal customer, does the client secret support the redirect/mandate flow through Stripe.js? Spike needed early Q2.

PayPal dunning trigger: invoice.overdue (not invoice.payment_failed)
mechanism

PayPal dunning is triggered by invoice.overdue event, not invoice.payment_failed. Custom retry via subs-api + Braintree. Handler code: endpoints_stripe_dunning.go.