cimplify
Cimplify Link

CheckoutElement

The unified checkout iframe. It renders contact capture, saved details, address, payment, provider authorization, and submit. Identity is handed off to Cimplify OAuth through the parent SDK.

CheckoutElement is the iframe behind <CimplifyCheckout> and the hosted Pay checkout page. It owns the checkout UI. It does not own the OTP/OAuth protocol; when the shopper wants to use saved Cimplify details, it asks the parent SDK to authenticate them.

Iframe URL

https://link.cimplify.io/elements/checkout?businessId=biz_…&nonce=<random>

# Alias (same component, same behavior):
https://link.cimplify.io/elements/payment?businessId=biz_…&nonce=<random>

What It Renders

A vertically stacked checkout form whose sections respond to auth state and order type:

  • Contact information: email or mobile number, shown inline before sign-in.
  • Cimplify sign-in: after a valid contact, the iframe sends auth_requested with a loginHint; OTP/passkey happens in auth.cimplify.io.
  • Signed-in row: once set_token arrives, the iframe shows the verified contact and a Change action.
  • Order type: pickup / delivery / dine-in toggle when more than one type is enabled.
  • Address section: saved addresses when signed in; otherwise a form with optional geolocation.
  • Payment section: saved methods when signed in; otherwise the merchant's configured providers.
  • Save info: "remember me for 1-click checkout" when the customer is signed in.
  • Submit button: rendered when renderSubmitButton: true.
  • Cart summary: shown when set_cart has been sent.

Checkout Auth Flow

CheckoutElement       Parent SDK                    auth.cimplify.io
  contact entered
  auth_requested ───▶ silent OAuth check ───────────▶ existing SSO?
                      if success: code returned
                      exchange callback
  set_token ◀─────── token broadcast

  if silent fails:
  auth_requested ───▶ modal OAuth ─────────────────▶ OTP/passkey/consent
  set_token ◀─────── token broadcast

The message sent by checkout after a valid contact:

{
  type: "auth_requested",
  loginHint: "jane@example.com",  // or normalized E.164 phone
  contactType: "email",           // "email" | "phone"
  mode: "checkout"
}

What this means:

  • If the shopper already has a valid Cimplify SSO session and it matches the hint, checkout signs them in with no visible modal.
  • If no matching session exists, the OAuth modal opens directly on the OTP step for the hinted contact.
  • The UI never reveals whether the contact is already a Link member before OTP verification.
  • Clicking Change sends { type: "auth_requested", mode: "checkout", prompt: "login" }, which skips silent auth and lets the shopper use a different account.

Init Message

After ready, send the standard init. Fields specific to CheckoutElement:

FieldNotes
businessIdRequired business context.
publicKeycpk_live_… or cpk_test_…. Used for API calls and test-mode detection.
orderTypesSubset of delivery | pickup | dine_in. Defaults to ["pickup", "delivery"].
defaultOrderTypePre-selected order type. Falls back to the first allowed order type.
renderSubmitButtonWhen true, the iframe renders its own Pay button and emits request_submit.
submitLabelOverride Pay button copy.
prefillEmailOptional contact prefill. Despite the name, it can seed only the contact field; checkout will still normalize/validate before auth.
appearanceSee Appearance API.

Mounting

React

<CimplifyCheckout> is the preferred integration for non-technical merchant storefronts and agent-generated storefronts. It creates the iframe, handles messages, starts OAuth, broadcasts tokens, processes checkout, and reports status.

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 Checkout({ cartId }: { cartId: string }) {
  return (
    <CimplifyCheckout
      client={client}
      cartId={cartId}
      orderTypes={["delivery", "pickup"]}
      defaultOrderType="delivery"
      submitLabel="Pay now"
      onComplete={(result) => {
        if (result.success) window.location.assign(`/orders/${result.order!.id}`);
      }}
      onStatusChange={(status, ctx) => console.log(status, ctx.display_text)}
    />
  );
}

For auth to work, the storefront must also have the OAuth routes from Sign in with Cimplify, and these public env vars must be available to browser code:

NEXT_PUBLIC_CIMPLIFY_CLIENT_ID=cim_client_…
NEXT_PUBLIC_CIMPLIFY_REDIRECT_URI=https://your-store.com/auth/callback

Vanilla SDK

const elements = createElements(client, businessId, {
  auth: {
    clientId: "cim_client_…",
    redirectUri: `${window.location.origin}/auth/callback`,
    callbackUri: "/auth/callback",
  },
});

const checkout = elements.create(ELEMENT_TYPES.CHECKOUT, {
  orderTypes: ["delivery", "pickup"],
  defaultOrderType: "delivery",
  submitLabel: "Pay GH₵29.99",
});

checkout.on(EVENT_TYPES.REQUEST_SUBMIT, async () => {
  const result = await elements.processCheckout({
    cart_id: cart.id,
    order_type: "delivery",
  });
  if (result.success) location.assign(`/orders/${result.order!.id}`);
});

checkout.mount("#checkout");

Pre-Filling the Cart

set_cart renders the line-item summary alongside the form. CheckoutCartData:

interface CheckoutCartData {
  items: CheckoutCartItem[];
  subtotal: string;
  tax_amount: string;
  total_discounts: string;
  service_charge: string;
  total: string;
  currency: string;
}

interface CheckoutCartItem {
  name: string;
  quantity: number;
  unit_price: string;
  total_price: string;
  image_url?: string;
  line_type: "simple" | "service" | "bundle" | "composite" | "digital";
  variant_name?: string;
  scheduled_start?: string;
  scheduled_end?: string;
  selections?: { name: string; quantity: number; variant_name?: string }[];
  add_ons?: { name: string; price: string }[];
  special_instructions?: string;
}

Lifecycle

See checkout lifecycle for the full state machine. The element emits checkout_status for each transition and checkout_complete on terminal success or failure.

Payment Authorization Challenges

Provider challenges are separate from Cimplify sign-in. When a payment provider needs an OTP, PIN, birthday, phone, or address to authorize payment, the element switches to AuthorizationView and emits:

{
  type: "checkout_status",
  status: "awaiting_authorization",
  context: { authorization_type: "otp", display_text: "Enter the code from your provider" }
}

The shopper enters the challenge inside the checkout iframe. You do not render a separate challenge UI.

Recovery

If the shopper reloads while payment is in flight, the element rehydrates from local storage on next mount and resumes from the last known state. The first lifecycle event you receive will be checkout_status: "recovering".

Agent Checklist

When wiring checkout for a merchant:

  1. Prefer <CimplifyCheckout>.
  2. Set NEXT_PUBLIC_CIMPLIFY_PUBLIC_KEY, NEXT_PUBLIC_CIMPLIFY_CLIENT_ID, and NEXT_PUBLIC_CIMPLIFY_REDIRECT_URI.
  3. Add /auth/callback with both GET and POST handlers.
  4. Let Cimplify OAuth handle shopper verification.
  5. Load saved Link details after verification completes.
  6. Let the SDK handle auth_requested and set_token.

Next

On this page