cimplify
TypeScript SDK

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 const

The 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_amount

Funding 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_terms state.

  • Orders Read the committed Order after order_created.

On this page