PayPal Integration
Choose the right PayPal path — direct PayPal vs the Stripe-rendered fallback — and understand when each renders.
PayPal Integration
FloPay supports PayPal through two paths. The SDK picks the right one automatically from the checkout session response — you do not switch components or props.
| Path | When it renders | Required for |
|---|---|---|
| Direct PayPal | Session response includes gateways.paypal (the client provisioned a provider_type=paypal gateway record). | Buyers arriving from Facebook, Meta, or Instagram in-app browsers — and the recommended default for new integrations. |
| PayPal via Stripe | Session response does not include gateways.paypal (the client only has a Stripe gateway). | Backwards compatibility for existing Stripe-only clients. |
PayPal never renders both buttons at once. The two paths are mutually exclusive per session.
Why Two Paths
Stripe's ExpressCheckoutElement PayPal renderer does not work reliably inside Facebook, Meta, and Instagram in-app browsers — the PayPal popup either fails to open or fails to return to the originating tab. Clients running paid social funnels were losing buyers there.
Direct PayPal sidesteps Stripe entirely on the PayPal path. FloPay loads the PayPal JS SDK with the gateway's client_id and environment, mounts PayPal's own button, and processes the capture against PayPal directly. It works inside the in-app browsers that break the Stripe-rendered flow.
Selection Logic
session.gateways.paypal ── present? ── yes ──> render direct PayPal (PayPal JS SDK)
│
no ──> render Stripe-rendered PayPal (ExpressCheckoutElement)FloPayCheckout consume the gateways map from the session response and route automatically. You do not need to feature-flag, sniff the user agent, or change props per client.
See CheckoutGatewaysDto for the full response shape. The previous top-level gateway field and gatewayData.paypalPublishableKey field have been replaced — see Migration.
SDK Toggle
showPayPal is the only SDK-level toggle for PayPal. It controls the entire PayPal gateway — both the direct PayPal path and the Stripe-rendered fallback — and defaults to true:
// Hide PayPal entirely (direct + Stripe-rendered)
<FloPayCheckout sessionId={sessionId} showPayPal={false} onComplete={handleSuccess} />Path selection (direct vs Stripe-rendered) is driven by the session's gateways.paypal entry, not by a prop.
Lifecycle Coverage
| Capability | Direct PayPal | PayPal via Stripe |
|---|---|---|
| One-time purchases | Yes (PayPal Orders v2) | Yes (Stripe PaymentIntent with PayPal as the payment method) |
| Subscription signup | Yes (PayPal Subscriptions API) | Yes (Stripe Subscription with PayPal payment method) |
| Recurring auto-charge | Yes — native PayPal Subscriptions billing cycle, no off-session retry from Flo | Yes — Stripe drives the renewal |
| Pause / resume / cancel | Yes (/suspend, /activate, /cancel on the PayPal subscription) | Yes (Stripe subscription transitions) |
| Refunds | Yes (against the PayPal capture id) | Yes (against the Stripe charge id) |
| Vaulted token reuse for upsells | Yes (vault.payment-token.created → reuse via user_payment_method with type='paypal') | Yes (Stripe payment method reuse) |
| Works in Facebook / Meta / Instagram in-app browsers | Yes | No (popup / return-tab issues) |
Inbound gateway events on both paths are normalized into the same outbound subscription.*, item.*, and invoice.* Flo webhook families — the outbound webhook shape does not change. See PayPal webhook events for the inbound PayPal event catalog and mapping.
Migration
For each client:
- Existing Stripe-only clients keep working with no changes.
gateways.paypalisnull, so the SDK renders the existing Stripe-rendered PayPal button. - To opt a client into direct PayPal, follow the direct PayPal setup guide to provision a
provider_type=paypalgateway record. Once the gateway exists, FloPay starts populatinggateways.paypalon that client's sessions and the SDK switches automatically. - Rolling back is a matter of deactivating the
provider_type=paypalgateway record. The session response dropsgateways.paypal, and the SDK falls back to the Stripe path on the next session.
Next Steps
- Direct PayPal — primary path, setup, lifecycle, sandbox checklist
- PayPal via Stripe — Stripe-rendered fallback path
- PayPal webhook events — inbound PayPal event catalog and outbound mapping
CheckoutGatewaysDto— session response shape