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:
- CreateCheckoutSession — Google sends the product and user context. Firmly initializes a cart on the merchant’s platform and returns session state.
- UpdateCheckoutSession — Google sends shipping address, shipping method, or promo changes. Firmly updates the cart and returns new totals and available options.
- 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:
- Generates a
device_idviacrypto.randomUUID()(this becomes the session ID) - Calls
POST /api/v2/domains/{domain}/cart/line-items?flush_cart=falseto create the cart - Stores the session mapping in the session store
- 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:
- Retrieves the session from the session store
- Applies the stored buyer info and payment data
- Calls
POST /api/v1/domains/{domain}/cart/complete-order - 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 |
Related
- UCP overview — what UCP is and why Firmly implements it
- UCP implementation — service architecture, API mapping
- UCP security — how requests are authenticated and protected