React integration
Add PCI-friendly card collection and tokenization to your React app with the Tokeflow Bridge SDK — TokeflowProvider, hooks, and secure element components.
The Tokeflow Bridge SDK ships first-class React bindings so you can collect card data, surface merchant capabilities, and tokenize payment details without handling raw card numbers. Card data is collected inside isolated, cross-origin iframes, encrypted in the browser, and never touches your servers — your React components only ever see token references.
This page covers the React layer: the TokeflowProvider, the hooks, the element components, and a complete, TypeScript-ready payment form.
The React bindings are a thin wrapper over the core bridge. Everything you can do with the vanilla SDK is available here — the provider manages a single bridge instance and the hooks expose its state to your component tree.
How it fits together
Your React app collects card details in secure fields, tokenizes them in the browser, and sends only the resulting tokenId to your backend. Your backend then uses its secret key to create the transaction against the Tokeflow API.
Use a public key (pk_live_… / pk_test_…) in browser code. Secret keys (sk_*) are server-side only and must never appear in client-side bundles. See Environments and keys.
Installation
npm install @tokeflow_com/bridge-js
# or: yarn add @tokeflow_com/bridge-js
# or: pnpm add @tokeflow_com/bridge-jsThe React bindings live in a dedicated subpath export:
import {
TokeflowProvider,
useTokeflow,
CardElement,
} from '@tokeflow_com/bridge-js/react';Core types and the error classes come from the root export:
import { TokeflowError, TokeflowErrorCode } from '@tokeflow_com/bridge-js';Provider setup
Wrap the part of your tree that needs payments in a TokeflowProvider. The provider creates and initializes a single bridge instance (which mints a browser session and loads the secure elements runtime) and shares it via React context.
import { TokeflowProvider } from '@tokeflow_com/bridge-js/react';
export default function App() {
return (
<TokeflowProvider
config={{
publicKey: 'pk_test_your_public_key',
locale: 'pt-BR',
onError: (error) => console.error(error.code, error.message),
}}
>
<YourApp />
</TokeflowProvider>
);
}Provider config
The config prop accepts the full bridge configuration. The most relevant options for a React app:
| Field | Type | Required | Description |
|---|---|---|---|
publicKey | string | Yes | Your Tokeflow public key (pk_test_* or pk_live_*). |
environment | 'sandbox' | 'production' | No | API environment. Auto-detected from the key by default. |
checkoutSessionId | string | No | Links the bridge to a backend-created checkout session (cks_…). Required for useCheckout. |
locale | TokeflowLocale | No | Default locale for every element. Defaults to 'en'. |
translations | Partial<TokeflowTranslations> | No | Override built-in translation strings. |
elementOptions | { style?: ElementStyle; fonts?: string[] } | No | Default styling and fonts applied to every element. |
autoRefreshSession | boolean | No | Refresh the session before it expires. Defaults to true. |
timeout | number | No | Request timeout in ms. Defaults to 30000. |
onSessionCreated | (session: Session) => void | No | Called when a session is created. |
onSessionExpired | () => void | No | Called when the session expires. |
onError | (error: TokeflowError) => void | No | Global error handler. |
Mount the provider as high in your tree as makes sense (e.g. around your checkout route), but no higher — initializing the bridge mints a session, so there is no benefit to creating it before the user reaches a payment surface.
Hooks
All hooks must be called from a component rendered inside a TokeflowProvider.
| Hook | Returns | Purpose |
|---|---|---|
useTokeflow() | { tokeflow, session, isReady, error } | Access the bridge instance and readiness state. |
useCapabilities() | Capabilities | null | Merchant capabilities derived from the session. |
useSessionSettings() | SessionSettings | null | Tokenization settings (e.g. allowed card brands). |
useAvailablePaymentMethods() | AvailablePaymentMethod[] | Methods the merchant has enabled ('cards', 'pix', 'applePay', 'googlePay'). |
useCheckout() | { context, isLoading, error, refresh } | Fetch and cache the linked checkout context. |
useTokeflow
The primary hook. Gate your UI on isReady before touching the bridge, and surface error if initialization fails.
import { useTokeflow } from '@tokeflow_com/bridge-js/react';
function PaymentForm() {
const { tokeflow, session, isReady, error } = useTokeflow();
if (error) return <div role="alert">Error: {error.message}</div>;
if (!isReady) return <div>Loading payment form…</div>;
// `tokeflow` is the initialized bridge instance.
return <p>Session {session?.id} is ready.</p>;
}| Field | Type | Description |
|---|---|---|
tokeflow | TokeflowBridge | null | The bridge instance. null until the provider initializes. |
session | Session | null | The current browser session. |
isReady | boolean | true once init() has resolved and elements can be mounted. |
error | TokeflowError | null | Initialization error, if any. |
useCapabilities and useSessionSettings
Read the merchant's capabilities and the active tokenization settings (such as the allowed card brands) to drive conditional UI.
import { useCapabilities, useSessionSettings } from '@tokeflow_com/bridge-js/react';
function CardBrands() {
const capabilities = useCapabilities();
const settings = useSessionSettings();
if (!settings) return null;
return (
<ul>
{settings.allowedCardBrands?.map((brand) => (
<li key={brand}>{brand}</li>
))}
</ul>
);
}Both hooks return null until the session is available.
useAvailablePaymentMethods
Render only the payment methods the merchant has enabled.
import { useAvailablePaymentMethods } from '@tokeflow_com/bridge-js/react';
function MethodPicker() {
const methods = useAvailablePaymentMethods(); // ('cards' | 'pix' | 'applePay' | 'googlePay')[]
return (
<div>
{methods.includes('cards') && <CardForm />}
{methods.includes('pix') && <PixForm />}
{methods.includes('applePay') && <ApplePayButton />}
</div>
);
}useCheckout
When the provider is configured with a checkoutSessionId, useCheckout fetches and caches the checkout context — the priced items, the selected currency, the merchant's available payment methods, and the customer's saved instruments.
import { useCheckout } from '@tokeflow_com/bridge-js/react';
function CheckoutSummary() {
const { context, isLoading, error, refresh } = useCheckout();
if (error) return <p role="alert">{error.message}</p>;
if (isLoading || !context) return <p>Loading…</p>;
return (
<>
<ul>
{context.checkout.items.map((item) => (
<li key={item.id}>
{item.offerId} — {item.amount} {context.checkout.selectedCurrency}
</li>
))}
</ul>
<button onClick={refresh}>Refresh</button>
</>
);
}| Field | Type | Description |
|---|---|---|
context | CheckoutContext | null | The cached checkout context. null until fetched. |
isLoading | boolean | true while the context is being fetched. |
error | TokeflowError | null | Fetch error, if any. |
refresh | () => Promise<void> | Re-fetch after your backend mutates the checkout (identifies the customer, changes items, etc.). |
useCheckout requires checkoutSessionId in the provider config. Without it, the hook surfaces a NO_CHECKOUT_SESSION error. Create the checkout session on your backend with a secret key, then pass its id (cks_…) to the provider. See Checkout sessions.
Element components
Each element renders a secure, cross-origin iframe that collects one part of the card (or billing) data. The encrypted value never leaves the iframe — your component receives only change events and, eventually, a token reference.
| React component | Description |
|---|---|
<CardElement> | Number + expiry + CVC in a single iframe. |
<CardNumberElement> | Card number only. |
<CardExpirationElement> | Expiry date only. |
<CardCvcElement> | CVC/CVV only. |
<CardholderNameElement> | Name on card. |
<TextElement> | Generic text input. |
<CpfElement> | Brazilian CPF input (used for PIX). |
<AddressLine1Element> / <AddressLine2Element> | Billing address lines. |
<CityElement> / <StateElement> | Billing city / state. |
<PostalCodeElement> / <CountryElement> | Billing postal code / country. |
<ApplePayButton> / <GooglePayButton> | Wallet payment buttons. |
Common props
Element components accept the element options plus React-friendly event callbacks:
| Prop | Type | Description |
|---|---|---|
ref | Ref<CardElementRef> | Exposes the underlying element instance via ref.current.element. |
id | string | DOM id for the element container. |
className | string | CSS class on the container. |
onReady | () => void | Fires when the secure field is ready. |
onChange | (e: ElementChangeEvent) => void | Fires on every input change (complete, empty, error, …). |
onFocus / onBlur | () => void | Focus lifecycle. |
onError | (e) => void | Element-level error. |
The ref .element pattern
This is the key React idiom: every element component forwards a ref whose .element property is the underlying bridge element instance. You pass that instance — not the React component — to tokenize().
import { useRef } from 'react';
import { CardElement, CardElementRef } from '@tokeflow_com/bridge-js/react';
function CombinedCardForm() {
const cardRef = useRef<CardElementRef>(null);
return (
<CardElement
ref={cardRef}
id="card"
onReady={() => console.log('Ready')}
onChange={(e) => console.log('Complete:', e.complete, 'Error:', e.error?.message)}
className="my-card-element"
/>
);
}
// Later, on submit:
// await tokeflow.cards.tokenize({ card: cardRef.current.element });Combined vs. split fields
Use a single <CardElement>, or split the inputs across <CardNumberElement>, <CardExpirationElement>, and <CardCvcElement> for full layout control.
import { useRef } from 'react';
import {
CardNumberElement,
CardExpirationElement,
CardCvcElement,
CardNumberElementRef,
CardExpirationElementRef,
CardCvcElementRef,
} from '@tokeflow_com/bridge-js/react';
function SplitCardForm() {
const numberRef = useRef<CardNumberElementRef>(null);
const expiryRef = useRef<CardExpirationElementRef>(null);
const cvcRef = useRef<CardCvcElementRef>(null);
// On submit, pass the underlying instances — note the key is `expiration`:
// await tokeflow.cards.tokenize({
// cardNumber: numberRef.current.element,
// expiration: expiryRef.current.element,
// cvc: cvcRef.current.element,
// });
return (
<>
<CardNumberElement ref={numberRef} id="number" />
<CardExpirationElement ref={expiryRef} id="expiry" />
<CardCvcElement ref={cvcRef} id="cvc" />
</>
);
}With split elements the expiration key is expiration (not expirationDate). With the combined element you pass a single card key.
Tokenization
tokeflow.cards.tokenize(input, options?) encrypts the collected card data and resolves to a CardToken. Send only token.tokenId to your backend — never any raw card field.
const token = await tokeflow.cards.tokenize({
card: cardRef.current.element,
});
// token.tokenId -> 'tok_…'The token shape:
interface CardToken {
tokenId: string; // your Tokeflow token reference (tok_…)
type: 'card';
brand: string; // 'visa', 'mastercard', etc.
last4: string; // last 4 digits
expiryMonth: string; // e.g. '12'
expiryYear: string; // e.g. '2029'
bin?: string; // bank identification number (optional)
createdAt?: string; // ISO 8601 timestamp (optional)
}You may attach metadata at tokenization time:
const token = await tokeflow.cards.tokenize(
{ card: cardRef.current.element },
{ metadata: { cartId: 'cart_123' } }
);Full payment form example
A complete, TypeScript-friendly form with readiness gating, inline validation, loading state, and a POST to your backend (which then creates the transaction with its secret key).
import React, { useRef, useState, FormEvent } from 'react';
import {
TokeflowProvider,
useTokeflow,
CardElement,
CardElementRef,
} from '@tokeflow_com/bridge-js/react';
import { TokeflowError } from '@tokeflow_com/bridge-js';
function PaymentForm() {
const { tokeflow, isReady } = useTokeflow();
const cardRef = useRef<CardElementRef>(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const handleSubmit = async (e: FormEvent) => {
e.preventDefault();
if (!tokeflow || !cardRef.current?.element) return;
setLoading(true);
setError(null);
try {
// 1. Tokenize in the browser — raw card data never reaches your servers.
const token = await tokeflow.cards.tokenize({
card: cardRef.current.element,
});
// 2. Send ONLY the token reference to your backend.
const res = await fetch('/api/payments', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ tokenId: token.tokenId }),
});
if (!res.ok) throw new Error('Payment could not be completed.');
alert('Payment successful!');
} catch (err) {
if (err instanceof TokeflowError) {
// Field-level error from the secure element / tokenization.
setError(err.message);
} else {
setError(err instanceof Error ? err.message : 'Unexpected error.');
}
} finally {
setLoading(false);
}
};
if (!isReady) return <div>Loading payment form…</div>;
return (
<form onSubmit={handleSubmit}>
<div className="form-group">
<label htmlFor="card-element">Card details</label>
<CardElement
ref={cardRef}
id="card-element"
onChange={(e) => setError(e.error?.message ?? null)}
/>
</div>
{error && (
<div className="error" role="alert">
{error}
</div>
)}
<button type="submit" disabled={loading}>
{loading ? 'Processing…' : 'Pay now'}
</button>
</form>
);
}
export default function App() {
return (
<TokeflowProvider config={{ publicKey: 'pk_test_your_public_key' }}>
<PaymentForm />
</TokeflowProvider>
);
}On the server, your /api/payments handler receives the tokenId and creates the charge with your secret key:
curl -X POST https://api.tokeflow.com/api/v1/transactions \
-H "Authorization: Bearer sk_live_mer_your_secret_key" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 5f1d9c2e-7a3b-4c8e-9f21-0a1b2c3d4e5f" \
-d '{
"amount": 9900,
"currency": "BRL",
"payment_method": "credit_card",
"card_ciphertext_id": "tok_8f3c2a1b"
}'{
"success": true,
"data": {
"id": "tx_7c9a1f2b",
"status": "authorized",
"amount": 9900,
"currency": "BRL",
"payment_method": "credit_card",
"created_at": "2026-01-15T12:30:00.000Z"
},
"request_id": "req_8f3c2a1b",
"timestamp": "2026-01-15T12:30:00.000Z"
}Never call the Tokeflow API directly from the browser with a secret key. The browser tokenizes; your server charges. See Authentication.
Error handling
Tokenization and element operations reject with a TokeflowError. Branch on its machine-readable code (a TokeflowErrorCode) to render friendly messages.
import { TokeflowError, TokeflowErrorCode } from '@tokeflow_com/bridge-js';
try {
const token = await tokeflow.cards.tokenize({ card: cardRef.current.element });
} catch (err) {
if (err instanceof TokeflowError) {
switch (err.code) {
case TokeflowErrorCode.INVALID_CARD:
setError('Please check your card number.');
break;
case TokeflowErrorCode.EXPIRED_CARD:
setError('This card has expired.');
break;
case TokeflowErrorCode.ELEMENT_INCOMPLETE:
setError('Please complete all card fields.');
break;
case TokeflowErrorCode.UNSUPPORTED_CARD_BRAND:
setError('This card brand is not supported.');
break;
case TokeflowErrorCode.NETWORK_ERROR:
setError('Network error. Please try again.');
break;
default:
setError(err.message);
}
console.log('code:', err.code, 'requestId:', err.requestId);
}
}Common codes you will handle in a React form: ELEMENT_INCOMPLETE, ELEMENT_INVALID, INVALID_CARD, EXPIRED_CARD, UNSUPPORTED_CARD_BRAND, TOKENIZATION_FAILED, NETWORK_ERROR, RATE_LIMITED, and SERVER_ERROR. TokeflowError also exposes httpStatus?, requestId?, and details? for diagnostics.
Set a global onError handler in the provider config to forward errors (code, message, requestId) to your own logging service in one place.
TypeScript support
The SDK ships full type definitions. React-specific ref types come from the /react subpath:
import type {
CardElementRef,
CardNumberElementRef,
CardExpirationElementRef,
CardCvcElementRef,
} from '@tokeflow_com/bridge-js/react';
import type {
Session,
CardToken,
TokenizeOptions,
ElementStyle,
ElementState,
ElementChangeEvent,
CardElementOptions,
CheckoutContext,
} from '@tokeflow_com/bridge-js';Next steps
- Authentication — public vs. secret keys and how to charge from your backend.
- Checkout sessions — back the
useCheckouthook with a server-created session. - Transactions — create the charge from your backend using the
tokenId. - Idempotency and rate limits — make your charge requests safe to retry.