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

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": { "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.

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({ 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.

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