Skip to content
Draft
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
3 changes: 2 additions & 1 deletion .cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@
"sig1=:[A-Za-z0-9+/=_.]+:",
"eyJ[A-Za-z0-9_-]+\\.\\.?[A-Za-z0-9_-]*",
"M[A-Z][A-Za-z0-9]{3,}\\.\\.\\.",
"(\"cursor\"|cursor)\\s*:\\s*(\"[^\"]*\"|'[^']*')"
"(\"cursor\"|cursor)\\s*:\\s*(\"[^\"]*\"|'[^']*')",
"\"act_[A-Za-z0-9_]+\""
],
"dictionaryDefinitions": [
{
Expand Down
3 changes: 2 additions & 1 deletion .github/linters/.markdownlint.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,6 @@
},
"MD033": false,
"MD046": false,
"MD024": false
"MD024": false,
"MD060": false
}
80 changes: 80 additions & 0 deletions docs/specification/checkout.md
Original file line number Diff line number Diff line change
Expand Up @@ -451,6 +451,82 @@ The platform resolves the recoverable error programmatically while
rendering the allergen disclosure in proximity to the referenced line
item.

## Actions

Checkout responses may include [runtime actions](overview.md#runtime-actions) in
the top-level `actions` array. Runtime actions use the common UCP envelope
(`code`, `severity`, `config`); the owning capability, extension, or payment
handler defines the action's meaning, configuration schema, platform behavior,
security requirements, completion criteria, and resulting checkout updates.

This section only defines how the common action model applies to the checkout
lifecycle. General action ownership, discovery, severity, and schema-resolution
rules are defined in [Runtime Actions](overview.md#runtime-actions).

### Checkout Processing

Actions are distinct from messages. Messages describe checkout state and carry
buyer-facing content; actions are scoped runtime instructions. A checkout
response can contain both.

Checkout interprets action severity as follows:

| Severity | Checkout behavior |
| :------- | :---------------- |
| `optional` | Platform **MAY** ignore the action. Checkout can still complete successfully. |
| `required` | Platform **MUST** resolve the action before checkout can complete successfully, but the checkout remains otherwise usable. The platform may continue to update, retrieve, or cancel the checkout while the action is outstanding. |
| `blocking` | The action blocks the current attempted checkout transition, such as `complete_checkout`. The platform **MUST** resolve the action, choose an allowed alternate path, or escalate via `continue_url` before retrying that transition. |

When processing a checkout response, platforms should resolve terminal and
recoverable errors before actions. Optional actions can be attempted best-effort.
Required actions should be resolved before final completion. Blocking actions
should be handled immediately because the attempted transition cannot proceed
until the action resolves or the platform follows the owning specification's
fallback path.

If the platform cannot execute a required or blocking checkout action, it
**MUST** follow the fallback behavior defined by the owning action specification.
For checkout, that fallback is typically selecting a different payment
instrument, retrying with different checkout state, or escalating to the
business-hosted checkout via `continue_url`. When ECP is available for the
checkout, the platform **MAY** satisfy escalation by loading `continue_url` in an
embedded context.

Completion is defined by the owning action specification. For some actions, the
platform updates checkout state with a handler-defined field. For others, the
rendered surface or invoked provider reports directly to the business, and the
platform's next checkout call causes the business to observe that the external
step has completed.

### Examples

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

IIRC, there was a suggestion to also keep a non-3ds example to validate that the solution works more broadly. Student/military discounts and account verification were brought up as examples. It would be great if we could illustrate one such example as well.


Actions appear under the top-level `actions` array on the checkout response:

```json
{
"ucp": { "version": "{{ ucp_version }}", "status": "success" },
"id": "chk_abc123",
"status": "complete_in_progress",
"actions": [
{
"code": "dev.ucp.payment.three_ds_challenge",
"severity": "blocking",
"config": {
"url": "https://business.example.com/ucp/payment/3ds/session_456",
"timeout_seconds": 300
}
}
]
}
```

In this example, checkout does not define the URL or 3DS semantics. The active
payment handler adopts the standard
[3DS Challenge](payment-actions/three-ds-challenge.md) action as part of its
runtime contract, including how the platform executes the challenge, how
completion is observed, and what fallback applies if the action cannot be
executed.

## Continue URL

The `continue_url` field enables checkout handoff from platform to business UI,
Expand Down Expand Up @@ -736,6 +812,10 @@ field or omitting them.

{{ schema_fields('types/message_warning', 'checkout') }}

### Action

{{ schema_fields('types/action', 'checkout') }}

### Payment

{{ schema_fields('payment', 'checkout') }}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -405,6 +405,29 @@ Content-Type: application/json

---

## Runtime Payment Authentication Actions

This platform tokenizer example focuses on token issuance, binding, and
detokenization. It does not itself define device data collection or 3DS
challenge actions. Those actions are part of the runtime contract of the payment
handler or processing integration that owns payment authorization.

A card-processing handler that uses tokens produced by this tokenizer may adopt
standard UCP payment actions such as:

| Code | Purpose | Spec |
| :--- | :------ | :--- |
| `dev.ucp.payment.device_data_collection` | Run invisible device/browser data collection associated with a payment instrument or attempt. | [Device Data Collection](../payment-actions/device-data-collection.md) |
| `dev.ucp.payment.three_ds_challenge` | Present a buyer-facing 3DS challenge during payment completion. | [3DS Challenge](../payment-actions/three-ds-challenge.md) |

When those actions are emitted during checkout, the platform executes the action
according to the owning payment handler's runtime contract. The tokenizer token
and selected payment instrument are resubmitted unchanged after the action's
`action.done` message; UCP does not add DDC or 3DS result fields to the token
credential or payment instrument.

---

## PSP Integration

### Prerequisites
Expand Down
35 changes: 35 additions & 0 deletions docs/specification/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,41 @@ Extensions can be:
- **Official**: `dev.ucp.shopping.fulfillment` extends `dev.ucp.shopping.checkout`
- **Vendor**: `com.example.installments` extends `dev.ucp.shopping.checkout`

### Runtime Actions

A **runtime action** is a scoped instruction emitted during the lifecycle of a
UCP entity. Actions use a common envelope — `code`, `severity`, and `config` —
but are not independently negotiated as top-level capabilities. Instead, action
codes are part of the runtime surface of an active capability, extension, or
payment handler.

UCP defines only the generic envelope and severity model. The owning
capability, extension, or payment handler defines:

- What the action means and when it may be emitted
- The `config` schema and security requirements
- The platform behavior required to execute the action
- Completion criteria and how completion is reflected in the UCP entity
- Fallback behavior when the platform cannot execute the action

To avoid ambiguity, custom action codes **SHOULD** be namespaced beneath the
capability, extension, or payment handler that defines them.
If no active entity resolves the action code, the business **MUST NOT** use that action.

Action severity describes the consequence of not handling the action:

| Severity | Meaning |
| :------- | :------ |
| `optional` | The platform may ignore the action; the entity can still reach a successful terminal state. |
| `required` | The platform must resolve the action before the entity can reach a successful terminal state, but the entity remains otherwise usable. |
| `blocking` | The action blocks the current attempted transition; resolve it, choose an allowed alternate path, or escalate before retrying that transition. |

Entity specifications that expose actions **MUST** define how these generic
severity values apply to their lifecycle. For example, checkout defines how
actions interact with checkout status, `complete_checkout`, and `continue_url`.
Cart or other entities can reuse the same action envelope while defining their
own lifecycle-specific processing rules.

### Schema Composition

Extensions can add new fields and modify shared structures (e.g., discounts
Expand Down
195 changes: 195 additions & 0 deletions docs/specification/payment-actions/device-data-collection.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
<!--
Copyright 2026 UCP Authors

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->

# Payment Device Data Collection Action

This specification defines the standard payment device data collection action.
It uses the common [Runtime Actions](../overview.md#runtime-actions) envelope and
is intended for payment handlers that need the platform to run a scoped,
invisible browser-capable collection step before payment processing continues.

The action code is:

```text
dev.ucp.payment.device_data_collection
```

## Purpose

Device data collection lets a business, payment handler, PSP, or 3DS provider
collect device or browser data associated with a selected payment instrument or
payment attempt. UCP does not carry the collected data. The collection surface
communicates any domain-specific data to the business or payment provider through
its own integration. The platform only executes the scoped surface and resumes
the UCP operation when the surface signals that it is done.

## Runtime Shape

The action is emitted in an entity response using the common action envelope:

```json
{
"code": "dev.ucp.payment.device_data_collection",
"severity": "blocking",
"config": {
"url": "https://business.example.com/ucp/payment/ddc/session_123",
"timeout_seconds": 10
}
}
```

### Config

The config schema is defined at
[`source/schemas/payment/actions/device_data_collection.json`](site:schemas/payment/actions/device_data_collection.json).

| Field | Type | Required | Notes |
| :---- | :--- | :------- | :---- |
| `url` | string | ✓ | HTTPS URL for the invisible collection surface. The URL is loaded by the platform and owns any PSP/3DS-provider protocol details. |
| `timeout_seconds` | integer | optional | Number of seconds the platform should wait after loading the surface before treating the action as failed or abandoned. If absent, the platform may apply its own timeout. |

The `url` **MUST** be an absolute HTTPS URL. The business **SHOULD** provide a
business-owned or business-authorized URL rather than requiring the platform to
load a raw issuer, ACS, or PSP URL directly.

## Platform Behavior

The platform **MUST** load `config.url` in an isolated, invisible,
browser-capable surface. On web this is typically an iframe; on native platforms
it may be an equivalent isolated webview or browser surface. The surface should
not be visible to the buyer and should not require buyer interaction.

The platform **MUST NOT** parse, transform, or relay PSP-specific device data
payloads. Those payloads stay within the loaded surface and the business or
payment-provider integration.

## Embedded Transport

The embedded transport for payment action surfaces is defined by
[`source/services/payment-actions/embedded.openrpc.json`](site:services/payment-actions/embedded.openrpc.json).
Messages are JSON-RPC 2.0 notifications sent from the loaded action surface to
the platform using `postMessage` or the equivalent native bridge.

### Done Message

When collection is complete, the loaded surface **MUST** send a `pa.done`
notification to its parent/opener:

```json
{
"jsonrpc": "2.0",
"method": "pa.done",

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Hey @raginpirate while defining only a "pa.done" without any extra payload simplifies the work for the platform, please confirm if there are any security implications with this approach.

"params": {
"code": "dev.ucp.payment.device_data_collection"
}
}
```

The platform **MUST** correlate the message to the surface it mounted for the
outstanding action. On web, this means validating `event.source` against the
mounted frame and validating `event.origin` according to the owning handler's
security policy. The `code` value is not a trust boundary; it is a typed signal
that the mounted surface believes the action is complete.

### Error Message

If the loaded surface cannot continue, it **MAY** send a `pa.error`
notification:

```json
{
"jsonrpc": "2.0",
"method": "pa.error",
"params": {
"code": "dev.ucp.payment.device_data_collection",
"error": {
"ucp": { "version": "{{ ucp_version }}", "status": "error" },
"messages": [
{
"type": "error",
"code": "action_failed",
"content": "Device data collection failed.",
"severity": "recoverable"
}
]
}
}
}
```

The action has no UCP result payload. Completion means only that the platform can
resume the UCP operation. Domain-specific collection results are observed by the
business or payment provider outside the UCP action payload.

## Resuming the UCP Operation

After receiving the done message, the platform **MUST** resubmit the UCP request
that led to the action, using the same checkout/payment state it would have used
before the action was emitted. The platform **MUST NOT** add a device-data result
field to checkout or payment instruments unless another active capability or
handler explicitly defines such a field.

For checkout, this means:

- If the action was returned from `update_checkout`, call `update_checkout` again
with the same intended checkout state.
- If the action was returned from `complete_checkout`, call `complete_checkout`
again with the same selected payment instrument and credential.

The business then decides, using its payment-provider state, whether checkout can
proceed, whether another action is required, or whether payment should fail.

## Severity

This action may be emitted with any common action severity:

| Severity | Meaning |
| :------- | :------ |
| `optional` | The platform may skip collection. Checkout or payment processing can continue, but risk outcomes or frictionless authorization rates may degrade. |
| `required` | Collection must complete before the entity can reach a successful terminal state, but the entity remains otherwise usable. |
| `blocking` | The current attempted transition is blocked until collection completes, times out, or the platform follows the owning handler's fallback path. |

When device data collection is emitted in response to `complete_checkout`,
`blocking` is usually appropriate: the attempted payment transition cannot
finish until collection completes or the business chooses another path.

## Ignoring, Timeout, and Instrument Switching

If the action is `optional` and the platform ignores it, the business **MUST NOT**
treat the missing collection as a protocol error. The business may continue
without the data, emit a required/blocking collection action later, request a
challenge, decline the payment, or escalate according to the owning handler's
rules.

If the action times out or the surface fails to load, the platform follows the
fallback behavior defined by the owning payment handler. For checkout, the
platform may retry, select another payment instrument, or escalate via
`continue_url` when allowed.

If the platform submits a different selected payment instrument while a device
data collection session is outstanding for the previous instrument or payment
attempt, the business **MUST** treat the previous collection session as
superseded and stop using it for the new instrument or payment attempt.

## Security

The owning payment handler **MUST** define the trust policy for `config.url`,
including allowed origins or URL patterns when appropriate. Platforms **SHOULD**
apply that policy before loading the surface.

The collection surface should be isolated from the platform's own buyer session
and credentials. The platform should not grant capabilities beyond those required
by the owning payment handler's device-data collection contract.
Loading
Loading