cimplify

Controlled Elements Checkout

Tier 2 -- use React components with custom layout. The unified checkout element handles auth, address, payment, and order creation in a single iframe.

Recommended: For most integrations, use CimplifyCheckout (Tier 1). Use Tier 2 only when you need custom layout around auth or need to control element placement in your page.

Primary Pattern: CimplifyCheckout

The unified checkout element handles the entire flow internally -- auth, address collection (for delivery), payment, and order creation.

TSX
"use client";
import { createCimplifyClient } from "@cimplify/sdk";
import { CimplifyCheckout } from "@cimplify/sdk/react";

const client = createCimplifyClient({
  publicKey: process.env.NEXT_PUBLIC_CIMPLIFY_PUBLIC_KEY,
});

export default function CheckoutPage({ cartId }: { cartId: string }) {
  return (
    <CimplifyCheckout
      client={client}
      cartId={cartId}
      locationId="loc_1"
      orderTypes={["pickup", "delivery"]}
      defaultOrderType="pickup"
      enrollInLink={true}
      appearance={{
        theme: "light",
        variables: { primaryColor: "#111827", borderRadius: "12px" },
      }}
      onStatusChange={(status, context) => {
        console.log(status, context.display_text);
      }}
      onComplete={(result) => {
        if (result.success) {
          window.location.href = "/order/" + result.order?.id;
        }
      }}
      onError={(error) => console.error(error)}
    />
  );
}

Advanced Pattern: AuthElement + CimplifyCheckout

Use ElementsProvider + AuthElement for a standalone sign-in step, then render CimplifyCheckout for the rest. This is useful when you want to show sign-in on a separate page or need to capture the auth token before proceeding.

TSX
"use client";
import { createCimplifyClient } from "@cimplify/sdk";
import {
  ElementsProvider,
  AuthElement,
  CimplifyCheckout,
} from "@cimplify/sdk/react";
import { useState } from "react";

const client = createCimplifyClient({
  publicKey: process.env.NEXT_PUBLIC_CIMPLIFY_PUBLIC_KEY,
});

export default function CheckoutPage({ cartId }: { cartId: string }) {
  const [authed, setAuthed] = useState(false);

  return (
    <ElementsProvider
      client={client}
      businessId="bus_123"
      options={{
        appearance: {
          theme: "light",
          variables: { primaryColor: "#111827", borderRadius: "12px" },
        },
      }}
    >
      {!authed ? (
        <div>
          <h2>Sign in</h2>
          <AuthElement
            onAuthenticated={(data) => {
              console.log("Customer:", data.customerId);
              setAuthed(true);
            }}
            onRequiresOtp={(data) => {
              console.log("OTP sent to:", data.contactMasked);
            }}
            onError={(err) => console.error(err.message)}
          />
        </div>
      ) : (
        <CimplifyCheckout
          client={client}
          cartId={cartId}
          orderTypes={["pickup", "delivery"]}
          onComplete={(result) => {
            if (result.success) {
              window.location.href = "/order/" + result.order?.id;
            }
          }}
          onError={(error) => console.error(error)}
        />
      )}
    </ElementsProvider>
  );
}

ElementsProvider Props

PropTypeDescription
clientCimplifyClientRequired. Created via createCimplifyClient().
businessId?stringIf omitted, resolved from the public key.
options?{ appearance?, linkUrl? }Theme and element settings.

AuthElement

Standalone sign-in element. This is the only element that renders a true standalone form -- AddressElement and PaymentElement now render the full unified checkout iframe.

PropType
prefillEmail?string
className?string
style?React.CSSProperties
onAuthenticated?(data: { token, accountId, customerId, customer }) => void
onRequiresOtp?(data: { contactMasked }) => void
onReady?() => void
onError?(err: { code, message }) => void
TSX
<AuthElement
  prefillEmail="customer@example.com"
  onAuthenticated={(data) => {
    console.log("Token:", data.token);
    console.log("Customer:", data.customerId);
  }}
  onRequiresOtp={(data) => {
    console.log("OTP sent to:", data.contactMasked);
  }}
/>

Cart Summary & Built-in Submit

The CimplifyCheckout component automatically fetches cart data and sends it to the checkout iframe for an inline order summary (line items, tax, totals). The iframe also renders its own submit button — when pressed, it emits a request_submit event that the SDK handles automatically by calling processCheckout().

You don't need to render a submit button yourself — the checkout element includes one. If you omit cartId, the SDK resolves the active cart automatically.

useCheckout()

Returns { submit, process, isLoading }. process gives full control with status callbacks and timeout; submit is a simpler wrapper.

TSX
const { process, isLoading } = useCheckout();

const result = await process({
  cart_id: cartId,
  order_type: "delivery",
  location_id: locationId,
  notes: "Ring the bell",
  tip_amount: 200,
  scheduled_time: "2025-06-15T12:00:00Z",
  enroll_in_link: true,
  pay_currency: "GHS",
  timeout_ms: 180_000,
  on_status_change: (status, context) => {
    setStatus(status);
    setStatusText(context.display_text || "");
  },
});

if (result.success) {
  router.push("/order/" + result.order?.id);
} else if (result.error?.recoverable) {
  // Show retry button
}

Legacy: Composed Elements

Backward compatibility: AddressElement and PaymentElement still exist and are exported, but they now render the full unified checkout iframe internally (not standalone forms). The composed pattern below still works but is no longer the recommended approach.

TSX
// Legacy composed pattern -- still works via backward compatibility.
// AddressElement and PaymentElement mount the full unified checkout iframe.
// Prefer CimplifyCheckout or AuthElement + CimplifyCheckout instead.

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

  return (
    <div>
      <AuthElement
        onAuthenticated={(data) => console.log("Authed:", data.customerId)}
      />
      <AddressElement mode="shipping" />
      <PaymentElement />
      <button disabled={isLoading} onClick={() => process({ cart_id: cartId, order_type: "delivery" })}>
        {isLoading ? "Processing..." : "Pay now"}
      </button>
    </div>
  );
}

Next Steps