Vanilla checkout
The framework-agnostic checkout surface. Use this from Vue, Svelte, plain HTML, or anywhere React wrappers are not the right fit. The same CimplifyElements controller backs `<CimplifyCheckout>`.
createElements gives you the SDK-managed checkout controller without React. It
mounts the checkout iframe, validates postMessage origin and nonce, handles
Storefront OAuth, and runs checkout.
What ships
import {
createCimplifyClient,
createElements,
CimplifyElements,
CimplifyElement,
ELEMENT_TYPES,
EVENT_TYPES,
MESSAGE_TYPES,
} from "@cimplify/sdk";Bootstrap
Configure the Cimplify public key and the storefront OAuth client. Use
cpk_test_... in sandbox and cpk_live_... in production.
const client = createCimplifyClient({
publicKey: "cpk_live_...",
});
const elements = createElements(client, "biz_01J5...", {
auth: {
clientId: "cim_client_...",
redirectUri: `${window.location.origin}/auth/callback`,
callbackUri: "/auth/callback",
},
appearance: {
theme: "light",
variables: { primaryColor: "#059669", borderRadius: "0.85rem" },
},
});The callback route is the same one used by storefront sign-in. It must accept
GET for redirect sign-in and POST for modal or silent sign-in. See
Sign in with Cimplify.
Mounting Checkout
The controller creates the checkout iframe and routes events.
const checkout = elements.create(ELEMENT_TYPES.CHECKOUT, {
orderTypes: ["delivery", "pickup"],
defaultOrderType: "delivery",
submitLabel: "Pay GHS 29.99",
});
checkout.on(EVENT_TYPES.READY, () => console.log("checkout iframe ready"));
checkout.on(EVENT_TYPES.ERROR, (err) => console.error(err));
checkout.mount("#cimplify-checkout");Element Type
| Constant | Iframe path | What it renders |
|---|---|---|
ELEMENT_TYPES.CHECKOUT | /elements/checkout | Full checkout: contact, sign-in, address, payment, submit. |
Event types
Subscribe via element.on(eventType, handler).
| Event | Payload | Fires on |
|---|---|---|
READY | { height } | iframe ready |
AUTHENTICATED | AuthenticatedData | OAuth sign-in completed |
CHANGE | { address } or { paymentMethod } | field changes |
ORDER_TYPE_CHANGED | { orderType } | delivery/pickup/dine-in toggle |
REQUEST_SUBMIT | {} | in-iframe Pay button pressed |
ERROR | { code, message } | startup, auth, iframe, or checkout error |
During checkout, the iframe collects email/phone inline. The controller starts Cimplify OAuth with that contact as the login hint, tries silent sign-in first, and opens hosted verification only when the shopper needs to verify.
Pushing the cart
checkout.setCart({
items: [
{
name: "Jollof bowl",
quantity: 1,
unit_price: "29.99",
total_price: "29.99",
line_type: "simple",
},
],
subtotal: "29.99",
tax_amount: "0.00",
total_discounts: "0.00",
service_charge: "0.00",
total: "29.99",
currency: "GHS",
});Processing checkout
processCheckout returns an AbortablePromise; call .abort() to cancel an
in-flight attempt. The promise settles on terminal checkout_complete or
timeout.
const task = elements.processCheckout({
cart_id: cart.id,
order_type: "delivery",
enroll_in_link: true,
on_status_change: (status, ctx) => {
setStatusLine(ctx.display_text ?? status);
},
});
cancelBtn.addEventListener("click", () => task.abort());
const result = await task;
if (result.success) {
location.assign(`/orders/${result.order!.id}`);
} else {
console.error(result.error?.code, result.error?.message);
}In-iframe submit button
When the iframe renders its own Pay button, listen for REQUEST_SUBMIT and run
checkout from the parent:
checkout.on(EVENT_TYPES.REQUEST_SUBMIT, async () => {
const result = await elements.processCheckout({
cart_id: cart.id,
order_type: "delivery",
});
// handle result
});Cleanup
Call destroy() when leaving the page or unmounting the host. This removes the
iframe, clears handlers, and removes the postMessage listener.
elements.destroy();
// or per element:
checkout.destroy();Full example
import {
createCimplifyClient,
createElements,
ELEMENT_TYPES,
EVENT_TYPES,
} from "@cimplify/sdk";
const client = createCimplifyClient({
publicKey: "cpk_live_...",
});
const elements = createElements(client, undefined, {
auth: {
clientId: window.CIMPLIFY_CLIENT_ID,
redirectUri: `${window.location.origin}/auth/callback`,
callbackUri: "/auth/callback",
},
});
const checkout = elements.create(ELEMENT_TYPES.CHECKOUT, {
defaultOrderType: "delivery",
});
checkout.on(EVENT_TYPES.READY, async () => {
const cart = await client.cart.get();
if (cart.ok) checkout.setCart(toCheckoutCart(cart.value));
});
checkout.on(EVENT_TYPES.AUTHENTICATED, (data) => {
console.log("Signed in customer", data.customerId);
});
checkout.on(EVENT_TYPES.REQUEST_SUBMIT, async () => {
const cart = await client.cart.get();
if (!cart.ok) return;
const result = await elements.processCheckout({
cart_id: cart.value.id,
order_type: "delivery",
});
if (result.success) {
location.assign(`/orders/${result.order!.id}`);
} else {
showError(result.error?.message ?? "Checkout failed");
}
});
checkout.mount("#checkout-container");
window.addEventListener("beforeunload", () => elements.destroy());Next
- CimplifyCheckout: Same controller as a React component
- Element events: Raw postMessage protocol
CimplifyCheckout (React)
Mount the Cimplify checkout iframe from React. This is the recommended embedded checkout path for agent-built storefronts and custom merchant sites.
Headless checkout
Drive the checkout API directly. No iframe, no Cimplify-rendered fields; every screen, every input, every microcopy is yours. Use this when the iframe's look or layout can't accommodate your brand, or when you're building a non-web surface (native app, kiosk, voice).