Skip to content

feat: Add seamless checkout transition functionality to ECaP #383

Open
jingyli wants to merge 4 commits intoUniversal-Commerce-Protocol:mainfrom
jingyli:ecap-transition
Open

feat: Add seamless checkout transition functionality to ECaP #383
jingyli wants to merge 4 commits intoUniversal-Commerce-Protocol:mainfrom
jingyli:ecap-transition

Conversation

@jingyli
Copy link
Copy Markdown
Contributor

@jingyli jingyli commented Apr 24, 2026

Description

Follow-up to feedback received on #244. We need some mechanism to allow host to facilitate seamless transition from Embedded Cart to Embedded Checkout without needing to renegotiate via create_checkout if they would like.

Category (Required)

  • Core Protocol: Changes to the base communication layer, global context, or breaking refactors. (Requires Technical Council approval)
  • Governance/Contributing: Updates to GOVERNANCE.md, CONTRIBUTING.md, or CODEOWNERS. (Requires Governance Council approval)
  • Capability: New schemas (Discovery, Cart, etc.) or extensions. (Requires Maintainer approval)
  • Documentation: Updates to README, or documentations regarding schema or capabilities. (Requires Maintainer approval)
  • Infrastructure: CI/CD, Linters, or build scripts. (Requires DevOps Maintainer approval)
  • Maintenance: Version bumps, lockfile updates, or minor bug fixes. (Requires DevOps Maintainer approval)
  • SDK: Language-specific SDK updates and releases. (Requires DevOps Maintainer approval)
  • Samples / Conformance: Maintaining samples and the conformance suite. (Requires Maintainer approval)
  • UCP Schema: Changes to the ucp-schema tool (resolver, linter, validator). (Requires Maintainer approval)
  • Community Health (.github): Updates to templates, workflows, or org-level configs. (Requires DevOps Maintainer approval)

Related Issues

Not an issue, but related to #244.

Checklist

  • I have followed the Contributing Guide.
  • I have updated the documentation (if applicable).
  • My changes pass all local linting and formatting checks.
  • (For Core/Capability) I have included/updated the relevant JSON schemas.
  • I have regenerated Python Pydantic models by running generate_models.sh under python_sdk.

@jingyli jingyli requested review from a team as code owners April 24, 2026 21:11
@jingyli jingyli requested review from archrao and westeezy April 24, 2026 21:11
@jingyli jingyli added this to the Working Draft milestone Apr 24, 2026
Comment thread docs/specification/embedded-cart.md
Comment thread source/services/shopping/embedded.openrpc.json Outdated
Comment thread source/services/shopping/embedded.openrpc.json
"required": ["ucp", "url"],
"properties": {
"ucp": { "$ref": "#/components/schemas/ucp_success" },
"url": {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While a generic uri format permits http://, allowing an unencrypted handoff at the cart-to-checkout inflection point is something we should avoid. It leaves session parameters and any delegated authority contexts wide open to interception.

One solution would be to introduce a reusable secure_url type as a schema components. We can enforce ^https:// to guarantee production integrity, while explicitly whitelisting localhost so local dev remains frictionless.

Any thoughts? Doesn't need to be part of this PR, I can create a standalone proposal.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are a couple of places in UCP today that emphasize the use of https:

I tightened the language of url in the spec documentation to also call out that it MUST be a HTTPS URL. I think the idea of enforcing it via schema directly makes sense, though agree with you that it can be treated as a standalone proposal/refactoring and should be consistently applied over all UCP URL fields.

Comment thread docs/specification/embedded-cart.md Outdated
Business **MUST** include a checkout capability and an embedded service binding
with `config.delegate`.
- `url` (string, **REQUIRED**): URL representing shift to checkout. Business **MAY** choose
to specify a redirect URL without having the actual checkout session created.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How I interpret the "Business MAY choose" case is that the business would advertise a URL like:

https://merchant.com/.../checkout/start?cart_id=cart_999

This effectively creates a lazy initiation mechanism, and allows the host to transition into checkout without a preprovisioned session.

However, the ECP spec (Section 3.1.2 and 3.2) is built on the assumption that the host has already received a JSON 'Checkout Response' containing the id before the iframe is loaded. By allowing a direct transition in embedded-cart.md without this response, we are potentially creating a gap.

Since the Binding Contract (Section 3.3.3) starts at the handshake, the host needs that checkout id immediately to anchor its orchestration, logging, and validation. Relying on the subsequent ec.start notification is too late, as it leaves a race condition where delegated actions can be triggered before the host has a session reference.

Recommendation: Require that the id (checkout_id) MUST be included in the ec.ready request params. This shifts the id exchange earlier, and should allow for accommodation of this use case.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

However, the ECP spec (Section 3.1.2 and 3.2) is built on the assumption that the host has already received a JSON 'Checkout Response' containing the id before the iframe is loaded. By allowing a direct transition in embedded-cart.md without this response, we are potentially creating a gap.

(Preface: this was also the part that took me some time to wrap my head around)
I don't think we are necessarily creating a gap here. To initiate ECP today, we need to append certain parameters onto the continue_url we have received via the Checkout response (ref). However, there are 2 flavours of continue_url that are prescribed by UCP:

  • Server-side stateful URL (the one where checkout session is proactively created by the business)
  • Stateless permalinks (no actual session is created until the redirect lands)

There is nothing wrong reusing the pattern here and offer both URL formats for full flexibility. What it would mean is that the checkout resource would only be returned to the host for the first during ec.start in the case of a stateless permalink transition URL.

Relying on the subsequent ec.start notification is too late, as it leaves a race condition where delegated actions can be triggered before the host has a session reference.

I don't think this race condition is ever possible given ec.start is a core lifecycle message that signals the readiness of the checkout UI for interaction. Without it, buyer cannot use Embedded Checkout to trigger a delegated action.

}
}
},
"url": "https://merchant.example.com/cart-to-checkout/checkout123",
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Recommendation: We should not rely on the URL for (checkout) identity disambiguation.

While a merchant may bake an ID into the URL slug, that identity is opaque to the host's programmatic orchestration. To support agentic workflows and reliable logging, let's include the checkout id as an explicit field in the transition.checkout object (when known).

This makes it slightly more explicit that there is a JIT variant too where the id isn't provided until ec.ready.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To support agentic workflows and reliable logging, let's include the checkout id as an explicit field in the transition.checkout object (when known).

Would you be able to help me understand this argument a bit more? I actually feel like having an explicit id here is a little bit weirder given:

  • This would be the first time where the identifier of a core capability can be optional in UCP schema.
  • If this id is set, does it mean that in theory (since embedded context can be concluded after receiving ep.cart.complete and host can move to a different transport implementation to continue with checkout): platforms have the ability to directly call update_checkout against this ID with a request payload and bypass create_checkout entirely?

@jingyli
Copy link
Copy Markdown
Contributor Author

jingyli commented Apr 30, 2026

Thanks @gsmith85 for the feedback! Comments are addressed and requesting another pass (especially on the 2 open threads around presence of checkout.id).

@jingyli jingyli requested a review from gsmith85 April 30, 2026 03:00
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.

2 participants