FloPayFloPay
Examples

FloPayCheckout

Complete checkout page example using the all-in-one FloPayCheckout component.

FloPayCheckout Example

A full checkout page with order summary, discount timer, and FloPayCheckout handling all payment logic.

Complete Page

'use client';
 
import type { NormalizedCheckoutSession, PaymentResult } from '@flopay/shared';
import { Suspense, useCallback, useEffect, useMemo, useState } from 'react';
import { buildCheckoutDisplayData, configureFlopay, resolveBillingApiUrl } from '@flopay/shared';
import { FloPayCheckout } from '@flopay/react';
import { PaymentAPI } from '@flopay/js';
import { useSearchParams } from 'next/navigation';
 
// Configure once at module load
configureFlopay({ environment: 'production' });
 
function CheckoutContent() {
  const searchParams = useSearchParams();
  const sessionId = searchParams.get('id') ?? '';
  const modeParam = searchParams.get('mode') as 'full' | 'auto' | 'confirm' | null;
 
  const [session, setSession] = useState<NormalizedCheckoutSession | null>(null);
  const [isLoading, setIsLoading] = useState(true);
  const [error, setError] = useState<string | null>(null);
  const [countdown, setCountdown] = useState(600);
 
  const api = useMemo(() => new PaymentAPI(resolveBillingApiUrl()), []);
 
  useEffect(() => {
    if (!sessionId) return;
    let cancelled = false;
    (async () => {
      try {
        const unified = await api.getUnifiedCheckoutSession(sessionId);
        if (!cancelled) {
          setSession(unified);
          if (unified.data.session?.status === 'complete')
            window.location.href = '/success';
        }
      } catch (err) {
        if (!cancelled)
          setError(err instanceof Error ? err.message : 'Failed to load session');
      } finally {
        if (!cancelled) setIsLoading(false);
      }
    })();
    return () => { cancelled = true; };
  }, [sessionId, api]);
 
  // Countdown timer
  useEffect(() => {
    const timer = setInterval(() => {
      setCountdown((prev) => {
        if (prev <= 1) { clearInterval(timer); return 0; }
        return prev - 1;
      });
    }, 1000);
    return () => clearInterval(timer);
  }, []);
 
  const handleComplete = useCallback((result: PaymentResult) => {
    if (result.status === 'succeeded') window.location.href = '/success';
  }, []);
 
  const formatCountdown = (seconds: number) => {
    const mins = Math.floor(seconds / 60);
    const secs = seconds % 60;
    return `${mins}:${secs.toString().padStart(2, '0')}`;
  };
 
  if (!sessionId) return <p>No session ID. Browse products first.</p>;
  if (isLoading) return <p>Loading...</p>;
  if (!session?.data.session) return <p>Checkout Error: {error}</p>;
 
  const cs = session.data.session;
  const displayData = buildCheckoutDisplayData(cs);
  const { items, currency, total, totalSave, discountPercent } = displayData;
  const fmt = (n: number) =>
    new Intl.NumberFormat('en-US', { style: 'currency', currency }).format(n);
 
  return (
    <div style={{
      background: 'white', maxWidth: 526, margin: '0 auto',
      borderRadius: '16px', boxShadow: '0 0 34px rgba(43, 51, 67, 0.12)',
      overflow: 'hidden',
    }}>
      {/* Title */}
      <div style={{ textAlign: 'center', padding: '1rem 1.5rem 0.25rem' }}>
        <p style={{ fontSize: '1.5rem', fontWeight: 600 }}>Safe & Secure Checkout</p>
      </div>
 
      <div style={{ padding: '0.25rem 1rem 1.5rem', display: 'flex', flexDirection: 'column', gap: '1.25rem' }}>
        {/* Discount timer */}
        {countdown > 0 && totalSave > 0 && (
          <div style={{
            background: '#fef2f2', borderRadius: '8px', padding: '0.5rem',
            textAlign: 'center', color: '#991b1b',
          }}>
            Your Discount Reserved for <strong>{formatCountdown(countdown)}</strong>
          </div>
        )}
 
        {/* Error */}
        {error && (
          <div style={{ background: '#FAECE9', borderRadius: '8px', padding: '0.5rem 0.75rem', color: '#C72B23' }}>
            Error: {error}
          </div>
        )}
 
        {/* Order summary */}
        <div>
          {items.map((item, i) => (
            <div key={i} style={{ display: 'flex', justifyContent: 'space-between', padding: '0 0.75rem', marginBottom: '0.5rem' }}>
              <span>{item.name.toUpperCase()}</span>
              <span style={{ color: '#A8A9AD' }}>{fmt(item.originalPrice)}</span>
            </div>
          ))}
 
          {totalSave > 0 && (
            <div style={{ background: '#EFF9F0', borderRadius: '8px', padding: '0.25rem 0.5rem', display: 'flex', justifyContent: 'space-between', marginBottom: '0.5rem' }}>
              <span style={{ color: '#7DAD3A', fontSize: '0.8rem', fontWeight: 500 }}>{discountPercent}% DISCOUNT APPLIED</span>
              <span style={{ color: '#7DAD3A' }}>-{fmt(totalSave)}</span>
            </div>
          )}
 
          <div style={{ borderTop: '1px solid #A8A9AD', margin: '0.5rem 0' }} />
 
          <div style={{ display: 'flex', justifyContent: 'space-between' }}>
            <p style={{ fontSize: '1.5rem', fontWeight: 600 }}>Total Due Today:</p>
            <p style={{ fontSize: '1.5rem', fontWeight: 600 }}>{fmt(total)}</p>
          </div>
        </div>
 
        {/* FloPayCheckout handles everything */}
        <FloPayCheckout
          sessionId={sessionId}
          checkoutMode={modeParam ?? undefined}
          onComplete={handleComplete}
          onError={(err) => setError(err.message)}
        />
      </div>
    </div>
  );
}
 
export default function CheckoutPage() {
  return (
    <Suspense fallback={<p>Loading...</p>}>
      <CheckoutContent />
    </Suspense>
  );
}

With Buttons Layout

<FloPayCheckout
  sessionId={sessionId}
  layout="buttons"
  buttonsTheme="rounded"
  onComplete={handleComplete}
  onError={(err) => setError(err.message)}
/>

With Confirm Mode

<FloPayCheckout
  sessionId={sessionId}
  checkoutMode="confirm"
  renderConfirmButton={({ onConfirm, isProcessing }) => (
    <button
      onClick={onConfirm}
      disabled={isProcessing}
      style={{
        width: '100%', padding: '1rem',
        backgroundColor: '#4A49FF', color: 'white',
        border: 'none', borderRadius: '8px',
        fontWeight: 600, cursor: isProcessing ? 'not-allowed' : 'pointer',
      }}
    >
      {isProcessing ? 'Processing...' : 'Confirm Purchase'}
    </button>
  )}
  onComplete={handleComplete}
/>

Key Points

  • configureFlopay() is called once at module load — not per render
  • buildCheckoutDisplayData() formats session data for display (handles discounts, trial naming, item hiding)
  • FloPayCheckout automatically reads email, userId, amount, and currency from the session
  • Error suppression: pass error={() => <></>} to handle errors entirely via onError
  • The component handles 3DS, PayPal redirects, and wallet payments internally

On this page