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_requestedwith aloginHint; OTP/passkey happens inauth.cimplify.io. - Signed-in row: once
set_tokenarrives, the iframe shows the verified contact and aChangeaction. - 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_carthas 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 broadcastThe 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
Changesends{ 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:
| Field | Notes |
|---|---|
businessId | Required business context. |
publicKey | cpk_live_… or cpk_test_…. Used for API calls and test-mode detection. |
orderTypes | Subset of delivery | pickup | dine_in. Defaults to ["pickup", "delivery"]. |
defaultOrderType | Pre-selected order type. Falls back to the first allowed order type. |
renderSubmitButton | When true, the iframe renders its own Pay button and emits request_submit. |
submitLabel | Override Pay button copy. |
prefillEmail | Optional contact prefill. Despite the name, it can seed only the contact field; checkout will still normalize/validate before auth. |
appearance | See 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/callbackVanilla 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:
- Prefer
<CimplifyCheckout>. - Set
NEXT_PUBLIC_CIMPLIFY_PUBLIC_KEY,NEXT_PUBLIC_CIMPLIFY_CLIENT_ID, andNEXT_PUBLIC_CIMPLIFY_REDIRECT_URI. - Add
/auth/callbackwith both GET and POST handlers. - Let Cimplify OAuth handle shopper verification.
- Load saved Link details after verification completes.
- Let the SDK handle
auth_requestedandset_token.
Next
- CimplifyCheckout (React): High-level wrapper
- Embedded iframe: Raw postMessage integration
- Element events: Full message contract
Universal Commerce Protocol (UCP)
A signed, capability-discoverable protocol AI agents use to browse, price, check out, and pay across any Cimplify business with verifiable consent.
Checkout sessions
A checkout session is a short-lived URL on `pay.cimplify.io` that hosts the full checkout for a specific cart. You create one server-side with your secret key, redirect the customer to the URL, and listen for the resulting webhook.