Docs
Firmly Agentic Commerce
Set theme to dark (⇧+D)

UCP Checkout Flow

This page walks through how a purchase works through UCP — from the moment Google initiates a checkout to the moment the order is placed on the merchant’s platform.

​​ Session model

UCP uses a three-step checkout session lifecycle. Each step maps directly to Firmly’s existing cart and checkout APIs:

  1. CreateCheckoutSession — Google sends the product and user context. Firmly initializes a cart on the merchant’s platform and returns session state.
  2. UpdateCheckoutSession — Google sends shipping address, shipping method, or promo changes. Firmly updates the cart and returns new totals and available options.
  3. CompleteCheckoutSession — Google sends payment authorization. Firmly tokenizes the payment, places the order on the merchant’s platform, and returns confirmation.

The user never leaves Google’s surface during this flow. Google owns the UI; Firmly owns the commerce backend.

​​ Step 1 — Create session

Google calls POST /checkout-sessions with line items and buyer context. Firmly:

  1. Generates a device_id via crypto.randomUUID() (this becomes the session ID)
  2. Calls POST /api/v2/domains/{domain}/cart/line-items?flush_cart=false to create the cart
  3. Stores the session mapping in the session store
  4. Returns the UCP-formatted session response

The session starts in incomplete status — shipping address, shipping method, and buyer email are still missing.

Line item limitation: cart contents are fixed at session creation. UCP does not allow adding, removing, or changing item quantities via the update endpoint. The user finalizes product selection on the Google AI surface before the checkout session begins.

Modifiable via Update? Fields
Yes Shipping address, shipping method, buyer info, payment instrument
No Line items, quantities

​​ Step 2 — Update session

Google calls PUT /checkout-sessions/{id} as the user fills in checkout details. The wrapper inspects which fields changed and routes to the appropriate Firmly APIs:

Field present What Firmly does
fulfillment.methods[].destinations[] (with selected_destination_id) Calls POST /cart/shipping-info — but only when the address is complete and differs from the last submitted address (see buffering below)
fulfillment.methods[].groups[].selected_option_id Calls POST /cart/shipment/methods with the shipment_id + shipping_method_id
discounts.codes Applies promo codes via POST / DELETE /cart/promo-codes (replacement semantics; an empty array clears all)
buyer Held in session state — used later during order completion
payment Held in session state — used later during order completion

Shipping info buffering — Google may send partial address updates as the user types. The wrapper buffers these in pending_shipping_info and only calls the shipping-info API when the address is complete and different from submitted_shipping_info. This avoids unnecessary backend calls during partial entry.

​​ Step 3 — Complete order

Google calls POST /checkout-sessions/{id}/complete when the user confirms. Firmly:

  1. Retrieves the session from the session store
  2. Applies the stored buyer info and payment data
  3. Calls POST /api/v1/domains/{domain}/cart/complete-order
  4. Returns the UCP-formatted order confirmation

The session transitions to completed — a terminal state.

​​ Session lifecycle

Sessions progress through these states as the checkout advances:

Status Meaning
incomplete Initial state — missing required fields (buyer email, shipping address, or shipping method)
ready_for_complete All required fields present, ready for order completion
complete_in_progress Order completion is underway
completed Order successfully placed (terminal)
canceled Session explicitly canceled (terminal)
requires_escalation Needs human intervention

The session can move back from ready_for_complete to incomplete if a required field is removed (for example, the user clears the shipping address). Both incomplete and ready_for_complete can be canceled at any time.

​​ Discount support

Discount codes are handled through the checkout session’s update step (and can also be supplied at creation). Firmly maps UCP discounts.codes to the merchant’s promo-code endpoints:

UCP operation Firmly API
Apply / replace discount codes POST /api/v2/domains/{domain}/cart/promo-codes
Clear all discount codes DELETE /api/v2/domains/{domain}/cart/promo-codes
Get applied discounts Included in the GET /api/v2/domains/{domain}/cart response (coupons)

discounts.codes uses replacement semantics: the submitted array replaces the currently applied set, and an empty array clears all codes. Codes are matched case-insensitively and de-duplicated; a strict superset of the current set is applied additively, and any other change is performed as a clear-and-re-add.

A rejected code does not fail the update — Firmly returns the updated cart with a recoverable warning in messages[]. Rejection codes include discount_code_invalid, discount_code_user_ineligible, discount_code_combination_disallowed, and discount_service_unavailable.

​​ External references

Resource Description
UCP REST Binding REST API specification for the checkout session endpoints
Native Checkout Guide How native checkout works on Google’s surfaces
Checkout Session Lifecycle Google’s documentation on session states and transitions
Fulfillment & Shipping How shipping options and address collection work in UCP