@flopay/js
v1.2.8
Published
Browser-side FloPay SDK. Provides `loadFloPay()` to initialize the SDK, a `FloPay` class for managing payment elements and confirmations, `PaymentAPI` for billing API calls, and `createCheckoutSession` for server-initiated checkout flows.
Readme
@flopay/js
Browser-side FloPay SDK. Provides loadFloPay() to initialize the SDK, a FloPay class for managing payment elements and confirmations, PaymentAPI for billing API calls, and createCheckoutSession for server-initiated checkout flows.
Currently backed by Stripe via the StripeAdapter. The adapter pattern (PaymentProviderAdapter interface) allows swapping providers without changing consumer code.
Installation
pnpm add @flopay/js@stripe/stripe-js ships as a direct dependency of @flopay/js — consumers do not need to install it separately.
Quick Start
Initialize the SDK
import { loadFloPay } from '@flopay/js';
const flopay = await loadFloPay('pk_test_...', {
billingApiUrl: 'https://billing.example.com', // optional, enables retrieveSession/retrieveUnifiedSession
});loadFloPay caches the instance -- calling it again with the same key returns the cached FloPay instance. The optional second argument accepts a FloPayConfig with billingApiUrl, locale, and appearance.
Create and Mount Elements
const elements = flopay.elements({
amount: 2999, // in cents
currency: 'usd',
});
const cardElement = await elements.create('card');
cardElement.mount(document.getElementById('card-container')!);Tokenize and Confirm Payment
// 1. Validate elements
const { error: submitError } = await flopay.submitElements();
if (submitError) throw submitError;
// 2. Tokenize card into a PaymentMethod
const { paymentMethodId, error } = await flopay.createPaymentMethod();
if (error || !paymentMethodId) throw error;
// 3. Confirm card payment with a client secret from your server
const result = await flopay.confirmCardPayment({
clientSecret: 'pi_xxx_secret_yyy',
paymentMethodId,
});
if (result.error) {
console.error(result.error.message);
} else {
console.log('Payment status:', result.status);
}PayPal Payment
const result = await flopay.confirmPayPalPayment({
billingApiUrl: 'https://billing.example.com',
sessionId: 'session_uuid',
email: '[email protected]',
returnUrl: window.location.href,
});
// After redirect return, resume the payment:
const resumed = await flopay.resumePayPalPayment();
if (resumed) {
console.log('PayPal payment status:', resumed.status);
}Retrieve a Session
// Using billingApiUrl from config (set at init time):
const session = await flopay.retrieveSession('session_uuid');
console.log(session.amount, session.currency, session.status);
// Or pass billingApiUrl explicitly:
const session2 = await flopay.retrieveSession('session_uuid', 'https://billing.example.com');
// For provider-specific data (Stripe clientSecret, publishableKey, etc.):
const unified = await flopay.retrieveUnifiedSession('session_uuid');
console.log(unified.data.stripe?.clientSecret);Failed session loads reject with FloPayError. When the billing API returns a structured error body, the SDK preserves the safe message, backend code, and HTTP statusCode; body-less responses fall back to code: "http_<status>".
PaymentAPI (Billing API Client)
import { PaymentAPI } from '@flopay/js';
const api = new PaymentAPI('https://billing.example.com');
// Fetch and normalize a checkout session. `nonce` is the session-bound
// checkout token returned from session creation — required by post-#640
// backends, which match it against `checkout_session.nonce` before returning
// the row.
const session = await api.getUnifiedCheckoutSession('session_uuid', sessionNonce);
// session.provider === 'stripe' | 'chargebee' | 'recurly'
// session.data.session contains the normalized CheckoutSession
// session.data.session.clientSecret carries the same nonce so downstream
// code can re-use it.
// Create a PaymentIntent — nonce becomes the `x-checkout-session-token`
// header automatically.
const intentResponse = await api.createPaymentIntent(
'session_uuid',
'[email protected]',
'pm_xxx',
{ nonce: sessionNonce },
);
// Process a tokenized payment. `nonce` is required: the SDK throws a
// `FloPayError` with code `MissingCheckoutSessionToken` when it is missing,
// and otherwise POSTs to `/v1/checkouts/sessions/<id>/process` with the
// header set.
const processResponse = await api.processPayment('user_id', {
sessionId: 'session_uuid',
nonce: sessionNonce,
tokenizedData: { id: 'pm_xxx', type: 'card' },
accountData: { userId: 'user_id', email: '[email protected]', firstName: 'John', lastName: 'Doe' },
});
// If the backend returns `202 checkout_processing`, the SDK keeps polling the
// checkout session until it completes or times out, then resolves/rejects.
// Check for prior payments (saved card UX)
const payments = await api.getPaymentsByEmail('[email protected]');createCheckoutSession
import { createCheckoutSession } from '@flopay/js';
const result = await createCheckoutSession({
billingApiUrl: 'https://billing.example.com',
checkoutBaseUrl: 'https://checkout.example.com',
clientId: 'client_123',
items: [{
providerItemId: 'prod_abc',
providerItemName: 'Pro Plan',
totalAmount: 49.99,
overrideAmount: 24.99,
}],
account: { userId: 'user_1', email: '[email protected]' },
successUrl: '/success',
cancelUrl: '/cancel',
redirectParams: { email: '[email protected]', bg: 'courses', mode: 'confirm' },
});
// On 201: browser redirects to checkout page. The resolved
// `CheckoutSessionResult` is `{ status: 201, redirectUrl, nonce }` — `nonce` is
// the session-bound checkout token returned by the billing API. A matching
// `flopay_checkout_token` cookie is written to the same parent domain as
// `checkout_data`, so a hosted-checkout page can read the token from
// `document.cookie` and forward it on continuation calls without having to
// pull it from the URL (kept out of history/Referer).
// On 204: browser redirects to successUrl (payment method on file)Use createCheckoutSessionWithRetries for automatic retry with exponential backoff on timeout errors.
Coupon validation errors are surfaced as FloPayError with structured code:
CouponLimitExceeded— more than 5 coupon codes supplied (also enforced client-side before the request leaves the browser).CouponCurrencyUnsupported— an amount-based coupon has no price for the cart currency.
import { FloPayError } from '@flopay/shared';
try {
await createCheckoutSession({ /* … */ couponCodes });
} catch (err) {
if (err instanceof FloPayError) {
if (err.code === 'CouponLimitExceeded') {
// show "Too many coupons" toast
} else if (err.code === 'CouponCurrencyUnsupported') {
// show "Coupon is not valid for this currency" toast
}
}
}API Reference
Exports
| Export | Description |
|--------|-------------|
| loadFloPay(publishableKey, options?) | Initializes the SDK. Returns a Promise<FloPay>. Caches by key. |
| FloPay | Main SDK class. Methods: elements(), submitElements(), createPaymentMethod(), confirmCardPayment(), confirmPayment(), confirmPayPalPayment(), resumePayPalPayment(), retrieveSession(), retrieveUnifiedSession(), getRawProvider(), destroy() |
| FloPayElements | Element group manager. Methods: create(type, options?), getElement(type), submit(), destroy() |
| StripeAdapter | PaymentProviderAdapter implementation for Stripe |
| PaymentAPI | Billing API client. Methods: getCheckoutSession(), getUnifiedCheckoutSession(), processPayment(), waitForCheckoutSessionCompletion(), createPaymentIntent(), createSetupIntent(), getPaymentsByEmail() |
| createCheckoutSession(options) | Creates a checkout session and redirects. Returns CheckoutSessionResult. |
| createCheckoutSessionWithRetries(options) | Same as above with automatic retries (default 3, exponential backoff). |
FloPay Class Methods
| Method | Returns | Description |
|--------|---------|-------------|
| elements(options?) | FloPayElements | Creates a new elements group. Destroys previous group. |
| submitElements() | Promise<{ error? }> | Validates all mounted elements |
| createPaymentMethod() | Promise<CreatePaymentMethodResult> | Tokenizes card fields into a pm_xxx ID. Auto-detects split fields vs unified PaymentElement. |
| confirmCardPayment(params) | Promise<ConfirmCardPaymentResult> | Confirms with clientSecret + paymentMethodId. Handles 3DS. |
| confirmPayment(params) | Promise<PaymentResult> | Confirms using mounted elements + clientSecret |
| confirmPayPalPayment(params) | Promise<ConfirmCardPaymentResult> | Full PayPal flow: create PM -> create intent -> confirm/redirect |
| resumePayPalPayment() | Promise<ConfirmCardPaymentResult \| null> | Resumes after PayPal redirect. Returns null if no PayPal params in URL. |
| retrieveSession(sessionId, billingApiUrl?) | Promise<CheckoutSession> | Retrieves a checkout session by ID via GET /v1/checkouts/sessions/{id}. Uses billingApiUrl from config or the optional second argument. |
| retrieveUnifiedSession(sessionId, billingApiUrl?) | Promise<NormalizedCheckoutSession> | Retrieves and normalizes a checkout session, including provider-specific data (Stripe clientSecret/publishableKey, etc.). |
| getRawProvider() | unknown | Returns the raw underlying provider instance (e.g. Stripe object) |
| destroy() | void | Tears down elements and provider |
Supported Element Types
payment-- Unified PaymentElement (cards, wallets, etc.)card-- Combined card input (number + expiry + CVC)cardNumber-- Card number field (for split card forms)cardExpiry-- Card expiry fieldcardCvc-- Card CVC fieldaddress-- Address input element
