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 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 idartos.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.

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 the payment_selection_required prompt.
  • 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_escalation with a continue_url the 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_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": "0x2::sui::SUI" }'
  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.

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 signing_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, 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.

Last updated on