FloPayFloPay
Gateways

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.

PathWhen it rendersRequired for
Direct PayPalSession 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 StripeSession 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

CapabilityDirect PayPalPayPal via Stripe
One-time purchasesYes (PayPal Orders v2)Yes (Stripe PaymentIntent with PayPal as the payment method)
Subscription signupYes (PayPal Subscriptions API)Yes (Stripe Subscription with PayPal payment method)
Recurring auto-chargeYes — native PayPal Subscriptions billing cycle, no off-session retry from FloYes — Stripe drives the renewal
Pause / resume / cancelYes (/suspend, /activate, /cancel on the PayPal subscription)Yes (Stripe subscription transitions)
RefundsYes (against the PayPal capture id)Yes (against the Stripe charge id)
Vaulted token reuse for upsellsYes (vault.payment-token.created → reuse via user_payment_method with type='paypal')Yes (Stripe payment method reuse)
Works in Facebook / Meta / Instagram in-app browsersYesNo (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:

  1. Existing Stripe-only clients keep working with no changes. gateways.paypal is null, so the SDK renders the existing Stripe-rendered PayPal button.
  2. To opt a client into direct PayPal, follow the direct PayPal setup guide to provision a provider_type=paypal gateway record. Once the gateway exists, FloPay starts populating gateways.paypal on that client's sessions and the SDK switches automatically.
  3. Rolling back is a matter of deactivating the provider_type=paypal gateway record. The session response drops gateways.paypal, and the SDK falls back to the Stripe path on the next session.

Next Steps

On this page