cimplify
Checkout integration

Vanilla Elements

The framework-agnostic checkout surface. Use this from Vue, Svelte, plain HTML, or anywhere else React isn't the right tool. The same controller backs the React wrappers; they're just convenience around `CimplifyElements`.

What ships

From @cimplify/sdk:

import {
  createCimplifyClient,
  createElements,           // factory
  CimplifyElements,         // the controller class
  CimplifyElement,          // a single mounted element
  ELEMENT_TYPES,            // "auth" | "address" | "payment" | "checkout" | "account"
  EVENT_TYPES,              // "ready" | "authenticated" | "change" | …
  MESSAGE_TYPES,            // postMessage type constants
} from "@cimplify/sdk";

Bootstrap

const client = createCimplifyClient({
  publicKey: "pk_live_…",
});

// businessId is optional; the controller resolves it from the public key.
const elements = createElements(client, "biz_01J5…", {
  appearance: {
    theme: "light",
    variables: { primaryColor: "#059669", borderRadius: "0.85rem" },
  },
});

Mounting an element

Every element type lives at link.cimplify.io/elements/<type>. The controller creates the iframe, hooks up postMessage, and routes events.

const checkout = elements.create(ELEMENT_TYPES.CHECKOUT, {
  orderTypes: ["delivery", "pickup"],
  defaultOrderType: "delivery",
  submitLabel: "Pay GH₵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"); // selector or HTMLElement

Element types

ConstantIframe pathWhat it renders
ELEMENT_TYPES.AUTH/elements/authSingle-line OTP sign-in.
ELEMENT_TYPES.ADDRESS/elements/addressAddress picker / form.
ELEMENT_TYPES.PAYMENT/elements/paymentAlias of checkout.
ELEMENT_TYPES.CHECKOUT/elements/checkoutFull unified checkout (auth + address + payment + submit).
ELEMENT_TYPES.ACCOUNT/elements/account/*Logged-in account portal.

Event types

Subscribe via element.on(eventType, handler). EVENT_TYPES covers:

EventPayloadFires on
READY{ height }iframe ready
AUTHENTICATEDAuthenticatedDataOTP success (Auth)
REQUIRES_OTP{ contactMasked }OTP dispatched (Auth)
CHANGE{ address } or { paymentMethod }field changes (Address / Payment)
ORDER_TYPE_CHANGED{ orderType }delivery/pickup toggle (Checkout)
REQUEST_SUBMIT{}in-iframe Pay button pressed
ERROR{ code, message }any failure

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 mid-flow. The promise settles only 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);
  },
});

// Cancel button:
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 you create the element with no explicit renderSubmitButton, the iframe renders its own Pay button. Listen for REQUEST_SUBMIT and call processCheckout:

checkout.on(EVENT_TYPES.REQUEST_SUBMIT, async () => {
  const result = await elements.processCheckout({
    cart_id: cart.id,
    order_type: "delivery",
  });
  // …
});

Cleanup

Call destroy() when leaving the page or unmounting the host. This removes the iframe, clears all event handlers, and removes the postMessage listener.

// On route leave:
elements.destroy();          // tears down all elements at once
// or per-element:
checkout.destroy();

Full example (vanilla TS)

import {
  createCimplifyClient,
  createElements,
  ELEMENT_TYPES,
  EVENT_TYPES,
} from "@cimplify/sdk";

const client = createCimplifyClient({ publicKey: "pk_live_…" });
const elements = createElements(client);

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.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");

// Later:
window.addEventListener("beforeunload", () => elements.destroy());

Next

On this page