Checkout
Use PurchaseIntentService to mint durable commerce, review immutable terms and an exact payment option, then confirm or resolve one recoverable attempt.
client.purchaseIntents is the checkout authority. Minting creates a durable
PurchaseIntent and immutable terms; it does not create an Order. Confirmation
creates the Order only after the server revalidates the accepted terms, source,
inventory, funding allocation, and opaque payment option.
Mint Cart terms
import {
MOBILE_MONEY_TENDER_AUTHORITY_KIND,
ORDER_TYPE,
PURCHASE_INTENT_PAYMENT_METHOD,
PURCHASE_INTENT_MOBILE_MONEY_NETWORK,
PURCHASE_INTENT_SOURCE_KIND,
} from '@cimplify/sdk'
const minted = await client.purchaseIntents.mint({
client_intent_key: crypto.randomUUID(),
source: {
kind: PURCHASE_INTENT_SOURCE_KIND.CART,
cart_id: cart.id,
},
checkout: {
customer: {
name: 'Jane Doe',
email: 'jane@example.com',
phone: '+233244000000',
save_details: false,
},
order_type: ORDER_TYPE.PICKUP,
address_info: { pickup_time: '2026-08-23T15:00:00Z' },
tender_preference: {
method_kind: PURCHASE_INTENT_PAYMENT_METHOD.MOBILE_MONEY,
authority: {
authority_kind: MOBILE_MONEY_TENDER_AUTHORITY_KIND.TRANSIENT,
phone_number: '+233244000000',
network: PURCHASE_INTENT_MOBILE_MONEY_NETWORK.MTN,
},
},
},
})
if (!minted.ok) throw minted.error
if (!minted.value.current_terms) throw new Error('Checkout terms are not prepared')The server derives seller and owner authority from the authenticated client. Do not send a business, account, staff-session, or hosted-session owner in the body.
For a saved Mobile Money authority, send only the opaque ID:
const tenderPreference = {
method_kind: PURCHASE_INTENT_PAYMENT_METHOD.MOBILE_MONEY,
authority: {
authority_kind: MOBILE_MONEY_TENDER_AUTHORITY_KIND.SAVED_REFERENCE,
stored_instrument_id: savedMobileMoney.id,
},
} as constThe server independently verifies ownership and derives its network. Never pair a saved reference with a phone number.
Review one exact option
import { selectPurchaseIntentPaymentOption } from '@cimplify/sdk'
const terms = minted.value.current_terms
const option = selectPurchaseIntentPaymentOption(terms, {
method_kind: PURCHASE_INTENT_PAYMENT_METHOD.MOBILE_MONEY,
})
if (!option) throw new Error('The selected payment method is unavailable')
renderPaymentReview({
commercePrincipal: option.principal_amount,
collectionFee: option.fee_amount,
customerDebit: option.customer_debit_amount,
currency: option.currency,
})The exact equations are:
store_credit_consumed + voucher_credit_consumed + rail_amount = due_now
principal_amount + fee_amount = customer_debit_amountFunding allocations belong to the accepted terms. Payment options collect only the residual principal and their own provider/offline fee. Do not display one top-level payment fee: different options may have different fees.
Confirm reviewed terms
import {
PURCHASE_INTENT_CONFIRM_STATUS,
createPurchaseIntentConfirmation,
} from '@cimplify/sdk'
const confirmation = createPurchaseIntentConfirmation(
terms,
option,
crypto.randomUUID(),
)
const confirmed = await client.purchaseIntents.confirm(
minted.value.id,
confirmation,
)
if (!confirmed.ok) throw confirmed.error
switch (confirmed.value.status) {
case PURCHASE_INTENT_CONFIRM_STATUS.ORDER_CREATED:
showOrder(confirmed.value.order_id, confirmed.value.order_number)
break
case PURCHASE_INTENT_CONFIRM_STATUS.TERMS_CHANGED:
showTermsReview(confirmed.value.replacement, confirmed.value.deltas)
break
case PURCHASE_INTENT_CONFIRM_STATUS.SELECTION_UNAVAILABLE:
showUnavailableLines(confirmed.value.lines)
break
case PURCHASE_INTENT_CONFIRM_STATUS.PAYMENT_FAILED:
showDeclinedPayment()
break
default:
handleProviderOrRecoveryState(confirmed.value)
}createPurchaseIntentConfirmation binds the current intent revision, terms version,
due-now value, currency, selected option ID, and its exact
customer_debit_amount. A true zero-rail purchase is the only case that can confirm
without an option.
Resolve provider actions and replay
resolve drives the canonical confirm/action/poll loop while keeping one durable
attempt authority:
import {
CHECKOUT_NEXT_ACTION,
PAYMENT_AUTHORIZATION_OUTCOME_STATUS,
PURCHASE_INTENT_ACTION_DECISION,
PURCHASE_INTENT_CONFIRM_STATUS,
} from '@cimplify/sdk'
const resolution = await client.purchaseIntents.resolve(minted.value.id, {
confirmation,
onActionRequired: async (action) => {
if (action.status === PURCHASE_INTENT_CONFIRM_STATUS.REDIRECT_REQUIRED) {
window.location.assign(action.authorization_url)
return PURCHASE_INTENT_ACTION_DECISION.STOP
}
if (action.status === PURCHASE_INTENT_CONFIRM_STATUS.CARD_POPUP_REQUIRED) {
const outcome = await client.checkout.authorizeCardPopup({
type: CHECKOUT_NEXT_ACTION.CARD_POPUP,
provider: action.provider,
client_secret: action.client_secret,
public_key: action.public_key,
provider_account_id: action.provider_account_id ?? undefined,
})
return outcome.status === PAYMENT_AUTHORIZATION_OUTCOME_STATUS.SUBMITTED
? PURCHASE_INTENT_ACTION_DECISION.CONTINUE
: PURCHASE_INTENT_ACTION_DECISION.STOP
}
const authorization = await collectAuthorization(action.authorization_type)
return {
kind: PURCHASE_INTENT_ACTION_DECISION.SUBMIT,
request: { authorization },
}
},
})Keep the same confirmation idempotency key after an ambiguous response. A definitive decline or an explicitly accepted replacement terms version starts a new attempt key. Persist only opaque intent, attempt, terms, option, and idempotency authorities for browser recovery—never customer contact, address, voucher, note, or raw tender data, and never a hash of low-entropy PII.
Hosted checkout
Hosted PurchaseIntents belong to the CheckoutSession, not the storefront API session:
const hosted = client.purchaseIntents.forHostedSession(sessionId)
const prepared = await hosted.prepare({
expected_intent_revision: 0,
idempotency_key: crypto.randomUUID(),
checkout,
})
if (!prepared.ok || !prepared.value.current_terms) {
throw prepared.ok ? new Error('Terms unavailable') : prepared.error
}
const result = await hosted.resolve(prepared.value.id, {
confirmation: createPurchaseIntentConfirmation(
prepared.value.current_terms,
selectPurchaseIntentPaymentOption(prepared.value.current_terms, tender) ?? null,
crypto.randomUUID(),
),
})Use the hosted service for prepare, confirm, replay, and action submission. An intent ID is the durable resource ID, but it does not replace the hosted session's owner authority.
Compatibility adapter
client.checkout.process() remains a deprecated SDK adapter over purchase-intent
routes for one migration window. New integrations should use
client.purchaseIntents so terms review, per-option fees, attempt replay, and hosted
ownership remain explicit. The backend /api/v1/checkout endpoint no longer exists,
and new checkout responses do not issue legacy Order bill tokens.
The deprecated client.checkout.submitAuthorization() method is fail-closed. It
can submit an action only when the same in-memory CheckoutService instance has
just received the matching payment_id, intent_id, and attempt_id from
process(). The SDK sends the authorization to the owner-fenced purchase-intent
attempt action route; it never falls back to the legacy payment-id authorization
mutation. After a reload, use the durable purchase-intent replay/action API instead.
-
Checkout Elements Use the hosted UI and its
reviewing_termsstate. -
Orders Read the committed Order after
order_created.
Cart
Session-bound cart. Add items with a flat payload, apply coupons, and read totals as branded ` Money` strings. The SDK returns `Result<T>` on every method and never throws.
Sign in with Cimplify
Add Cimplify sign-in to your storefront. One button, three route handlers, one React hook. Shoppers sign in via passkey or one-time code, then land back on your page signed in.