Skip to content

[Proposal]: Authenticated checkout sessions have no way to surface buyer-specific payment instruments #358

@appdrops

Description

@appdrops

Summary

After identity linking (OAuth 2.0), the checkout response's payment.handlers only reflects store-level payment capabilities (Stripe, delegate). There is no mechanism to surface buyer-specific instruments that become available after authentication — wallet balance, saved cards, loyalty points, store credit. The agent can see WHO the buyer is (buyer object is populated) but not WHAT they can pay with. This blocks autonomous checkout for authenticated sessions, forcing escalation to the store's checkout page every time.

Motivation

This is the missing link between identity linking and autonomous checkout. In live testing between UCP Playground and a WooCommerce store (UCPReady), an identity-linked buyer with wallet balance hit this wall:

  1. Identity works — checkout response returns buyer: {first_name: "Jane", email: "jane@example.com"}
  2. But payment.instruments: [] — empty, no wallet surfaced
  3. Agent tries the only declared handler (dev.ucp.delegate_payment) — store rejects it: "Selected payment method is not available"
  4. Session escalates to continue_url — autonomous checkout blocked

We propose adding payment.available_instruments to checkout responses for authenticated sessions — a per buyer, per-session list of usable payment methods resolved at runtime. This is distinct from #187 (store-level available_instruments in handler config). The target users are agent platforms that need to complete checkout programmatically after identity linking.

*Co-authored with @zologic (Almin Zolotic)

Goals

  • Authenticated checkout responses include payment.available_instruments — a per-buyer list of usable payment methods (wallet, saved cards, loyalty) resolved at runtime from the identity-linked session
  • Agents can select a buyer-specific instrument via update_checkout with selected_instrument_id without trial-and-error or escalation
  • Guest checkouts are unaffected — available_instruments returns empty for unauthenticated sessions

Non-Goals

  • Not changing store-level payment_handlers in the manifest — that's generic capability declaration and stays as-is
  • Not defining how merchants tokenize or secure payment credentials — that's handler-specific (Stripe, PayPal, etc.)
  • Not replacing feat: add available_instruments to payment handler configurations #187 (available_instruments at handler config level) — this proposal is per-buyer, per-session, complementary
  • Not requiring merchants to support available_instruments — it's optional, only populated for authenticated sessions

Detailed Design

Data Structures

Add available_instruments array to the checkout response payment object:

"payment": {                                                                                                                                                         
    "handlers": [ /* unchanged — store-level */ ],                                                                                                                   
    "instruments": [ /* unchanged — currently selected */ ],                                                                                                         
    "selected_instrument_id": "wallet-001",                                                                                                                     
    "available_instruments": [                                                                                                                                       
        {                                                                                                                                                            
            "id": "wallet-001",                                                                                                                                      
            "handler_id": "store-wallet",                                                                                                                            
            "type": "store_credit",                                                                                                                                  
            "label": "Store Wallet",                                                                                                                                 
            "balance": { "amount": 5000, "currency": "EUR" },                                                                                                        
            "sufficient": true                                                                                                                                       
        },                                                                                                                                                           
        {                                                                                                                                                            
            "id": "saved-card-visa-4242",                                                                                                                            
            "handler_id": "handler-stripe-xxx",                                                                                                                      
            "type": "saved_card",                                                                                                                                    
            "label": "Visa ending in 4242",                                                                                                                          
            "brand": "visa",                                                                                                                                         
            "last_digits": "4242"                                                                                                                                    
        }                                                                                                                                                            
    ]                                                                                                                                                                
}                                                                                                                                                                    

Each instrument has:

  • id — unique identifier for selection
  • handler_id — links to parent payment handler
  • type — enum: store_credit, saved_card, loyalty, bnpl, gift_card
  • label — human-readable display name
  • balance / sufficient — optional, for balance-based instruments
  • brand / last_digits — optional, for card-type instruments

API Changes

create_checkout / get_checkout response — add payment.available_instruments array. Only populated when the session is authenticated via identity linking.

Empty array for guest sessions.

update_checkout request — no schema change needed. Agents select an instrument by setting payment.selected_instrument_id to an id from available_instruments. The existing field already supports this.

Behavioral Changes

  • Merchants resolve available_instruments at checkout creation time based on the authenticated buyer's account (stored cards, wallet balance, loyalty tier)
  • The list MAY change between create_checkout and update_checkout calls (e.g., wallet balance changes) - agents should re-read from get_checkout if needed
  • If available_instruments is empty or absent, the agent falls back to store-level payment.handlers as today (no breaking change)

Risks and Mitigations

Security: available_instruments exposes buyer-specific payment data (wallet balance, saved card last digits). Mitigation: this data only appears in authenticated sessions — the buyer already consented via OAuth. Instrument IDs are opaque references, not credentials. No card numbers or tokens are exposed — only display metadata (last 4 digits, brand, label). Merchants MUST NOT include full card numbers or sensitive credentials in this field.

Performance: Merchants must resolve buyer instruments at checkout creation time, which may require additional database lookups (wallet balance, saved cards). Mitigation: this is a single query at create_checkout time, same request lifecycle. Merchants already resolve buyer identity in authenticated sessions — instrument lookup piggybacks on that. No additional round trips for the agent.

Backward Compatibility: No breaking change. available_instruments is a new optional field on the checkout response. Existing clients that don't read it are unaffected. Guest checkouts return an empty array (or omit the field entirely). Agents that don't understand it fall back to payment.handlers as they do today.

Complexity: Adds one new array field to the checkout response schema. The selection mechanism reuses the existing selected_instrument_id field — no new API endpoints or tool schemas required. Merchant implementation complexity is proportional to the payment methods they support (wallet = 1 query, saved cards = 1 query to Stripe/vault).

Test Plan

Unit Tests:

  • Checkout response schema validation — available_instruments array is present and well-formed for authenticated sessions
  • available_instruments is empty array or absent for guest (unauthenticated) sessions
  • Each instrument has required fields: id, handler_id, type, label
  • selected_instrument_id accepts an ID from available_instruments via update_checkout
  • Selecting an invalid instrument ID returns a structured error

Integration Tests:

  • Identity linking (OAuth) → create_checkout → verify available_instruments contains buyer's stored payment methods
  • update_checkout with selected_instrument_id from available_instruments → verify checkout status transitions toward ready_for_complete
  • Same checkout flow without identity linking → verify available_instruments is empty and behavior falls back to payment.handlers
  • Instrument with sufficient: false (insufficient wallet balance) → verify checkout rejects or requests supplementary payment

End-to-End Tests:

  • Full agent flow: identity link → search → create checkout → read available_instruments → select wallet → complete checkout → order created
  • Agent receives empty available_instruments → correctly falls back to delegate payment handler → escalates to continue_url
  • UCP Playground can be used as the test harness — it already records full tool call sequences, checkout responses, and outcomes per session

Graduation Criteria

Working Draft → Candidate:

  • Schema merged and documented (with Working Draft disclaimer).
  • Unit and integration tests are passing.
  • Initial documentation is written.
  • TC majority vote to advance.

Candidate → Stable:

  • Adoption feedback has been collected and addressed.
  • Full documentation and migration guides are published.
  • TC majority vote to advance.

Implementation History

  • [YYYY-MM-DD]: Proposal submitted.
  • [YYYY-MM-DD]: TC approved "Provisional"; capability enters "Working Draft".
  • [YYYY-MM-DD]: TC approved advancement to "Candidate".
  • [YYYY-MM-DD]: TC approved "Implemented"; capability enters "Stable".

Code of Conduct

  • I agree to follow this project's Code of Conduct

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions