Headless primitives for building multi-step checkout on top of Stripe. The library owns the state machine, session lifecycle, storage, routing, and the default Stripe surface — your app composes the UI (cart, address, review, confirmation, custom steps).
Our Storybook Docs are the best place to explore behavior, adapters, and full integration recipes.
npm i @constructor-io/constructorio-ui-checkout| Path | Best for | Entry |
|---|---|---|
| React | React apps with the full flow | <CioCheckoutProvider> + useCioCheckout() |
| Vanilla JS (npm) | Non-React SPAs or custom UIs | createCheckoutFlow(config) |
| Standalone bundle | Server-rendered sites, <script> |
window.CioCheckout.resume(config) |
All three wrap the framework-agnostic createCheckoutFlow(config) core.
import {
CioCheckoutProvider,
CioCheckoutStep,
CioStripePaymentStep,
CioFulfillmentStep,
PAYMENT_STEP,
useCioCheckout,
} from '@constructor-io/constructorio-ui-checkout';
import '@constructor-io/constructorio-ui-checkout/styles.css';
function App() {
return (
<CioCheckoutProvider
provider="stripe"
steps={[
{ id: 'cart' },
{ id: 'address' },
{ id: PAYMENT_STEP },
{ id: 'fulfill' },
{ id: 'done' },
]}
onCreateSession={async () => {
const res = await fetch('/api/checkout-session', { method: 'POST' });
return res.json(); // must return { clientSecret, publishableKey }
}}
onEvent={(event) => console.log(event)}
>
<StartButton />
<CioCheckoutStep id="cart">
<MyCart />
</CioCheckoutStep>
<CioCheckoutStep id="address">
<MyAddressForm />
</CioCheckoutStep>
<CioCheckoutStep id={PAYMENT_STEP}>
<CioStripePaymentStep uiMode="form" />
</CioCheckoutStep>
<CioCheckoutStep id="fulfill">
<CioFulfillmentStep
onFulfill={() => fetch('/api/verify').then((r) => r.json())}
/>
</CioCheckoutStep>
<CioCheckoutStep id="done">
<MyConfirmation />
</CioCheckoutStep>
</CioCheckoutProvider>
);
}
function StartButton() {
const flow = useCioCheckout();
if (flow.state.currentStepId !== null) return null;
return <button onClick={() => flow.start()}>Checkout</button>;
}You own the button, the layout, and every non-Stripe step. See the Integration Guide for routed multi-step, modal presentation, guards, guest vs. login, and mid-flow cart updates.
A framework-agnostic bundle for non-React SPAs, server-rendered sites, or <script>-tag integrations. The bundle exposes window.CioCheckout — a state-only namespace; you drive your own DOM.
<script src="/path/to/constructorio-ui-checkout.standalone.js"></script>
<script>
const flow = CioCheckout.resume({
steps: [{ id: 'cart' }, { id: 'payment' }, { id: 'done' }],
onCreateSession: () =>
fetch('/api/checkout-session', { method: 'POST' }).then((r) => r.json()),
onEvent: (e) => console.log(e),
});
flow.start();
</script>Consuming through a bundler? Import the same bundle via subpath — same CioCheckout surface:
import CioCheckout from '@constructor-io/constructorio-ui-checkout/constructorio-ui-checkout-standalone';CioCheckout.resume(config) creates a checkout flow backed by a sessionStorage adapter (auto-hydrates on page load) and registers it as the active flow so any other CIO library on the page can reach it.
| Member | Description |
|---|---|
CioCheckout.VERSION |
Library version string |
CioCheckout.createCheckoutFlow |
Factory returning the framework-agnostic checkout flow |
CioCheckout.createSessionStorageAdapter |
Default sessionStorage-backed CheckoutStorageAdapter |
CioCheckout.resume(config) |
Creates + registers a flow with the sessionStorage adapter injected |
CioCheckout.reset() |
Destroys the registered flow |
CioCheckout.cioCheckoutRegistry |
Singleton registry — register(flow), getFlow(), hasFlow(), clear() |
For multi-library setups where another CIO library (e.g. pia) needs to reach the active checkout flow, register once and let others call getFlow():
import {
createCheckoutFlow,
cioCheckoutRegistry,
} from '@constructor-io/constructorio-ui-checkout';
const flow = createCheckoutFlow({ provider: 'stripe', steps, onCreateSession, ... });
cioCheckoutRegistry.register(flow);
// Elsewhere:
const active = cioCheckoutRegistry.getFlow();
active?.syncCart(newItems);Persistence is opt-in. Pass a storage adapter + storageKey to CioCheckoutProvider (or createCheckoutFlow) and the library serializes CheckoutFlowState on every change and hydrates on next mount:
import { createSessionStorageAdapter } from '@constructor-io/constructorio-ui-checkout';
<CioCheckoutProvider
storage={createSessionStorageAdapter()}
storageKey={`checkout-${userId}`}
...
/>Supply your own adapter (Redis, DynamoDB, your API) for B2B pause/resume, cross-device recovery, or emailed abandoned-cart links. See Persistence & Resume for the full recipe.
Note: Local development requires Node.js >= 20. The consuming library supports Node.js >= 18, but the Storybook 9 toolchain requires Node.js >= 20.
npm ci # install dependencies for local dev
npm run dev # start a local Storybook dev server
npm run lint # run linter
npm run lint:fix # run linter with auto-fix
npm run check-types # run TypeScript type checking
npm run test # run tests
npm run test:coverage # run tests with coverage report
npm run check-license # check dependency licenses
npm run build # build the library
npm run build-storybook # build Storybook for deploymentYour backend creates the Stripe Checkout Session and returns { clientSecret, publishableKey } to the library. The server ui_mode must match the client uiMode you pass to <CioStripePaymentStep>: 'form' (beta) or 'elements' (GA).
const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
app.post('/api/checkout-session', async (req, res) => {
const session = await stripe.checkout.sessions.create({
ui_mode: 'form', // use 'elements' for the elements uiMode
mode: 'payment',
line_items: [
{
price_data: {
currency: 'usd',
product_data: { name: 'Product Name' },
unit_amount: 4999,
},
quantity: 1,
},
],
return_url:
'https://example.com/order-confirm?session_id={CHECKOUT_SESSION_ID}',
});
res.json({
clientSecret: session.client_secret,
publishableKey: process.env.STRIPE_PUBLISHABLE_KEY,
});
});Note: Requires
stripeSDK v18+.ui_mode: 'form'is in beta and requires Stripe to enable it on the account.
- Node.js >= 18
- React >= 16.12.0
- React DOM >= 16.12.0
- @stripe/stripe-js >= 9.3.1
- @stripe/react-stripe-js >= 6.6.0
- @constructor-io/constructorio-ui-components >= 1.4.0
- Stripe Custom Checkout (embedded components)
- Stripe Checkout Form (embedded form, beta)
- Stripe Embedded Checkout — Quickstart
- Constructor.io
Dispatch the Publish workflow in GitHub Actions. You're required to provide two arguments:
- Version Strategy:
major,minor, orpatch. - Title: A title for the release.
This workflow will automatically:
- Bump the library version using the provided strategy.
- Create a new git tag.
- Create a new GitHub release.
- Compile the library.
- Publish the new version to NPM.
- Deploy the Storybook docs to GitHub Pages.
The library version is tracked by releases and git tags. This intentionally avoids pushing version bumps to the main branch, sidestepping branch-protection rule exceptions.
- Fork the repo and create a new branch.
- Run
npm cito install dependencies. - Make your changes.
- Run
npm run lintandnpm testto verify. - Submit a PR for review.
Please avoid committing anything sensitive — API keys, Stripe secret keys, customer data, or internal URLs. The default .gitignore excludes .env* files; keep it that way.
MIT - Constructor.io