FloPayCheckout
The recommended way to add payments — a single component that handles everything.
FloPayCheckout Guide
FloPayCheckout is the fastest way to integrate FloPay. One component handles session fetching, Stripe initialization, card fields, wallets, PayPal, 3DS, and checkout modes — no manual wiring needed.
Quick Start
The recommended integration is one-shot: pass a createSession payload and FloPayCheckout creates the session and renders the form in a single step — no separate backend route to build, call, and pass a sessionId back to the client.
import { FloPayCheckout } from '@flopay/react';
import { configureFlopay } from '@flopay/shared';
// Call once at app startup (layout.tsx or _app.tsx)
configureFlopay({ environment: 'production' });
function CheckoutPage() {
return (
<FloPayCheckout
createSession={{
clientId: 'your-client-id',
currency: 'EUR',
products: [{ code: 'omni-ai-booster' }],
account: {
userId: 'user_123',
email: 'customer@example.com',
firstName: 'Jane',
lastName: 'Doe',
},
successUrl: '/success',
cancelUrl: '/cancel',
}}
onComplete={(result) => {
if (result.status === 'succeeded') {
window.location.href = '/success';
}
}}
onError={(err) => console.error(err.message)}
/>
);
}That's it. The component automatically:
- Creates the checkout session from your
createSessionpayload (no backend route required) - Reads
gateways.stripe.publishableKeyto initialize card fields and every payment method enabled on the connected Stripe account - Reads
gateways.paypalto decide which PayPal path to render:- When
gateways.paypalis present: loads the PayPal JS SDK directly (see Direct PayPal guide) - When
nullor missing: renders PayPal via Stripe'sExpressCheckoutElementas fallback (see PayPal via Stripe) - PayPal never renders twice
- When
- Uses each gateway entry's
environment('stage'or'production') to pick sandbox vs live credentials per gateway - Renders card fields, the Stripe-enabled payment methods configured on the connected Stripe account (Apple Pay, Google Pay, Cash App, Klarna, Afterpay, iDEAL, Bancontact, etc.), and PayPal
- Handles 3DS authentication
- Calls
onCompletewhen payment succeeds
See Inline Session Creation for the full createSession payload, currency requirements, and email handling.
Already have a session ID?
If you create the session on your backend (for example to keep pricing or entitlement logic server-side), pass the returned sessionId instead of createSession:
<FloPayCheckout sessionId={sessionId} onComplete={handleSuccess} />Every option on this page works the same whether you pass createSession or sessionId. The examples below use sessionId for brevity.
Environment Setup
FloPayCheckout needs to know which billing API to call. Choose one:
Option A: configureFlopay() (recommended)
import { configureFlopay } from '@flopay/shared';
configureFlopay({ environment: 'production' });Call this once at app startup, for example in app/layout.tsx or _app.tsx.
Option B: Explicit prop
<FloPayCheckout
sessionId={sessionId}
billingApiUrl="https://api.flopay.com"
onComplete={handleSuccess}
/>Checkout Modes
Full Mode (default)
Shows the complete payment form — card fields, wallets, PayPal.
<FloPayCheckout sessionId={sessionId} onComplete={handleSuccess} />Confirm Mode
Uses a saved payment method. Shows a single "Confirm Purchase" button. Falls back to full mode if payment fails.
<FloPayCheckout
sessionId={sessionId}
checkoutMode="confirm"
onComplete={handleSuccess}
/>Customize the confirm button:
<FloPayCheckout
sessionId={sessionId}
checkoutMode="confirm"
renderConfirmButton={({ onConfirm, isProcessing }) => (
<button onClick={onConfirm} disabled={isProcessing}>
{isProcessing ? 'Working...' : 'Buy Now'}
</button>
)}
onComplete={handleSuccess}
/>Auto Mode
Automatically submits with a saved payment method — no user interaction. Falls back to full mode if it fails.
<FloPayCheckout
sessionId={sessionId}
checkoutMode="auto"
onComplete={handleSuccess}
onSessionCompleted={(successUrl) => {
// Session was already completed (e.g. page reload after success)
window.location.href = successUrl;
}}
/>Layout Modes
Default Layout
All payment methods visible together: wallets on top, divider, then card fields below.
Buttons Layout
Payment methods shown as stacked buttons. Clicking "Credit / Debit Card" expands into the card form with a back button and title.
<FloPayCheckout
sessionId={sessionId}
layout="buttons"
onComplete={handleSuccess}
/>Theme Bundles
Pick a bundled theme and the SDK styles both the Stripe-rendered fields and the React-rendered wrapper, submit, and inputs:
// Modern light — Inter, soft shadows, generous spacing
<FloPayCheckout sessionId={id} layout="buttons" theme="modern-light" />
// Bold dark — saturated FloPay blue on a dark surface
<FloPayCheckout sessionId={id} layout="buttons" theme="bold-dark" />
// Glass dark — translucent surfaces over a blue gradient
<FloPayCheckout sessionId={id} layout="buttons" theme="glass-dark" />Available theme values: 'classic' | 'modern-light' | 'modern-dark' | 'bold-light' | 'bold-dark' | 'glass-light' | 'glass-dark'. See the Theming guide for the full reference and resolution precedence.
Custom Styles
Override individual elements with buttonsStyles:
<FloPayCheckout
sessionId={sessionId}
layout="buttons"
buttonsStyles={{
cardButton: { borderRadius: '12px', border: '2px solid #4A49FF' },
cardButtonFontSize: '1rem',
cardFormContainer: { backgroundColor: '#f8f7ff' },
cardInputBorder: '#c4c3ff',
cardInputColor: '#1a1a2e',
cardInputPlaceholderColor: '#9ca3af',
submitButton: { backgroundColor: '#2d2ccc', borderRadius: '12px' },
title: { color: '#2d2ccc' },
}}
onComplete={handleSuccess}
/>Custom Card Button Content
Use cardButtonContent to replace the default card button body with your own React content:
<FloPayCheckout
sessionId={sessionId}
layout="buttons"
cardButtonContent={
<div
style={{
display: 'flex',
alignItems: 'center',
gap: '0.75rem',
width: '100%',
}}
>
<span style={{ fontWeight: 700 }}>Pay by card</span>
<span style={{ fontSize: '0.75rem', opacity: 0.7 }}>
Visa, Mastercard, Amex
</span>
<span style={{ marginLeft: 'auto', fontSize: '0.7rem' }}>Secure</span>
</div>
}
onComplete={handleSuccess}
/>Use cardButtonContent for the inner content and buttonsStyles.cardButton for the outer button container styles.
Buttons Layout Header Slots
Use cardBackButtonContent and cardTitleContent to replace the default "Go back" and "Secure card checkout" text in the expanded card form header. Pass '' when you want to remove the text completely.
<FloPayCheckout
sessionId={sessionId}
layout="buttons"
cardBackButtonContent=""
cardTitleContent="Enter card details"
onComplete={handleSuccess}
/>cardTitleContent also replaces the standalone title in the default card form layout.
For dark backgrounds, the bundled bold-dark / glass-dark themes already style the Stripe Element text inside the iframe. Override individual fields with cardInputColor, cardInputPlaceholderColor, and cardInputBackground when needed:
<FloPayCheckout
sessionId={sessionId}
layout="buttons"
theme="bold-dark"
buttonsStyles={{
cardInputColor: '#f9fafb',
cardInputPlaceholderColor: '#6b7280',
cardInputBackground: '#1f2937',
nameInput: { backgroundColor: '#1f2937', color: '#f9fafb' },
}}
onComplete={handleSuccess}
/>See the full ButtonsLayoutStyles reference for all available properties.
Inline Session Creation (One-Shot)
This is the recommended flow shown in the Quick Start — pass createSession and skip the backend API route entirely. The component creates the session and renders the checkout in one step:
<FloPayCheckout
layout="buttons"
createSession={{
clientId: 'your-client-id',
currency: 'EUR',
products: [{ code: 'omni-ai-booster' }],
account: {
userId: 'user_123',
email: 'customer@example.com',
firstName: 'John',
lastName: 'Doe',
},
successUrl: '/success',
cancelUrl: '/cancel',
}}
onComplete={(result) => window.location.href = '/success'}
/>No sessionId needed — the component creates the session, initializes the payment provider, and renders the form automatically.
For embedded checkout, include createSession.account.email in the initial payload. If you only have a temporary value such as test@email.com, pass it first and then replace it in onBeforeButtonClick before the card flow continues.
Session-level currency is required. The SDK throws FloPayError({ type: 'validation_error', code: 'CurrencyRequired' }) synchronously when no currency can be resolved from the session or any per-line currency. Set createSession.currency at the top of the payload for the cleanest behavior.
Tracking Button Clicks
Fire GTM events when users interact with payment buttons:
<FloPayCheckout
sessionId={sessionId}
layout="buttons"
onButtonClick={(method) => {
window.dataLayer?.push({
event: 'initiate_checkout',
payment_method: method,
});
}}
onComplete={handleSuccess}
/>method values: 'card', 'paypal', 'apple_pay', 'google_pay'.
Before Card Button Click
Use onBeforeButtonClick when you need to do async work before the credit card button continues, such as confirming checkout details or replacing a temporary email address:
<FloPayCheckout
layout="buttons"
createSession={{
clientId: 'your-client-id',
currency: 'EUR',
products: [{ code: 'product-1' }],
account: {
userId: 'user_1',
email: 'test@email.com',
},
successUrl: '/success',
cancelUrl: '/cancel',
}}
onBeforeButtonClick={async ({ method, createSession }) => {
if (method !== 'card') return;
const email = await openEmailCaptureModal({
initialEmail: createSession?.account.email ?? '',
});
if (!email) return false;
return {
account: { email },
};
}}
onComplete={handleSuccess}
/>onBeforeButtonClick is credit card only. It runs only for the "Credit / Debit Card" button in layout="buttons". It does not run for PayPal, Apple Pay, or Google Pay. If you need required data before every payment method, collect it before rendering FloPayCheckout.
Returning false cancels the card click. Throwing routes the error through onError. When createSession is used, the returned patch refreshes the card flow with the merged session draft. createSession.account.email should already be present when the embedded checkout renders; use this hook to update it, not to skip it.
Decline Events
Use onDecline when you want GTM or analytics events for declined payments, failed 3DS, PayPal cancellation, or wallet dismissal:
<FloPayCheckout
sessionId={sessionId}
onDecline={(decline) => {
window.dataLayer?.push({
event: 'checkout_decline',
...decline,
});
}}
onComplete={handleSuccess}
/>Payment Methods
Payment methods are toggled at the gateway level with two props: showStripe and showPayPal. Which individual methods appear inside the Stripe gateway (cards, Apple Pay, Google Pay, Cash App, Klarna, Afterpay, iDEAL, Bancontact, etc.) is controlled from your Stripe dashboard — see Stripe payment methods.
Stripe
Enabled by default. Renders card fields plus every payment method enabled on the connected Stripe account. Apple Pay and Google Pay only paint on supported devices/browsers; Stripe-enabled local methods (Cash App, Klarna, Afterpay, iDEAL, Bancontact, etc.) appear when the buyer's locale and currency match the method's eligibility rules.
// Hide the entire Stripe gateway — cards, wallets, and all Stripe-enabled payment methods
<FloPayCheckout
sessionId={sessionId}
showStripe={false}
onComplete={handleSuccess}
/>Wallet buttons depend on Stripe domain registration. Apple Pay also needs the Apple association file. See the Apple Pay Setup and Google Pay Setup guides.
PayPal
Enabled by default. Routes to the direct PayPal path when the session response includes gateways.paypal, otherwise falls back to PayPal via Stripe. Handles redirects automatically.
// Hide the entire PayPal gateway (both direct and Stripe-rendered paths)
<FloPayCheckout sessionId={sessionId} showPayPal={false} onComplete={handleSuccess} />AVS (Address Verification)
Enable AVS to collect the user's country and postal/ZIP code. When enabled, billing_details are passed to Stripe's createPaymentMethod() so Stripe can run postal code and address verification checks automatically.
Basic Setup
<FloPayCheckout
sessionId={sessionId}
enableAVS
onComplete={handleSuccess}
/>The country dropdown defaults to the session's customer.country value (typically resolved from the user's IP by your backend). If no country is provided, it defaults to US.
Country from GEO/IP Lookup
Pass the user's country in the session creation params. The SDK pre-fills the dropdown:
<FloPayCheckout
createSession={{
clientId: 'your-client-id',
currency: 'EUR',
products: [{ code: 'product-1' }],
account: {
userId: 'user_1',
email: 'user@example.com',
country: 'GB', // resolved from user's IP
},
successUrl: '/success',
cancelUrl: '/cancel',
}}
enableAVS
layout="buttons"
onComplete={handleSuccess}
/>The dropdown shows "United Kingdom" and the postal code label says "Postcode" instead of "ZIP Code".
Dynamic Labels
The postal code field label adapts to the selected country:
| Country | Label |
|---|---|
| US | ZIP Code |
| GB, AU, NZ | Postcode |
| CA | Postal Code |
| IE | Eircode |
| All others | Postal Code |
AVS Field Layout
By default, country and postal code sit side-by-side in a row. Switch to stacked:
<FloPayCheckout
sessionId={sessionId}
enableAVS
avsLayout="column"
onComplete={handleSuccess}
/>Theming AVS Fields
AVS fields inherit from the card input styles. Override individually with countrySelect and zipInput in buttonsStyles:
<FloPayCheckout
sessionId={sessionId}
enableAVS
layout="buttons"
theme="bold-dark"
buttonsStyles={{
countrySelect: { backgroundColor: '#1f2937', color: '#f9fafb' },
zipInput: { backgroundColor: '#1f2937', color: '#f9fafb' },
}}
onComplete={handleSuccess}
/>How It Works
- User selects country and enters postal code in the form
- On submit,
billing_details(name + address with country and postal_code) are passed to Stripe'screatePaymentMethod() - Stripe runs AVS checks automatically when billing details are present
- The
accountData.zipandaccountData.countryare also sent to your backend in the process request - Configure Stripe Dashboard → Radar → Rules to block or allow based on AVS results (e.g., "Block if postal code check fails")
AVS is performed by Stripe at payment confirmation time. The SDK sends the billing details — your Stripe Radar rules determine whether to block, allow, or flag based on the verification result. No backend code changes are needed for basic AVS.
Error Handling
<FloPayCheckout
sessionId={sessionId}
onError={(err) => {
// err.type: 'validation_error' | 'api_error' | 'authentication_error' | ...
// err.message: human-readable error message
console.error(`Payment failed: ${err.message}`);
showToast(err.message);
}}
onComplete={handleSuccess}
/>Suppress the built-in error UI and handle it yourself:
<FloPayCheckout
sessionId={sessionId}
error={() => <></>}
onError={(err) => setMyError(err.message)}
onComplete={handleSuccess}
/>Custom Loading State
<FloPayCheckout
sessionId={sessionId}
loading={<MySkeletonLoader />}
onComplete={handleSuccess}
/>Full Example
See the FloPayCheckout Example for a complete checkout page with order summary, discount timer, and all props configured.
Next Steps
- API Reference — full props table
- Theming — customize card field appearance
- Stripe payment methods — how the dynamic Stripe gateway resolves methods at runtime
- Apple Pay Setup — domain verification
- Google Pay Setup — Stripe payment method domains
- AVS (Address Verification) — postal code verification setup