@commercengine/checkout
v0.1.6
Published
Universal checkout SDK for Commerce Engine - works with React, Vue, Svelte, Solid, and vanilla JS
Maintainers
Readme
@commercengine/checkout
Universal checkout SDK for Commerce Engine. Works with React, Vue, Svelte, Solid, and vanilla JavaScript.
Features
- Universal - One package for all frameworks
- No Provider Required - Initialize once, use anywhere
- Reactive State - Automatic UI updates when cart changes
- Type Safe - Full TypeScript support
- Tree Shakeable - Only bundle what you use
Architecture
Designed for large applications with multiple routes (Next.js, Remix, etc.):
- Singleton Store - One checkout instance shared across all components and routes
- Non-blocking Init -
initCheckout()returns immediately, iframe loads in background - SSR Safe - Initialization only runs on client, server renders with default state
- Zero Core Web Vitals Impact:
- LCP: Iframe is hidden, doesn't affect largest contentful paint
- INP/FID: No main thread blocking during initialization
- CLS: Fixed positioning, no layout shifts
┌─────────────────────────────────────────────────────────────┐
│ App Root (layout.tsx / _app.tsx) │
│ └─ initCheckout() ──► Creates hidden iframe (async) │
├─────────────────────────────────────────────────────────────┤
│ Any Component (header, product page, cart page...) │
│ └─ useCheckout() ──► Same singleton store │
├─────────────────────────────────────────────────────────────┤
│ Route Changes (SPA navigation) │
│ └─ Checkout instance persists, no re-initialization │
└─────────────────────────────────────────────────────────────┘Installation
npm install @commercengine/checkout
# or
pnpm add @commercengine/checkout
# or
yarn add @commercengine/checkoutQuick Start
React / Next.js / Remix
import { useCheckout, initCheckout, destroyCheckout } from "@commercengine/checkout/react";
import { useEffect } from "react";
// Initialize once at app root
function App({ children }) {
useEffect(() => {
initCheckout({
storeId: "store_xxx",
apiKey: "ak_xxx",
onComplete: (order) => {
console.log("Order placed:", order.orderNumber);
},
});
return () => destroyCheckout();
}, []);
return <>{children}</>;
}
// Use anywhere - no provider needed
function Header() {
const { cartCount, openCart, isReady } = useCheckout();
return (
<button onClick={openCart} disabled={!isReady}>
Cart ({cartCount})
</button>
);
}
function ProductCard({ product }) {
const { addToCart } = useCheckout();
return (
<button onClick={() => addToCart(product.id, product.variantId, 1)}>
Add to Cart
</button>
);
}
// User state is also reactive
function UserGreeting() {
const { isLoggedIn, user } = useCheckout();
if (!isLoggedIn) return <span>Guest</span>;
return <span>Welcome, {user?.firstName || user?.email}</span>;
}Vue / Nuxt
<script setup>
import { useCheckout, initCheckout, destroyCheckout } from "@commercengine/checkout/vue";
import { onMounted, onUnmounted } from "vue";
// Initialize once at app root (App.vue or plugin)
onMounted(() => {
initCheckout({
storeId: "store_xxx",
apiKey: "ak_xxx",
});
});
onUnmounted(() => {
destroyCheckout();
});
// Use anywhere
const { cartCount, openCart, isReady } = useCheckout();
</script>
<template>
<button @click="openCart" :disabled="!isReady">
Cart ({{ cartCount }})
</button>
</template>Svelte / SvelteKit
<script>
import { checkout, initCheckout, destroyCheckout } from "@commercengine/checkout/svelte";
import { onMount, onDestroy } from "svelte";
// Initialize once at app root (+layout.svelte)
onMount(() => {
initCheckout({
storeId: "store_xxx",
apiKey: "ak_xxx",
});
});
onDestroy(() => {
destroyCheckout();
});
</script>
<button on:click={() => $checkout.openCart()} disabled={!$checkout.isReady}>
Cart ({$checkout.cartCount})
</button>Or use individual stores for fine-grained reactivity:
<script>
import { cartCount, isReady } from "@commercengine/checkout/svelte";
</script>
<span class="badge">{$cartCount}</span>Solid / SolidStart
import { useCheckout, initCheckout, destroyCheckout } from "@commercengine/checkout/solid";
import { onMount, onCleanup } from "solid-js";
// Initialize once at app root
function App(props) {
onMount(() => {
initCheckout({
storeId: "store_xxx",
apiKey: "ak_xxx",
});
});
onCleanup(() => {
destroyCheckout();
});
return <>{props.children}</>;
}
// Use anywhere
function Header() {
const checkout = useCheckout();
return (
<button onClick={() => checkout.openCart()} disabled={!checkout.isReady}>
Cart ({checkout.cartCount})
</button>
);
}Vanilla JavaScript
import { initCheckout, getCheckout, subscribeToCheckout } from "@commercengine/checkout";
// Initialize
initCheckout({
storeId: "store_xxx",
apiKey: "ak_xxx",
onComplete: (order) => {
window.location.href = `/thank-you?order=${order.orderNumber}`;
},
});
// Use anywhere
document.getElementById("cart-btn").onclick = () => {
getCheckout().openCart();
};
// Subscribe to cart updates
subscribeToCheckout(
(state) => state.cartCount,
(count) => {
document.getElementById("cart-badge").textContent = count.toString();
}
);Configuration
Pass configuration to initCheckout():
initCheckout({
// Required
storeId: "store_xxx",
apiKey: "ak_xxx",
// Environment (default: "production")
environment: "production", // or "staging"
// Or use custom URL for local development
url: "http://localhost:8080",
// Theme (default: "system")
theme: "light", // or "dark" or "system"
// Appearance
appearance: {
zIndex: 99999,
},
// Authentication
authMode: "managed", // or "provided"
accessToken: "...", // if user already logged in
refreshToken: "...",
// Quick Buy (add product on init)
quickBuy: {
productId: "prod_xxx",
variantId: "var_xxx", // or null
quantity: 1,
},
sessionMode: "continue-existing", // or "force-new"
autoDetectQuickBuy: false, // parse URL params
// Features
features: {
loyalty: true, // Loyalty points redemption (default: true)
coupons: true, // Coupon/promo codes (default: true)
collectInStore: false, // Collect-in-store option (default: false)
freeShippingProgress: true, // Free shipping progress tracker (default: true)
productRecommendations: true, // Product recommendations in cart (default: true)
},
// Callbacks
onReady: () => {},
onOpen: () => {},
onClose: () => {},
onComplete: (order) => {},
onCartUpdate: (cart) => {},
onLogin: (data) => {},
onLogout: (data) => {},
onTokenRefresh: (data) => {},
onSessionError: () => {},
onError: (error) => {},
});API Reference
Initialization
| Function | Description |
|----------|-------------|
| initCheckout(config) | Initialize checkout with configuration |
| destroyCheckout() | Destroy checkout instance and cleanup |
Hooks / Composables
| Framework | Import |
|-----------|--------|
| React | useCheckout() from @commercengine/checkout/react |
| Vue | useCheckout() from @commercengine/checkout/vue |
| Svelte | checkout store from @commercengine/checkout/svelte |
| Solid | useCheckout() from @commercengine/checkout/solid |
Return Values
| Property | Type | Description |
|----------|------|-------------|
| isReady | boolean | Whether checkout is ready to use |
| isOpen | boolean | Whether checkout overlay is open |
| cartCount | number | Number of items in cart |
| cartTotal | number | Cart subtotal amount |
| cartCurrency | string | Currency code (e.g., "USD") |
| isLoggedIn | boolean | Whether user is logged in |
| user | UserInfo \| null | Current user info (null if not logged in) |
| Method | Description |
|--------|-------------|
| openCart() | Open the cart drawer |
| openCheckout() | Open checkout directly (Buy Now flow) |
| close() | Close the checkout overlay |
| addToCart(productId, variantId, quantity?) | Add item to cart |
| updateTokens(accessToken, refreshToken?) | Update auth tokens |
Vanilla JS Helpers
| Function | Description |
|----------|-------------|
| getCheckout() | Get current state and methods (non-reactive) |
| subscribeToCheckout(listener) | Subscribe to all state changes |
| subscribeToCheckout(selector, listener) | Subscribe to specific state |
| checkoutStore | Direct access to Zustand store |
TypeScript
All types are exported for convenience:
import type {
// Configuration
CheckoutConfig,
CheckoutFeatures,
Environment,
AuthMode,
SessionMode,
QuickBuyConfig,
// State
CartData,
OrderData,
UserInfo,
ErrorData,
// Auth events
AuthLoginData,
AuthLogoutData,
AuthRefreshData,
// Store types
CheckoutState,
CheckoutActions,
CheckoutStore,
} from "@commercengine/checkout/react"; // or /vue, /svelte, /solidMigration from @commercengine/react
If you're migrating from the old @commercengine/react package:
Before:
import { useCheckout } from "@commercengine/react";
function App() {
const { openCart, cartCount } = useCheckout({
storeId: "store_xxx",
apiKey: "ak_xxx",
onComplete: (order) => { ... },
});
return <button onClick={openCart}>Cart ({cartCount})</button>;
}After:
import { useCheckout, initCheckout, destroyCheckout } from "@commercengine/checkout/react";
import { useEffect } from "react";
// Initialize once at app root
function App({ children }) {
useEffect(() => {
initCheckout({
storeId: "store_xxx",
apiKey: "ak_xxx",
onComplete: (order) => { ... },
});
return () => destroyCheckout();
}, []);
return <>{children}</>;
}
// Use anywhere without config
function Header() {
const { openCart, cartCount } = useCheckout();
return <button onClick={openCart}>Cart ({cartCount})</button>;
}Key differences:
- Initialize once at app root with
initCheckout()instead of passing config to every hook useCheckout()takes no parameters - just returns state and methods- Cleanup with
destroyCheckout()in app unmount - Works across the entire app without prop drilling or providers
License
MIT
