FloPayCheckout
All-in-one checkout component that handles session fetching, provider initialization, and payment form rendering automatically.
FloPayCheckout
Recommended. FloPayCheckout is the simplest way to add payments to your app. It replaces the manual FloPayProvider + loadFloPay + PaymentAPI setup with a single component.
Basic Usage
import { FloPayCheckout } from '@flopay/react';
function CheckoutPage({ sessionId }: { sessionId: string }) {
return (
<FloPayCheckout
sessionId={sessionId}
onComplete={(result) => {
window.location.href = '/success';
}}
onError={(err) => {
console.error(err.type, err.message);
}}
/>
);
}That's it. The component handles:
- Fetching the checkout session from the billing API
- Reading the
gatewaysmap to discover which gateways the client has provisioned - Extracting the Stripe publishable key from
gateways.stripefor the Stripe-driven payment methods - Choosing the PayPal path from
gateways.paypal: direct PayPal when present (see the Direct PayPal guide), otherwise the Stripe-rendered PayPal via Stripe fallback - Initializing FloPay with the correct per-gateway publishable key and
environment - Rendering a
SplitCardFormwith card fields, the Stripe-enabled payment methods configured on the connected Stripe account (Apple Pay, Google Pay, and Stripe-enabled local methods such as Cash App, Klarna, Afterpay), and PayPal - Injecting session data (email, userId, amount, currency) automatically
Props
| Prop | Type | Default | Description |
|---|---|---|---|
sessionId | string | — | Checkout session UUID from the billing API. Required unless createSession is provided. |
nonce | string | — | Session-bound checkout token (the nonce returned when sessionId was created). Pass it with a backend-created sessionId so the SDK can authenticate continuation calls — without it post-#640 backends reject the session read and /process with 401 "Missing checkout session token.". Not needed with createSession, where the SDK mints and forwards the nonce itself. See Checkout Session Token. |
createSession | InlineSessionDraft | — | Create a session inline — no separate API route needed. Alternative to sessionId. For embedded checkout, pass createSession.account.email up front. |
billingApiUrl | string | Auto-resolved | Override the billing API URL. By default resolved from configureFlopay(), with staging as the final fallback. See Configuration. |
appearance | FloPayAppearance | — | Visual theme for payment elements. |
locale | string | 'auto' | Locale for payment elements. |
onComplete | (result: PaymentResult) => void | — | Called when payment succeeds. |
onError | (error: FloPayError) => void | — | Called when payment fails. |
onDecline | (decline: DeclineEvent) => void | — | Called when a payment is declined, authentication fails, PayPal is cancelled, or a wallet sheet is dismissed. |
onFullNameChange | (value: string) => void | — | Called when the cardholder name input changes. |
onCountryChange | (country: string) => void | — | Called when the AVS country dropdown changes. |
onZipChange | (zip: string) => void | — | Called when the AVS ZIP/postcode input changes. |
showStripe | boolean | true | Whether to render the Stripe gateway. When false, the entire Stripe panel is hidden — card fields, Apple Pay, Google Pay, and every other Stripe-enabled payment method. When true (default), each payment method enabled on the connected Stripe account renders dynamically. See Stripe payment methods. |
showPayPal | boolean | true | Whether to render the PayPal gateway. When false, both the direct PayPal and PayPal via Stripe paths are hidden. |
layout | 'default' | 'buttons' | 'default' | Layout mode. 'buttons' shows PayPal, wallets, and a "Credit / Debit Card" button that expands into the card form. |
theme | ThemeId | — | Single-prop theming. Styles both the Stripe-side appearance and the React-rendered wrapper, submit, and inputs from one value. See Theme Bundles. |
buttonsStyles | ButtonsLayoutStyles | — | Per-field style overrides merged on top of the resolved theme bundle. |
buttonsTheme | ButtonsLayoutTheme | — | Deprecated. Legacy preset ('default' | 'minimal' | 'rounded' | 'dark') still resolved via resolveButtonsLayoutTheme() for back-compat. Prefer theme. Silently ignored when theme is also supplied. |
cardButtonContent | ReactNode | — | Replaces the default content inside the card button when layout="buttons". |
cardBackButtonContent | ReactNode | — | Replaces the default "Go back" label in the expanded card form when layout="buttons". Pass '' to remove the text and keep only the icon. |
cardTitleContent | ReactNode | — | Replaces the default "Secure card checkout" title. Pass '' to remove the title text entirely. |
onButtonClick | (method: CheckoutButtonMethod) => void | — | Called when a payment method button is clicked. method: 'card', 'paypal', 'apple_pay', or 'google_pay'. |
onBeforeButtonClick | (event: BeforeButtonClickEvent) => void | false | InlineSessionPatch | Promise<...> | — | Card only. Runs before the "Credit / Debit Card" button continues in layout="buttons". Returning false cancels the click. Returning a patch refreshes the card flow with merged createSession data. |
enableAVS | boolean | AVSFieldConfig | false | Enable Address Verification. true → country dropdown + ZIP/postcode (legacy default). Pass an AVSFieldConfig object for per-field, per-country control of country, postal code, street address, city, and state. See AVS guide. |
avsLayout | 'row' | 'column' | 'row' | Layout for the country / postal code pair: 'row' (side-by-side) or 'column' (stacked). Address line, city, and state always render on their own rows. |
submitLabel | string | 'CONFIRM PAYMENT' | Custom submit button text. |
loading | ReactNode | Spinner | Custom loading UI while session loads. |
error | (err: FloPayError) => ReactNode | Error message | Custom error UI if session fails to load. |
debug | boolean | false | Render extra on-screen diagnostic panels (gateway/PayPal lifecycle, available payment methods). Intended for local development. See Debug mode. |
className | string | — | CSS class for the form wrapper. |
children | ReactNode | SplitCardForm | Override the default form (see below). |
checkoutMode | CheckoutMode | Session value or 'full' | Checkout mode: 'full', 'auto', or 'confirm'. |
confirmLabel | string | 'Confirm Purchase' | Label for the confirm button in confirm mode. |
renderConfirmButton | (props) => ReactNode | — | Custom confirm button renderer. |
onSessionCompleted | (successUrl: string) => void | — | Called when session is already completed. |
Checkout Modes
Full Mode (default)
Standard checkout — shows card fields, the Stripe-enabled payment methods configured on the connected Stripe account (Apple Pay, Google Pay, Cash App, Klarna, etc.), and PayPal. User enters payment details and submits.
Confirm Mode
Shows a "Confirm Purchase" button instead of the payment form. Uses a saved payment method on the backend (vault). Falls back to full mode if payment fails.
<FloPayCheckout
sessionId={sessionId}
checkoutMode="confirm"
confirmLabel="Complete Purchase"
onComplete={(result) => router.push('/success')}
/>You can 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 after the session loads. No user interaction required. Falls back to full mode if auto-checkout fails.
<FloPayCheckout
sessionId={sessionId}
checkoutMode="auto"
onComplete={(result) => router.push('/success')}
onSessionCompleted={(successUrl) => router.push(successUrl)}
/>The onSessionCompleted callback fires when the session is already completed (e.g., from a previous auto-checkout). Use it to redirect the user.
Layout Modes
Default Layout
Shows all payment methods and the card form together — PayPal, Apple Pay, Google Pay above a divider, then the card fields below.
Buttons Layout
Shows payment methods as stacked buttons: PayPal, wallets (Apple/Google Pay), and a "Credit / Debit Card" button. Clicking the card button expands into the card form with a "Go back" button and title. Both text regions can be replaced via slot props.
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.
<FloPayCheckout
sessionId={sessionId}
layout="buttons"
onComplete={handleSuccess}
/>Theme Bundles
Pass a ThemeId to the theme prop. The value styles the Stripe-rendered fields and the React-rendered wrapper, submit button, and inputs from one source.
theme value | Aesthetic |
|---|---|
'classic' | No-op marker — preserves the historic FloPay look (#EDEDFF wrapper, #4A49FF indigo submit) |
'modern-light' / 'modern-dark' | Inter, soft shadows, generous spacing, FloPay-blue accents |
'bold-light' / 'bold-dark' | Saturated FloPay blue with gradient pill submit and heavy borders |
'glass-light' / 'glass-dark' | Translucent surfaces with backdrop blur over a blue gradient |
<FloPayCheckout
sessionId={sessionId}
layout="buttons"
theme="modern-light"
onComplete={handleSuccess}
/>
<FloPayCheckout
sessionId={sessionId}
layout="buttons"
theme="bold-dark"
onComplete={handleSuccess}
/>
<FloPayCheckout
sessionId={sessionId}
layout="buttons"
theme="glass-dark"
onComplete={handleSuccess}
/>Resolution Precedence
When multiple styling props are present, the SDK resolves them from highest priority to lowest:
- Explicit
appearance— overrides the resolved bundle's appearance (palette + Striperules). - Explicit
buttonsStyles— merges per-field on top of the resolved bundle's button-layout styles. themebundle.- Legacy
buttonsThemepreset (back-compat). - Hardcoded defaults.
Custom Buttons Styles
Use buttonsStyles to override individual fields on top of the resolved theme bundle:
<FloPayCheckout
sessionId={sessionId}
layout="buttons"
theme="modern-light"
buttonsStyles={{
cardButton: {
borderRadius: '12px',
border: '2px solid #4A49FF',
fontWeight: 700,
},
cardFormContainer: {
backgroundColor: '#f8f7ff',
borderRadius: '12px',
},
cardInputBorder: '#c4c3ff',
submitButton: {
backgroundColor: '#2d2ccc',
borderRadius: '12px',
},
backButton: {
color: '#4A49FF',
},
backButtonIcon: {
backgroundColor: '#ededff',
},
title: {
color: '#2d2ccc',
fontSize: '1.2rem',
},
}}
onComplete={handleSuccess}
/>Custom Card Button Content
Use cardButtonContent when you want to replace the default "Credit / Debit Card" button body with your own React content while keeping the Flo button behavior and outer styling.
<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 React content and buttonsStyles.cardButton for the outer button container styles.
Buttons Layout Header Slots
Use cardBackButtonContent and cardTitleContent when you want to replace the default text in the expanded card form header. These props accept any ReactNode, and passing '' removes the text completely.
<FloPayCheckout
sessionId={sessionId}
layout="buttons"
cardBackButtonContent=""
cardTitleContent="Enter card details"
onComplete={handleSuccess}
/>cardTitleContent also applies to the standalone title in the default card form layout.
ButtonsLayoutStyles Reference
Layout & Container Styles:
| Property | Type | Description |
|---|---|---|
cardButton | Record<string, string | number> | Style for the "Credit / Debit Card" button |
cardButtonFontSize | string | Font size for the card button label (default: '0.95rem') |
cardFormContainer | Record<string, string | number> | Style for the expanded card form wrapper |
backButton | Record<string, string | number> | Style for the "Go back" button text |
backButtonFontSize | string | Font size for the "Go back" label (default: '0.85rem') |
backButtonIcon | Record<string, string | number> | Style for the circular back button icon |
submitButton | Record<string, string | number> | Style for the submit/confirm button |
submitButtonFontSize | string | Font size for the submit button label (default: '1rem') |
title | Record<string, string | number> | Style for the "Secure card checkout" title |
titleFontSize | string | Font size for the title (default: '1.05rem') |
errorBanner | Record<string, string | number> | Style for the error message banner |
Card Input Styles:
These control the Stripe Elements appearance (rendered inside iframes):
| Property | Type | Description |
|---|---|---|
cardInputBorder | string | Border color for card number, expiry, CVC, and name fields |
cardInputColor | string | Text color inside Stripe card input fields |
cardInputPlaceholderColor | string | Placeholder text color for Stripe card input fields |
cardInputFontSize | string | Font size for Stripe card input fields |
cardInputBackground | string | Background color for card input field containers |
nameInput | Record<string, string | number> | Style for the "Full Name on Card" text input |
AVS Field Styles:
| Property | Type | Description |
|---|---|---|
countrySelect | Record<string, string | number> | Style for the country select dropdown container |
zipInput | Record<string, string | number> | Style for the ZIP/postcode input container |
addressLine1Input | Record<string, string | number> | Style for the street address input (address_line_1) |
addressLine2Input | Record<string, string | number> | Style for the apt/suite input (address_line_2) |
cityInput | Record<string, string | number> | Style for the city input |
stateInput | Record<string, string | number> | Style for the state/province input or dropdown |
The cardInputColor, cardInputPlaceholderColor, and cardInputFontSize properties are passed to Stripe Elements via their style option, which controls text appearance inside the cross-origin iframe. This is how the dark theme gets light text on dark backgrounds. AVS fields (countrySelect, zipInput) inherit from the card input styles by default — override them individually for custom theming.
Importing Theme Bundles
You can also import the bundle map or individual constants directly when you need to merge two themes or render a preview swatch outside the SDK:
import {
THEMES,
resolveTheme,
MODERN_LIGHT_APPEARANCE,
BUTTONS_LAYOUT_MODERN_LIGHT,
} from '@flopay/shared';
// Resolve by id
const bundle = resolveTheme('modern-light');
// Or merge a bundle's buttonsLayout with custom overrides
const customLayout = {
...BUTTONS_LAYOUT_MODERN_LIGHT,
cardButton: {
...BUTTONS_LAYOUT_MODERN_LIGHT.cardButton,
backgroundColor: '#f0f0ff',
},
};See Constants → Themes for every exported constant.
The legacy BUTTONS_LAYOUT_DEFAULT / _MINIMAL / _ROUNDED / _DARK presets and resolveButtonsLayoutTheme() continue to work for back-compat. Prefer the new THEMES map and resolveTheme in new code.
Inline Session Creation
Instead of creating a session via a backend API route and passing the sessionId, you can create the session directly from the component — zero backend code needed:
<FloPayCheckout
layout="buttons"
createSession={{
clientId: '18bff186-284c-483f-acee-e712f21d2b8d',
currency: 'EUR',
products: [{
code: 'omni-ai-booster',
name: 'AI Supercharger Pack',
totalAmount: 8000,
}],
account: {
userId: 'user_123',
email: 'customer@example.com',
firstName: 'John',
lastName: 'Doe',
},
successUrl: '/success',
cancelUrl: '/cancel',
}}
onComplete={(result) => window.location.href = '/success'}
onError={(err) => console.error(err.message)}
/>The component POSTs to the billing API, receives the full session data, initializes Stripe, and renders the payment form — all in one step.
Backend requirement: The billing API must support ?expand=true on POST /v1/checkouts/sessions to return full session data. If not supported, the component falls back to the two-step flow (create + GET).
The create response also carries a nonce that authenticates every subsequent /v1/checkouts/* call as the x-checkout-session-token header. When FloPayCheckout creates the session (createSession) it handles this automatically; when you pass a backend-created sessionId, supply the matching nonce prop so the SDK can forward it. Direct HTTP integrators should read the Checkout session token guide.
InlineSessionDraft
| Field | Type | Required | Description |
|---|---|---|---|
clientId | string | Yes | Client identifier |
currency | string | Required | Session-level ISO 4217 currency code. The SDK throws FloPayError({ type: 'validation_error', code: 'CurrencyRequired' }) before any network call when nothing can be resolved from this field or per-line currency. |
products | CheckoutProduct[] | No | Unified products array. Each entry is a catalog entry identified by code and is sent to the backend verbatim. |
account | CheckoutAccount with optional email | Yes | Buyer info. For embedded checkout, send account.email up front because the session is created with accountData.email. If needed, seed a temporary email and replace it in onBeforeButtonClick before the card flow continues. |
successUrl | string | Yes | Redirect URL after success |
cancelUrl | string | Yes | Redirect URL on cancel |
checkoutMode | 'full' | 'auto' | 'confirm' | No | Default: 'full' |
couponCodes | string[] | No | Discount codes |
tagsData | TagsData | No | Analytics tags |
utmMetadata | Record<string, string | null | undefined>[] | No | UTM / funnel metadata |
Button Click Events
Track when users interact with payment methods using onButtonClick:
<FloPayCheckout
sessionId={sessionId}
layout="buttons"
onButtonClick={(method) => {
// method: 'card' | 'paypal' | 'apple_pay' | 'google_pay'
window.dataLayer?.push({
event: 'initiate_checkout',
payment_method: method,
});
}}
onComplete={handleSuccess}
/>The event fires at these moments:
| Action | method value |
|---|---|
| "Credit / Debit Card" button clicked (buttons layout) | 'card' |
| Card form submitted (default layout) | 'card' |
| PayPal button clicked | 'paypal' |
| Apple Pay button clicked | 'apple_pay' |
| Google Pay button clicked | 'google_pay' |
For PayPal and wallets, the event fires when the buyer clicks the button to initiate the payment — before the PayPal redirect or the wallet authorization sheet. If onBeforeButtonClick returns false for that method, onButtonClick is not fired (the click is cancelled). Use onComplete to track successful payment confirmation.
onBeforeButtonClick
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', totalAmount: 29.99 }],
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 },
tagsData: { sessionId: 'email_captured' },
};
}}
onComplete={handleSuccess}
/>onBeforeButtonClick only runs for the buttons-layout credit card button. It does not run for PayPal, Apple Pay, Google Pay, or default-layout card submission. Returning false cancels the click. Throwing routes the error to onError. Returned patches may include account, couponCodes, tagsData, and utmMetadata. FloPayCheckout still bootstraps PayPal and wallet buttons normally when this hook is present; only the card path is enriched by the returned patch. createSession.account.email should already be present when the embedded checkout renders; use this hook to update it, not to omit it from the initial session payload.
onDecline
Use onDecline when you need structured failure/cancellation events:
<FloPayCheckout
sessionId={sessionId}
onDecline={(decline) => {
// { method, message, code?, declineCode? }
window.dataLayer?.push({
event: 'checkout_decline',
...decline,
});
}}
onComplete={handleSuccess}
/>onDecline fires for provider declines, failed 3DS, PayPal cancellations, and wallet dismissals.
Custom Loading State
<FloPayCheckout
sessionId={sessionId}
loading={<MySkeletonLoader />}
onComplete={handleSuccess}
/>Custom Error State
<FloPayCheckout
sessionId={sessionId}
error={(err) => (
<div className="error-banner">
<p>Failed to load checkout: {err.message}</p>
<button onClick={() => window.location.reload()}>Retry</button>
</div>
)}
onComplete={handleSuccess}
/>Debug mode
Pass debug to render extra on-screen diagnostic panels while developing locally. When debug is omitted or false, all diagnostic state is no-op'd and nothing extra is rendered, so it's safe to leave the prop wired up in shared code.
<FloPayCheckout sessionId={sessionId} debug onComplete={handleSuccess} />With debug enabled, the component currently surfaces:
- Which payment methods resolved as available for the session (card, PayPal, Apple Pay, Google Pay) and which gateway each is routed through.
- The Direct PayPal button lifecycle (script load, button render, order creation, approval, capture, failure) — including the failure panel that would otherwise return
nullin production.
debug is intended for local development only. Do not enable it in production builds — the diagnostic panels are not styled for end users and expose internal state. We will expand the information shown by debug in later SDK versions; the rendered output is not a stable contract.
Custom Form (Children Override)
By default, FloPayCheckout renders a SplitCardForm. To use a different form component, pass it as children:
import { FloPayCheckout, CheckoutForm } from '@flopay/react';
<FloPayCheckout sessionId={sessionId}>
<CheckoutForm
onComplete={handleSuccess}
layout="accordion"
showAddress="billing"
/>
</FloPayCheckout>When you provide children, FloPayCheckout auto-injects sessionId, billingApiUrl, email, userId, firstName, and lastName from the session data. You don't need to pass them manually. Explicit props on the child take precedence.
Comparison with Manual Setup
FloPayCheckout (recommended)
import { FloPayCheckout } from '@flopay/react';
<FloPayCheckout sessionId={sessionId} onComplete={handleSuccess} />Manual Setup (advanced)
import { loadFloPay, PaymentAPI } from '@flopay/js';
import { FloPayProvider, SplitCardForm } from '@flopay/react';
const api = new PaymentAPI('https://api.stage.flopay.com');
const session = await api.getUnifiedCheckoutSession(sessionId);
const flopay = await loadFloPay(session.data.stripe?.publishableKey);
<FloPayProvider
flopay={flopay}
options={{ paymentMethodCreation: 'manual', amount: 4999, currency: 'usd' }}
>
<SplitCardForm
sessionId={sessionId}
email="user@example.com"
userId="user_123"
onComplete={handleSuccess}
/>
</FloPayProvider>Use the manual setup when you need full control over provider initialization, element options, or want to use the FloPay SDK outside of React.