cimplify
Checkout integration

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

ConstantIframe pathWhat it renders
ELEMENT_TYPES.CHECKOUT/elements/checkoutFull checkout: contact, sign-in, address, payment, submit.

Event types

Subscribe via element.on(eventType, handler).

EventPayloadFires on
READY{ height }iframe ready
AUTHENTICATEDAuthenticatedDataOAuth 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

On this page