Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 55 additions & 0 deletions docs/specification/embedded-cart.md
Original file line number Diff line number Diff line change
Expand Up @@ -361,6 +361,8 @@ proceed to initiate a checkout session based on the completed cart by issuing a
- **Type:** Notification
- **Payload:**
- `cart` (object, **REQUIRED**): The final state of the cart.
- `transition` (object, **OPTIONAL**): Advertisement for availability to transition
directly into Embedded Checkout.

**Example Message:**

Expand All @@ -381,6 +383,59 @@ proceed to initiate a checkout session based on the completed cart by issuing a
}
```

Business **MAY** also choose to specify a `transition` field to advertise a mechanism
for hosts to seamlessly transition into Embedded Checkout from the completed
Embedded Cart. This is represented by `transition` containing a minimal `checkout` object:

- `ucp` (object, **REQUIRED**): Metadata to fully qualify the advertisement.
Business **MUST** include a checkout capability and an embedded service binding
with `config.delegate`.
- `url` (string, **REQUIRED**): URL representing shift to checkout. Business **MUST**
specify a HTTPS URL and **MAY** choose a stateless URL like permalink. See
[Checkout Capability - Continue URL Format](checkout.md#format).

**Example Message With `transition`:**

```json
{
"jsonrpc": "2.0",
"method": "ep.cart.complete",
"params": {
"cart": { ...cart fields... },
"transition": {
"checkout": {
"ucp": {
"version": "{{ ucp_version }}",
"status": "success",
"capabilities": {
"dev.ucp.shopping.checkout": [ { "version": "{{ ucp_version }}" } ]
},
"services": {
Comment thread
jingyli marked this conversation as resolved.
"dev.ucp.shopping": [
{
"version": "{{ ucp_version }}",
"transport": "embedded",
"config": {
"delegate": [...]
}
}
]
}
},
"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?

}
}
}
}
```

When `transition` is received as part of `ep.cart.complete`, the host **MAY** choose
to initiate Embedded Checkout by appending `checkout.url` with relevant parameters -
see [Embedded Checkout - Loading an Embedded Checkout URL](embedded-checkout.md#loading-an-embedded-checkout-url).
Host also has full flexibility over how they want to render Embedded Checkout (i.e.
reuse the same embedded context as Embedded Cart or tear down the current one in favour
of bootstrapping a new embedded context).

### State Change Messages

State change notifications follow the shared EP pattern — see
Expand Down
3 changes: 2 additions & 1 deletion docs/specification/embedded-checkout.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,10 +131,11 @@ indicate ECP availability and allowed delegations for a specific session.
```json
{
"id": "checkout_abc123",
"status": "open",
"status": "incomplete",
"continue_url": "https://merchant.example.com/checkout/abc123",
"ucp": {
"version": "{{ ucp_version }}",
"status": "success",
"services": {
"dev.ucp.shopping": [
{
Expand Down
23 changes: 23 additions & 0 deletions source/services/shopping/embedded.openrpc.json
Original file line number Diff line number Diff line change
Expand Up @@ -496,6 +496,29 @@
"$ref": "../../schemas/shopping/cart.json",
"description": "Final cart state."
}
},
{
"name": "transition",
"schema": {
"type": "object",
"description": "Optional advertisement from business for seamless transition from cart to checkout.",
"required": ["checkout"],
"properties": {
"checkout": {
"type": "object",
"description": "Metadata required by the host to bootstrap and transition into an embedded checkout session.",
"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.

"type": "string",
Comment thread
jingyli marked this conversation as resolved.
"format": "uri",
"description": "The transition URL."
}
}
}
}
}
}
]
},
Expand Down
Loading