Skip to content

feat(ucp): negotiate payment method on checkout completion, escalate to browser handoff when none is supported - #152

Open
Lukas Rump (lukasrump) wants to merge 1 commit into
mainfrom
feat/ucp-payment-method-negotiation
Open

feat(ucp): negotiate payment method on checkout completion, escalate to browser handoff when none is supported#152
Lukas Rump (lukasrump) wants to merge 1 commit into
mainfrom
feat/ucp-payment-method-negotiation

Conversation

@lukasrump

Copy link
Copy Markdown
Contributor

What & why

Today the checkout completer places an order regardless of whether the client can
actually pay through any handler the shop advertises. For x402 (order-first,
settle-after) that's by design, but it means a client that supports none of the
advertised payment_handlers either ends up with a placed, never-payable order or
no path forward.

This adds a deterministic, opt-in payment-method negotiation:

  • The client commits a handler via CheckoutUpdateRequest.payment (PaymentInstrument).
  • On completion, if the committed handler is registered in the SDK
    PaymentHandlerRegistry, the order is placed as today.
  • Otherwise (no commitment, or an unsupported/declined handler) the checkout is
    returned as requires_escalation with a continue_url and no order is placed
    the UCP-standard fallback that lets a human finish in a browser.

It's driven by what the client agrees to pay with, not by what the shop offers, so
the shop never silently places an order (e.g. an unpaid invoice order) against a method
the agent didn't choose.

Opt-in / non-breaking

Gated behind a per-channel policy requireCommittedPaymentMethod (default off):

  • off (default): unchanged behaviour — completion proceeds without a committed
    payment method (spec-conformant: the UCP payment object is optional).
  • on: completion requires a committed, settle-able handler; otherwise it escalates.

So this changes nothing unless a merchant opts in — suitable for agent-only channels
that must not accumulate phantom unpaid orders. The flag is read via SystemConfigService
(SwagAgenticCommerce.config.requireCommittedPaymentMethod); an admin toggle (config.xml)
is an easy follow-up.

Spec alignment

  • PaymentInstrument on update and payment_handlers advertisement are standard UCP.
  • requires_escalation + continue_url is the spec's prescribed fallback for a
    capability/negotiation failure.
  • Payment remains optional per the spec; the strict behaviour is opt-in only.

Scope

  • Ucp/Checkout/CheckoutSessionStore.php, CheckoutSessionManager.php — persist/read the committed handler id.
  • Ucp/Adapter/ShopwareCheckoutAdapter.php — capture the commitment on update; policy-gated escalation on complete.
  • Tests + docs/payment-method-negotiation.md.
  • continue_url can point at any merchant checkout; a signed cart-adopting handoff route is deployment-specific and intentionally not included here.

Coordination note

feat/ucp-ap2-mandates reworks completion to a CheckoutCompleteRequest /
verifiedCheckout API. This targets main; once that branch lands, the escalation
gate should move into the request-based flow. Happy to rebase/adapt.

Testing

Unit test added for the escalation path (policy on + no supported handler committed ⇒
requires_escalation, completer never invoked → no order). Please run PHPStan/PHPUnit in
CI — the branch was authored without a local PHP toolchain.

@lukasrump
Lukas Rump (lukasrump) force-pushed the feat/ucp-payment-method-negotiation branch from 8ff49e4 to e0596ed Compare July 29, 2026 12:52
Place an order on completeCheckout only when the client committed a payment handler the shop can settle (registered in the SDK PaymentHandlerRegistry). When the per-channel policy requireCommittedPaymentMethod is enabled and no supported handler was committed, return the checkout as requires_escalation with a continue_url and place no order — the UCP-standard fallback that lets a human finish in a browser.

Off by default: completion proceeds unchanged (UCP treats the payment object as optional), so this is opt-in per channel and non-breaking.
@lukasrump
Lukas Rump (lukasrump) force-pushed the feat/ucp-payment-method-negotiation branch from e0596ed to a65bb2b Compare July 29, 2026 13:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant