Skip to Content
Checkout

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 tracks. On Connect the bridge collapses cart → checkout → mandate → complete into confirm_purchase. On the Build track you drive each UCP step yourself.

The store slug energy-sport and ids like checkout_456 in the examples are illustrative — substitute live values from your own search results (metadata.artos_seller.slug) and checkout responses.

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.

RailRegistry keySelect with (handler_id / payment_method)Auth on completion
Cardsh.artos.cardartos.cardPlatform key
Cryptosh.artos.cryptoartos.cryptoBuyer 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.

Connect — 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": { "checkouts": [{ "store_slug": "energy-sport", "checkout_id": "checkout_456" }], "payment_method": "artos.crypto", "payment_mandate_id": "pm_optional_preauth" } }
  • checkouts — the checkout to pay, or one per shop (up to 5) to pay them all in one crypto transaction (below).
  • payment_method — the handler id to route to (artos.card / artos.crypto). Omit it only if you want the payment_selection_required prompt.
  • payment_mandate_id — optional; enforces a pre-authorized spending allowance.
  • quote_id — optional, crypto only; binds the purchase to a price locked with quote_purchase (below).
  • Report totals exactly as returned — do not pre-compute discounts. When a hosted rail is selected the response is requires_escalation with a continue_url the buyer must open; always present that link.

Lock the crypto price first — quote_purchase

A crypto total is paid from the coins in the buyer’s wallet. Only the coins the store lists in its sh.artos.crypto handler’s accepted_coins count; Artos accepts only coins with deep liquidity on DeepBook. It picks them in this order: USDC first (sent to the merchant as is, no conversion), then other stablecoins such as USDsui, then SUI and the other accepted tokens. It combines up to three coins in one transaction. Any coin other than USDC is converted to USDC through DeepBook at a rate that holds for about 60 seconds. A payment made entirely in USDC has no rate, so its quote holds as long as the checkout and never changes. When the buyer names a coin (“pay with SUI”), pass pay_with with its symbol or coin type, and Artos pays with that coin only. The buyer can switch coins at any point before paying: quote again with the new pay_with. To show the buyer exactly what they will pay before they approve, call quote_purchase with the same arguments you will pass to confirm_purchase, pay_with included. It prepares the payment and moves no money:

{ "name": "quote_purchase", "arguments": { "checkouts": [{ "store_slug": "energy-sport", "checkout_id": "checkout_456" }], "payment_method": "artos.crypto" } }

The result is the checkout plus a payment_quote:

{ "payment_quote": { "quote_id": "3f9c…", "coin_symbol": "USDC", "coin_amount": 2.27, "total": 427, "currency": "USD", "expires_at": "2026-09-25T12:01:00.000Z", "legs": [ { "kind": "direct", "coin_symbol": "USDC", "coin_amount": 2.27, "stable": true }, { "kind": "swap", "coin_symbol": "USDsui", "coin_amount": 2.002, "stable": true } ] } }

total is in minor units. coin_symbol / coin_amount name the coin paying the most, and legs lists every coin; show the buyer all of them. When a single non-stable coin pays (stable: false), its rate is total / coin_amount. If the wallet cannot cover the total (keeping a little SUI for network fees), quote_purchase returns insufficient_funds with what the wallet holds, and nothing is locked. If the buyer named a coin whose DeepBook market is too thin for this amount right now, it returns insufficient_liquidity: offer another coin or a smaller order. Pass quote_id to confirm_purchase before expires_at. The purchase pays that exact amount or nothing: if the price no longer holds, confirm_purchase charges nothing and returns a new payment_quote to show the buyer before asking again. A card or $0 checkout has nothing to lock; quote_purchase says so, and you confirm it directly.

Pay several shops in one transaction

A buyer shopping at several Artos stores can pay them all at once. Pass one checkout per shop (2 to 5) in checkouts to quote_purchase, then the same checkouts and the returned quote_id to confirm_purchase. One Sui transaction pays every merchant, all or nothing, and each shop places its own order:

{ "name": "quote_purchase", "arguments": { "checkouts": [ { "store_slug": "energy-sport", "checkout_id": "checkout_456" }, { "store_slug": "goodys", "checkout_id": "checkout_789" } ] } }

The quote carries the whole payment in payment_quote and each shop’s checkout with its share under checkouts[].payment_quote. The purchase returns orders, one per shop. The buyer’s spend caps apply to the combined total.

Paying together is crypto only. A checkout that redeems an Artos coupon, shops in different currencies, or shops that share a payout wallet return combined_unsupported with nothing charged: pay those one by one. In the rare case a shop fails after the payment went through, the result reports which orders were placed along with the transaction_digest. Call confirm_purchase with the failed checkouts and that transaction_digest to place them without paying again.

Build — 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({ checkouts: [{ storeSlug: 'energy-sport', checkoutId: 'checkout_456' }], paymentMethod: 'artos.crypto', // or 'artos.card'; omit to be prompted }); // outcome.status: 'completed' | 'escalation_required' // | 'payment_selection_required' | 'quote_changed' | 'error'

To lock a crypto price before the buyer approves, call preparePurchase with the same input and pass its quote.quoteId as quoteId:

const prep = await shop.preparePurchase({ checkouts: [{ storeSlug: 'energy-sport', checkoutId: 'checkout_456' }], paymentMethod: 'artos.crypto', }); if (prep.status === 'quoted') { // Show every prep.quote.legs coin and prep.quote.expiresAt, then on approval: await shop.confirmPurchase({ checkouts: [{ storeSlug: 'energy-sport', checkoutId: 'checkout_456' }], paymentMethod: 'artos.crypto', quoteId: prep.quote.quoteId, }); // 'quote_changed' carries the new quote; nothing was signed }

Pass one checkout per shop to pay several at once: preparePurchase returns quoted_together, and confirmPurchase signs once and returns completed_together with an order per shop (or partially_completed with the transactionDigest to retry the rest with).

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_id or a line_items array, 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.

  1. 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 defaults to auto (the wallet order above). Pass a coin type such as 0x2::sui::SUI, or a symbol the store accepts such as SUI (any case), to pay with that coin only. Calling again with another coin replaces the prepared payment. A coin missing from the store’s accepted_coins returns an unsupported_coin error message, and a named coin without enough DeepBook liquidity for the amount returns insufficient_liquidity.

  1. 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).
  2. 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.

Payment handoff (continue_url)

When a checkout completes on the card rail without a payment token (or hits a 3-DS step), the server returns status: requires_escalation and a continue_url — the hosted page where the buyer enters their card. Forward that link and the buyer finishes payment themselves. This is the basis of the Checkout handoff guide.

Be precise about which continue_url you mean:

Responsecontinue_url is…
Cart / incomplete checkouta “continue in browser” link to that resource
Checkout with requires_escalationthe hosted payment page (hand this off)
Completed / cancelled checkoutomitted (the session is terminal)

A continue_url on a still-incomplete checkout is not a payment link and not proof of payment.

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 kid from the platform profile keys and verifies the ES256 signature;
  • requires exp (missing/past → mandate_expired) and a jti (replays rejected);
  • requires the merchant binding 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, USDC in the wallet pays any remainder directly. A remainder paid in another coin 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 when the wallet has no USDC, 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 (in USDC, or above DeepBook’s minimum in another coin) or card.

Last updated on