Checkout
Create a durable purchase intent, review immutable terms and an exact payment option, then confirm it idempotently. An Order does not exist until confirmation commits.
Checkout is a two-step purchase-intent protocol. Minting freezes the current Cart, buyer, funding allocations, and eligible collection options into immutable terms. Confirmation accepts one exact terms version and payment option. It is the only step that may create an Order.
All responses use Cache-Control: no-store. Keep the same idempotency key when a
request has an ambiguous network outcome. Rotate it only when the shopper changes
material checkout or tender input.
POST /api/v1/purchase-intents
Mint an open Cart purchase intent. The authenticated publishable key determines the seller; callers cannot supply seller or owner authority.
Request
curl -X POST https://storefronts.cimplify.io/api/v1/purchase-intents \
-H "X-API-Key: cpk_test_your_publishable_key" \
-H "Idempotency-Key: 5b1f9d80-2c70-4f2c-bf61-a0e6fa6b02a1" \
-H "Content-Type: application/json" \
-d '{
"client_intent_key": "5b1f9d80-2c70-4f2c-bf61-a0e6fa6b02a1",
"source": { "kind": "cart", "cart_id": "cart_01H…" },
"checkout": {
"customer": {
"name": "Ama Mensah",
"email": "ama@example.com",
"phone": "+233241234567",
"save_details": false
},
"order_type": "delivery",
"address_info": {
"street_address": "12 Independence Ave",
"city": "Accra",
"country": "GH"
},
"tender_preference": {
"method_kind": "mobile_money",
"authority": {
"authority_kind": "transient",
"phone_number": "+233241234567",
"network": "mtn"
}
}
}
}'tender_preference controls option ranking, not commercial authority. A saved Mobile
Money preference contains only authority_kind: "saved_reference" and its opaque
stored_instrument_id; never resend its phone number. The server still returns all
eligible options.
Response
{
"data": {
"id": "pi_01J…",
"status": "open",
"revision": 1,
"current_terms": {
"id": "pit_01J…",
"intent_revision": 1,
"terms_version": 1,
"money": {
"currency": "GHS",
"total_obligation": "105.00",
"due_now": "105.00",
"rail_amount": "105.00",
"store_credit_consumed": "0.00",
"voucher_credit_consumed": "0.00"
},
"payment_options": [
{
"id": "pio_01J…",
"rank": 0,
"method_kind": "mobile_money",
"collection_mode": "request_to_pay",
"provider": "mtn_momo",
"principal_amount": "105.00",
"fee_amount": "1.50",
"customer_debit_amount": "106.50",
"currency": "GHS",
"status": "eligible",
"expected_next_action": "provider_approval",
"expires_at": "2026-08-23T18:30:00Z"
}
],
"valid_until": "2026-08-23T18:30:00Z"
}
}
}Funding and collection have distinct consent semantics:
store_credit_consumed + voucher_credit_consumed + rail_amount = due_now
For the selected option:
principal_amount + fee_amount = customer_debit_amount
Show the selected option's fee and customer debit before confirmation. There is no truthful top-level payment fee because each option may price collection differently.
GET /api/v1/purchase-intents/:intent_id
Read the current owner-fenced intent and immutable terms. Use this after a reload or an ambiguous mint response instead of creating another intent.
POST /api/v1/purchase-intents/:intent_id/prepare
Prepare or update Cart material for an existing draft/open intent. The request is:
{
"expected_intent_revision": 0,
"idempotency_key": "prepare_01J…",
"checkout": {
"customer": {
"name": "Ama Mensah",
"phone": "+233241234567",
"email": "ama@example.com",
"save_details": false
},
"order_type": "delivery",
"address_info": {
"street_address": "12 Independence Ave",
"city": "Accra",
"country": "GH"
},
"tender_preference": { "method_kind": "card" }
}
}An exact (intent, idempotency_key) replay returns the original prepared terms.
Contact, address, voucher, and raw tender data must not be used as browser recovery
fingerprints.
POST /api/v1/purchase-intents/:intent_id/confirm
Confirm the exact reviewed terms and option. accepted_customer_debit_amount comes
from the selected option, including that option's fee; it is not the provider-neutral
rail_amount.
curl -X POST https://storefronts.cimplify.io/api/v1/purchase-intents/pi_01J…/confirm \
-H "X-API-Key: cpk_test_your_publishable_key" \
-H "Idempotency-Key: confirm_01J…" \
-H "Content-Type: application/json" \
-d '{
"accepted_intent_revision": 1,
"accepted_terms_version": 1,
"accepted_due_now": "105.00",
"accepted_customer_debit_amount": "106.50",
"currency": "GHS",
"payment_option_id": "pio_01J…",
"idempotency_key": "confirm_01J…"
}'The response is a closed union. Terminal/recoverable outcomes include
order_created, collection_processing, payment_failed, and
payment_outcome_unknown. Provider interaction may return redirect_required,
card_popup_required, or authorization_required. Commercial revalidation may
return terms_changed, selection_unavailable, offer_ended, or
source_unavailable; render those states and require a fresh review rather than
silently accepting replacements.
For a true zero-rail purchase, payment_option_id is null and
accepted_customer_debit_amount is zero. Any issued option must be selected by its
opaque ID.
POST /api/v1/purchase-intents/:intent_id/attempts/:attempt_id/actions
Submit one typed provider challenge for the exact attempt. The body contains only the authorization answer:
{
"authorization": { "type": "otp", "value": "847362" }
}Never send an Order ID, Payment ID, provider reference, phone number, or raw payment credential to this endpoint.
Hosted checkout sessions
Hosted sessions own their purchase intent through the session authority. Use
POST /v1/checkout/sessions/:session_id/prepare, /confirm, GET /replay, and
/attempts/:attempt_id/actions. Do not call direct storefront intent routes with a
hosted intent ID. In the SDK, use client.purchaseIntents.forHostedSession(sessionId).
-
Next: Orders Read the Order only after confirmation returns
order_created. -
FX Lock a quote before preparing terms when the shopper pays in another currency.
Cart
The cart is server-owned and bound to the caller’s session. Items are added by `item_id` with an optional `variant_id` and add-on selections. The same cart is read on the storefront and on checkout; there is no client-side cart sync.
Storefront Auth
Raw customer OTP endpoints on the storefront API. New storefronts should prefer SDK OAuth sign-in; these endpoints remain available for direct API integrations.