FloPayFloPay
Guides

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

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({ sessionId }: { sessionId: string }) {
  return (
    <FloPayCheckout
      sessionId={sessionId}
      onComplete={(result) => {
        if (result.status === 'succeeded') {
          window.location.href = '/success';
        }
      }}
      onError={(err) => console.error(err.message)}
    />
  );
}

That's it. The component automatically:

  1. Fetches the checkout session from the billing API
  2. Reads the Stripe publishable key from gatewayData.publishableKey (card, Apple Pay, Google Pay)
  3. Reads the dedicated PayPal publishable key from gatewayData.paypalPublishableKey (PayPal). When this field is null or missing, the PayPal button is hidden — there is no fallback to the primary key.
  4. Initializes one Stripe instance per key (two when the keys differ)
  5. Renders card fields, Apple Pay, Google Pay, and PayPal
  6. Handles 3DS authentication
  7. Calls onComplete when payment succeeds

Environment Setup

FloPayCheckout needs to know which billing API to call. Choose one:

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 Presets

Four built-in themes for the buttons layout:

// Minimal — borderless, subtle backgrounds
<FloPayCheckout sessionId={id} layout="buttons" buttonsTheme="minimal" />
 
// Rounded — pill buttons, soft shadows
<FloPayCheckout sessionId={id} layout="buttons" buttonsTheme="rounded" />
 
// Dark — dark backgrounds, light text
<FloPayCheckout sessionId={id} layout="buttons" buttonsTheme="dark" />

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, use cardInputColor, cardInputPlaceholderColor, and cardInputBackground to style the Stripe Element text inside the iframe:

<FloPayCheckout
  sessionId={sessionId}
  layout="buttons"
  buttonsTheme="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)

Skip the backend API route entirely — create the session and render the checkout in one component:

<FloPayCheckout
  layout="buttons"
  createSession={{
    clientId: 'your-client-id',
    items: [{
      providerItemId: 'omni-ai-booster',
      providerItemName: 'AI Supercharger Pack',
      totalAmount: 8000,
      overrideAmount: 3900,
      currency: 'EUR',
    }],
    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.

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',
    items: [{
      providerItemId: 'product-1',
      totalAmount: 29.99,
      currency: 'EUR',
    }],
    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

Apple Pay & Google Pay

Enabled by default. Only renders on supported devices/browsers.

// Disable wallets
<FloPayCheckout
  sessionId={sessionId}
  showApplePay={false}
  showGooglePay={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. Requires PayPal to be enabled on the Stripe account. Handles redirects automatically.

// Disable PayPal
<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',
    items: [{ providerItemId: 'product-1', totalAmount: 29.99, currency: 'EUR' }],
    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:

CountryLabel
USZIP Code
GB, AU, NZPostcode
CAPostal Code
IEEircode
All othersPostal 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"
  buttonsTheme="dark"
  buttonsStyles={{
    countrySelect: { backgroundColor: '#1f2937', color: '#f9fafb' },
    zipInput: { backgroundColor: '#1f2937', color: '#f9fafb' },
  }}
  onComplete={handleSuccess}
/>

How It Works

  1. User selects country and enters postal code in the form
  2. On submit, billing_details (name + address with country and postal_code) are passed to Stripe's createPaymentMethod()
  3. Stripe runs AVS checks automatically when billing details are present
  4. The accountData.zip and accountData.country are also sent to your backend in the process request
  5. 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