DevelopersTools
React
Open Fianto checkout from React with the FiantoButton component or the useCheckout hook, in client components on React 19.
@fianto/react wraps @fianto/js for React: FiantoButton is the branded
pay button, and useCheckout tracks one checkout in React state for a button you design yourself.
There is no provider and no key to configure: your server creates the session.
Before you start
- React 19 or later (the peer dependency).
- A checkout route on your server that answers
{ id, url }, such asCheckout()from@fianto/nextjs. - The page with the button is served from the same origin as the session's
success_url.
Steps
Install the package
pnpm add @fianto/reactIt installs @fianto/js with it, and re-exports fetchCheckoutSession and the error classes, so
you import everything from @fianto/react.
Render FiantoButton in a client component
Every file the package ships starts with 'use client', so you can import it from a Server
Component module. The component that renders the button and handles its callbacks must be a client
component itself.
// app/checkout-button.tsx
'use client';
import { FiantoButton, fetchCheckoutSession } from '@fianto/react';
import { useState } from 'react';
const MESSAGES: Record<string, string> = {
succeeded: 'Thanks! We are confirming your payment.',
canceled: 'Checkout canceled.',
expired: 'That checkout link expired. Check your order status before trying again.',
closed: 'We could not tell what happened. Check your order status before paying again.',
};
export function CheckoutButton({ orderId }: { orderId: string }) {
const [message, setMessage] = useState<string | null>(null);
return (
<div>
<FiantoButton
session={() => fetchCheckoutSession('/api/checkout', { body: { orderId } })}
theme="brand"
label="pay"
onResult={(result) => setMessage(MESSAGES[result.status] ?? null)}
onError={(error) => console.error(error)} // the route's message is for you
/>
{message ? <p role="status">{message}</p> : null}
</div>
);
}The button renders plain React markup, so the server and the client render the same DOM. When your
route refuses, it shows the payer a short line chosen by the error's code and passes the full error
to onError. A click while a checkout is open brings its popup to the front.
| Prop | Type | Default |
|---|---|---|
session | { id, url } or () => Promise<{ id, url }> (required) | — |
theme | 'brand' | 'dark' | 'light' | 'outline' | 'auto' | 'brand' |
label | 'plain' | 'pay' | 'buy' | 'checkout' | 'subscribe' | 'donate' | 'pay' |
shape | 'rect' | 'rounded' | 'pill' | 'rounded' |
size | 'static' | 'fill' | 'static' |
locale | 'en' | 'vi' | en on the server, then the browser's language after mount |
fallback | 'redirect' | 'none' | 'redirect' |
loading, disabled | boolean | false |
onResult | (result: CheckoutResult) => void | — |
onError | (error: Error) => void | — |
className, style | as on any element | — |
ref reaches the underlying <button>. Pass locale when the server must render the same language
the visitor sees; without it, a non-English visitor sees English until the component mounts.
Or build your own button with useCheckout
// app/pay-link.tsx
'use client';
import { fetchCheckoutSession, useCheckout } from '@fianto/react';
export function PayLink({ orderId }: { orderId: string }) {
const { open, focus, isOpen, result, error } = useCheckout({
session: () => fetchCheckoutSession('/api/checkout', { body: { orderId } }),
});
return (
<div>
{/* Call open() directly in the click handler, never after an await. */}
<button onClick={() => (isOpen ? focus() : void open())} aria-busy={isOpen}>
{isOpen ? 'Checkout open…' : 'Pay'}
</button>
{result ? <p role="status">Result: {result.status}</p> : null}
{error ? <p role="alert">Checkout could not be started. Please try again.</p> : null}
</div>
);
}useCheckout({ session, fallback? }) returns:
| Field | Type |
|---|---|
open | Returns a promise of the result, or of undefined. Never throws: on failure it sets error and resolves undefined. |
focus | () => boolean. Brings the open popup to the front; false when there is none. |
status | 'idle' | 'open' | 'done' | 'error' |
result | The last CheckoutResult, or null |
error | The last error, or null. A CheckoutSessionError with a code when your route refused. |
isOpen | status === 'open' |
Only the latest open() drives the state. Calling it again takes over the popup, and the earlier
checkout resolves closed with reason superseded.
Read the result safely
result.status is succeeded, canceled, expired or closed, the same as
openCheckout, with a reason on closed.
Fulfil from Fianto, not from the button
succeeded means the payer's transaction was confirmed on the checkout page, not that the order is
paid. Mark it paid only from a verified order.paid webhook or a server-side retrieve.
closed means unknown
closed tells you nothing about the payment: one may still be confirming. With reason
unreachable, checkout may still be open. Ask the payer to check their order status first.
Check it worked
Click the button. A popup opens Fianto's checkout page for the session your route created. Close the popup after the page has loaded: onResult receives closed
with reason closed_by_payer.
Troubleshooting
| You see | Why | Fix |
|---|---|---|
| The popup is blocked | open() ran after an await, a setTimeout or inside a useEffect. | Call open() directly in the click handler. |
closed with reason unreachable right away | The page sends Cross-Origin-Opener-Policy: same-origin. | Send same-origin-allow-popups on that page. |
Next.js errors when a Server Component renders FiantoButton or calls useCheckout | session, onResult and onError are functions, and hooks run only in client components. | Move that code into a file that starts with 'use client'. |
error.code is internal_error | Fianto refused for a reason your route does not pass to the browser. | Read what the route's onError logged on your server. |
| The button shows English first, then another language | No locale prop: the server renders en and the browser language applies after mount. | Pass locale when the server knows the language. |
See also
Was this page helpful? Tell us
Browser (@fianto/js)
Open Fianto checkout from the browser with openCheckout, redirectToCheckout or the <fianto-button> element, and read its four result statuses.
CLI
Every Fianto CLI command and flag - whoami, events list, get and tail, trigger and sign - with the environment variables and exit codes.