Checkout
Checkout is per-store. Build a checkout at one store, pick a payment rail, and complete it. For multiple shops, run one checkout per store sequentially (there is no universal cart).
This page covers both paths. On Path A the bridge collapses cart → checkout →
mandate → complete into confirm_purchase. On Path B you drive each UCP step
yourself.
Lifecycle
Payment rails
A checkout response advertises every enabled rail under ucp.payment_handlers,
keyed by handler type (sh.artos.card / sh.artos.crypto), each entry carrying
an inner id and inline config. You select a rail by that inner id —
artos.card or artos.crypto — not by the registry key.
| Rail | Registry key | Select with (handler_id / payment_method) | Auth on completion |
|---|---|---|---|
| Card | sh.artos.card | artos.card | Platform key |
| Crypto | sh.artos.crypto | artos.crypto | Buyer bearer only |
Card is tokenized (Stripe pm_…) or a hosted pay-URL handoff; crypto settles
on-chain to the merchant in USDsui and is where coupons apply.
Crypto completion is buyer-bound: send the buyer OAuth bearer only, never the platform
X-API-Key(see Authentication).
Selecting a rail
UCP has no “select payment” operation. The agent picks a rail by completing with
the chosen instrument. If a store offers more than one rail and you complete with
none selected, the server returns the checkout body as a recoverable UCP error
payment_selection_required (HTTP 200, ucp.status: error, checkout
status: incomplete). No mandate is verified or consumed. Read
ucp.payment_handlers, ask the buyer, and retry with the rail selected.
Path A — confirm_purchase
The bridge does the heavy lifting: fetches a fresh quote, verifies the store’s
merchant_authorization against its published signing keys, mints the AP2
mandate, and calls complete_checkout with the chosen rail.
{
"name": "confirm_purchase",
"arguments": {
"store_slug": "energy-sport",
"checkout_id": "checkout_456",
"payment_method": "artos.crypto",
"payment_mandate_id": "pm_optional_preauth"
}
}payment_method— the handler id to route to (artos.card/artos.crypto). Omit it only if you want thepayment_selection_requiredprompt.payment_mandate_id— optional; enforces a pre-authorized spending allowance.- Report totals exactly as returned — do not pre-compute discounts. When a
hosted rail is selected the response is
requires_escalationwith acontinue_urlthe buyer must open; always present that link.
Path B — the SDK’s confirmPurchase
With @artos-commerce/ucp-client, a single
confirmPurchase is the equivalent of the manual cart → checkout → mandate →
complete sequence: it re-prices, verifies the store’s merchant_authorization,
mints the AP2 mandate, routes the $0 / card / crypto rail, and (for crypto)
signs and submits the Sui PTB.
const outcome = await shop.confirmPurchase({
storeSlug: 'energy-sport',
checkoutId: 'checkout_456',
paymentMethod: 'artos.crypto', // or 'artos.card'; omit to be prompted
});
// outcome.status: 'completed' | 'escalation_required'
// | 'payment_selection_required' | 'error'The raw UCP completion the SDK performs is documented below for non-JS clients.
Raw UCP complete (MCP + REST)
Build the checkout, then complete it. Every state-changing call needs both
meta["ucp-agent"].profile and meta["idempotency-key"].
Create the checkout:
{
"name": "create_checkout",
"arguments": {
"checkout": {
"cart_id": "cart_123",
"shipping_address": {
"line1": "1 Market St", "city": "San Francisco",
"region": "CA", "postal_code": "94105", "country": "US"
}
},
"meta": {
"ucp-agent": { "profile": "https://your-app.example/.well-known/ucp" },
"idempotency-key": "co-create-2f9c…"
}
}
}In raw UCP the checkout takes either
cart_idor aline_itemsarray, where each line item is{ "item": { "id": "variant_123" }, "quantity": 1 }— not the bridge’s flat{ id, quantity }.
Card completion. Supply a tokenized instrument and the AP2 checkout_mandate:
{
"name": "complete_checkout",
"arguments": {
"id": "checkout_456",
"checkout": {
"payment": {
"instruments": [{
"handler_id": "artos.card",
"selected": true,
"credential": { "type": "token", "token": "pm_1Pxxxx" }
}]
},
"ap2": { "checkout_mandate": "<base64url-header>..<base64url-signature>" }
},
"meta": {
"ucp-agent": { "profile": "https://your-app.example/.well-known/ucp" },
"idempotency-key": "co-complete-7b1a…"
}
}
}The REST equivalent is POST /s/:slug/checkout-sessions/:id/complete with the
{ payment, ap2 } body (no MCP wrapping), plus Request-Id, UCP-Agent, and
Idempotency-Key headers. See Direct UCP for the full
header rules.
Crypto completion.
- Prepare the payment intent (buyer bearer only) to get an unsigned Sui PTB:
curl -X POST https://api.artos.sh/s/energy-sport/checkout-sessions/checkout_456/payment-intent \
-H "Authorization: Bearer BUYER_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-H "Request-Id: $(uuidgen)" \
-H 'UCP-Agent: profile="https://your-app.example/.well-known/ucp"' \
-d '{ "buyer_address": "0xBUYER", "input_coin_type": "0x2::sui::SUI" }'- Sign and submit the returned PTB with the buyer’s wallet (the bridge does this with the shared agent key on the buyer’s alias).
- Complete with the tx digest as the instrument token plus the AP2 mandate,
again with the buyer bearer only. Pass
ap2.intent_mandate(the user-signed allowance) when settling crypto directly.
AP2 mandates
The AP2 mandate is the agent authorization over the priced terms — not a
payment option the buyer picks. Artos uses a compact ES256 JWS of the form
header..signature (empty middle segment), not full SD-JWT+kb. The server:
- resolves the agent key by
kidfrom the platform profilesigning_keysand verifies the ES256 signature; - requires
exp(missing/past →mandate_expired) and ajti(replays rejected); - requires the
merchantbinding to equal the store slug; - confirms the embedded terms (
id/total/currency) equal the live quote; - re-verifies the embedded
merchant_authorization(proof the agent signed over terms Artos authored).
See Profiles & trust for the full conformance notes and deliberate deviations.
Coupons (crypto only)
Any eligible Artos coupon the buyer owns is applied when paying with crypto with
apply_coupon: true (buyer opt-in via confirm_purchase / the SDK’s
applyCoupon flag):
- applies up to the full order total (merchandise, tax, and shipping);
- is reusable — a smaller order spends only that amount, the rest stays;
- one coupon per order (best eligible, chosen automatically); no stacking;
- account total ≠ one-order discount — e.g. two $1 coupons give at most $1 off a single checkout, not $2;
- does not apply on the card rail.
Never pre-compute or promise a discounted total — report the totals exactly as returned after the order is placed.
If one coupon does not fully cover the order, the remaining amount is paid on the chosen rail. On crypto, that remainder goes through DeepBook, which enforces a minimum trade size — small leftovers (e.g. $0.30 after a $1 coupon on a $1.30 order) can fail even when coupons are valid.
$0 orders
Some orders total $0 when a single coupon (or discount) fully covers the
total. These complete normally — report the returned total and never treat a $0
total as an error. Partial coupon coverage is not $0: the remainder still must
clear on crypto (and DeepBook’s minimum) or card.