cimplify
TypeScript SDK

Checkout

Convert a cart into a paid order. The body is **flat**: fields like ` cart_id`, `customer`, `order_type`, and `payment_method` sit at the top level (not nested under any envelope). Production uses ` #[serde(flatten)]`; the SDK matches that shape exactly.

Process a checkout

const r = await client.checkout.process({
  cart_id: cart.id,
  location_id: 'loc_main',
  order_type: 'pickup',                  // 'delivery' | 'pickup' | 'dine-in' | 'walk-in'
  payment_method: 'mobile_money',        // 'mobile_money' | 'card'
  customer: {
    name: 'Jane Doe',
    email: 'jane@example.com',
    phone: '+233244000000',
    save_details: true,
  },
  address_info: {
    pickup_time: '2026-05-08T15:00:00Z',
  },
  mobile_money_details: {
    phone_number: '+233244000000',
    provider: 'mtn',
  },
  special_instructions: 'No straw, please.',
})

if (!r.ok) {
  console.error(r.error.code, r.error.message)
  return
}

const result = r.value
console.log(result.order_id, result.order_number)
console.log(result.payment_status)               // 'pending' | 'success' | 'failed' | ...
console.log(result.requires_authorization)
console.log(result.next_action)                  // discriminated union, see below

bill_token: guest order lookups

On a successful guest checkout the response carries a bill_token. The SDK persists it client-side keyed by order_id, then attaches it to every subsequent orders.get / checkout.pollPaymentStatus call. You do not need to thread it through manually; it just works in the same browser session.

if (r.ok && r.value.bill_token) {
  // Already cached on the client; this read is purely for your records.
  // No need to pass it back in subsequent calls.
  console.log(r.value.bill_token)
}

next_action: branch on what to do next

switch (result.next_action?.type) {
  case 'none':
    // Payment captured. Show a confirmation screen.
    break

  case 'redirect':
    window.location.href = result.next_action.authorization_url
    break

  case 'card_popup':
    openProviderPopup({
      provider: result.next_action.provider,
      clientSecret: result.next_action.client_secret,
      publicKey: result.next_action.public_key,
    })
    break

  case 'authorization':
    // Collect OTP / PIN / phone / birthday / address from the user, then submit
    promptForAuthorization(result.next_action.authorization_type, result.next_action.display_text)
    break

  case 'poll':
    pollUntilTerminal(result.order_id)
    break
}

submitAuthorization

When the customer completes an OTP / PIN / etc., POST it back through the SDK. The response is the same CheckoutResult shape, with an updated next_action.

const r = await client.checkout.submitAuthorization({
  order_id: 'ord_xxx',
  authorization_type: 'otp',
  authorization_value: '123456',
})

if (!r.ok) {
  console.error(r.error.code, r.error.message)
  return
}

if (r.value.next_action?.type === 'poll') {
  await pollUntilTerminal(r.value.order_id)
}

pollPaymentStatus

async function pollUntilTerminal(orderId: string, intervalMs = 2000, maxAttempts = 30) {
  for (let i = 0; i < maxAttempts; i++) {
    const r = await client.checkout.pollPaymentStatus(orderId)
    if (!r.ok) return r

    const status = r.value.status
    if (status === 'success' || status === 'failed') {
      return r
    }

    await new Promise((resolve) => setTimeout(resolve, intervalMs))
  }
}

Attach a customer to a guest order

// After a guest checkout, attach customer info to the order (uses bill_token automatically)
const r = await client.checkout.updateOrderCustomer('ord_xxx', {
  name: 'Jane Doe',
  email: 'jane@example.com',
  phone: '+233244000000',
  save_details: false,
})

Cross-currency checkout

Set pay_currency on the request and the SDK locks an FX quote for you behind the scenes; the fx field on the response shows the locked rate.

const r = await client.checkout.process({
  cart_id: cart.id,
  customer: { /* ... */ },
  order_type: 'pickup',
  payment_method: 'card',
  address_info: {},
  pay_currency: 'USD',
})

if (r.ok && r.value.fx) {
  console.log(r.value.fx.base_amount, '→', r.value.fx.pay_amount)
  console.log(r.value.fx.rate, r.value.fx.quote_id)
}

CheckoutFormData (request body)

FieldTypeNotes
cart_idstringRequired.
customerCheckoutCustomerInfoRequired. name, email, phone, save_details.
order_type'delivery' | 'pickup' | 'dine-in' | 'walk-in'Required.
payment_method'mobile_money' | 'card'Required.
address_infoCheckoutAddressInfoRequired (can be empty for pickup).
location_idstringMulti-location businesses.
mobile_money_detailsMobileMoneyDetailsRequired when payment_method = 'mobile_money'.
special_instructionsstringFree-form note for the order.
idempotency_keystringAuto-generated if omitted.
pay_currencyCurrencyCodeTriggers FX quoting if different from cart currency.
fx_quote_idstringPre-locked quote (otherwise the SDK locks one).
metadataRecord<string, unknown>Round-tripped to webhooks.

Method reference

MethodReturns
process(data)Result<CheckoutResult>
submitAuthorization(input)Result<CheckoutResult>
pollPaymentStatus(orderId)Result<PaymentStatusResponse>
updateOrderCustomer(orderId, customer)Result<Order>
verifyPayment(orderId)Result<Order>
  • Cart The cart_id input comes from here

  • Orders Read the order created by checkout.process

  • FX Rates and locked quotes for cross-currency pay

  • Error handling PAYMENT_FAILED, AUTH_INCOMPLETE, recovery flows

On this page