cimplify
Checkout integration

Controlled Elements (React)

React wrappers around the Cimplify Element iframes. Drop a component into your tree, pass a few props, and you have a working checkout. The iframe still does all the rendering; you just orchestrate it with React.

Two ways in

@cimplify/sdk/react ships two layers:

  • <CimplifyCheckout>: a single component that renders the unified checkout iframe (auth + address + payment + submit) and exposes lifecycle callbacks. This is what pay.cimplify.io itself uses.
  • <ElementsProvider> + piecewise <AuthElement> / <AddressElement> / <PaymentElement>: for layouts where you want auth above the fold and address/payment below, or want to interleave Cimplify-rendered fields with your own.

Single-component checkout

import { CimplifyClient } from "@cimplify/sdk";
import { CimplifyCheckout } from "@cimplify/sdk/react";

const client = new CimplifyClient({
  publicKey: process.env.NEXT_PUBLIC_CIMPLIFY_PUBLIC_KEY!,
  credentials: "include",
});

export function CheckoutScreen({ cartId }: { cartId: string }) {
  return (
    <CimplifyCheckout
      client={client}
      cartId={cartId}
      orderTypes={["delivery", "pickup"]}
      defaultOrderType="delivery"
      submitLabel="Pay now"
      appearance={{
        theme: "light",
        variables: { primaryColor: "#059669", borderRadius: "0.85rem" },
      }}
      onStatusChange={(status, ctx) => {
        console.log(status, ctx.display_text);
      }}
      onComplete={(result) => {
        if (result.success) {
          window.location.assign(`/orders/${result.order!.id}`);
        }
      }}
      onError={(err) => console.error(err.code, err.message)}
    />
  );
}

CimplifyCheckout props

PropTypeRequiredNotes
clientCimplifyClientyesThe same client you use elsewhere; the iframe inherits its public key.
businessIdstringnoAuto-resolved from the public key when omitted.
cartIdstringnoAuto-resolved via client.cart.get() when omitted.
locationIdstringnoPin the order to a specific store location.
orderTypes("delivery" | "pickup" | "dine_in")[]noDefaults to ["pickup", "delivery"].
defaultOrderTypesame as abovenoPre-selected order type.
submitLabelstringnoOverride the Pay button copy.
enrollInLinkbooleannoDefault true. Saves the customer's details for 1-click checkout next time.
appearanceElementAppearancenoMemoize this; reference changes after mount are warned about and ignored.
linkUrlstringnoOverride the Link host (development).
demoModebooleannoSkip the API; for screenshots and storybook.
onComplete(result: ProcessCheckoutResult) => voidyesFires once on terminal success/failure.
onStatusChange(status, ctx) => voidnoSee checkout lifecycle.
onError(err: { code, message }) => voidnoNon-terminal initialization or runtime errors.

Piecewise Elements

Use <ElementsProvider> when you want Auth, Address, and Payment to live in different parts of your layout. The provider creates a single CimplifyElements controller; child components share token and customer state through it.

import {
  ElementsProvider,
  AuthElement,
  AddressElement,
  PaymentElement,
  useCheckout,
} from "@cimplify/sdk/react";

function CheckoutForm({ cartId }: { cartId: string }) {
  const { process, isLoading } = useCheckout();

  return (
    <>
      <AuthElement
        prefillEmail="jane@example.com"
        onAuthenticated={(d) => console.log("signed in", d.customerId)}
        onRequiresOtp={(d) => console.log("OTP sent to", d.contactMasked)}
        onError={(e) => console.error(e)}
      />
      <AddressElement mode="shipping" onChange={({ address, saveToLink }) => {/* … */}} />
      <PaymentElement amount={2999} currency="GHS" onChange={({ paymentMethod }) => {/* … */}} />

      <button
        disabled={isLoading}
        onClick={async () => {
          const result = await process({
            cart_id: cartId,
            order_type: "delivery",
          });
          if (result.success) router.push(`/orders/${result.order!.id}`);
        }}
      >
        {isLoading ? "Processing…" : "Pay"}
      </button>
    </>
  );
}

export default function CheckoutScreen({ cartId }: { cartId: string }) {
  return (
    <ElementsProvider client={client} options={{ appearance: { theme: "light" } }}>
      <CheckoutForm cartId={cartId} />
    </ElementsProvider>
  );
}

Component reference

<AuthElement>

PropTypeNotes
prefillEmailstringPre-fill the contact field.
onAuthenticated(data: AuthenticatedData) => voidCarries token, accountId, customerId, customer.
onRequiresOtp(data: { contactMasked }) => voidOTP dispatched.
onReady / onError() => voidLifecycle hooks.

<AddressElement>

PropTypeNotes
mode"shipping" | "billing"Default shipping.
onChange({ address: AddressInfo, saveToLink }) => voidFires on edit and on saved-address selection.

<PaymentElement>

PropTypeNotes
amountnumberCents; drives display only.
currencyCurrencyCodeDefaults to cart currency.
onChange({ paymentMethod: PaymentMethodInfo, saveToLink }) => void

No CheckoutElement / AccountElement wrappers

For the unified checkout iframe, use <CimplifyCheckout> (it already wraps that route). For the Link account portal, mount the iframe directly; see AccountElement.

Hooks

HookReturns
useElements()CimplifyElements | null. The underlying controller.
useElementsReady()boolean. True once the businessId resolves.
useCheckout(){ submit, process, isLoading }. Submit fires elements.submitCheckout; process fires elements.processCheckout.

Next

On this page