From 471d24fee3b7d44f7f787a172e674116e20c21d9 Mon Sep 17 00:00:00 2001 From: SaifBenChahed Date: Thu, 21 May 2026 15:15:13 -0400 Subject: [PATCH] Add v1 additional fields extensions --- docs/documentation/core-concepts.md | 2 + .../additional-fields-advanced.md | 182 +++++++++ docs/specification/additional-fields.md | 315 +++++++++++++++ docs/specification/checkout-mcp.md | 4 + docs/specification/checkout.md | 13 + .../order-additional-fields-advanced.md | 113 ++++++ docs/specification/order-additional-fields.md | 111 ++++++ docs/specification/overview.md | 50 ++- docs/specification/playground.md | 32 +- mkdocs.yml | 21 + .../schemas/shopping/additional_fields.json | 242 ++++++++++++ .../shopping/additional_fields_advanced.json | 358 ++++++++++++++++++ 12 files changed, 1430 insertions(+), 13 deletions(-) create mode 100644 docs/specification/additional-fields-advanced.md create mode 100644 docs/specification/additional-fields.md create mode 100644 docs/specification/order-additional-fields-advanced.md create mode 100644 docs/specification/order-additional-fields.md create mode 100644 source/schemas/shopping/additional_fields.json create mode 100644 source/schemas/shopping/additional_fields_advanced.json diff --git a/docs/documentation/core-concepts.md b/docs/documentation/core-concepts.md index 711d6470a..8699c0630 100644 --- a/docs/documentation/core-concepts.md +++ b/docs/documentation/core-concepts.md @@ -181,6 +181,8 @@ up-to-date list. | Extension | Extends | Description | | :--- | :--- | :--- | +| `dev.ucp.shopping.additional_fields` | checkout, order | Merchant-configured checkout text fields and read-only order field summaries | +| `dev.ucp.shopping.additional_fields_advanced` | additional_fields | Advanced additional field types and validation hints | | `dev.ucp.shopping.discount` | checkout, cart | Discount codes and promotions | | `dev.ucp.shopping.fulfillment` | checkout | Shipping and delivery options | | `dev.ucp.shopping.ap2_mandate` | checkout | Non-repudiable authorization for autonomous commerce | diff --git a/docs/specification/additional-fields-advanced.md b/docs/specification/additional-fields-advanced.md new file mode 100644 index 000000000..4267b743e --- /dev/null +++ b/docs/specification/additional-fields-advanced.md @@ -0,0 +1,182 @@ + + +# Advanced Additional Fields Extension + +## Overview + +Advanced Additional Fields extension allows businesses to use additional input +types and validation hints for checkout additional fields. + +This extension extends +[`dev.ucp.shopping.additional_fields`](additional-fields.md). The base +extension defines the field envelope, the `text` type, and the `multiline` +hint for text. This extension adds validation hints for `text` and defines +`boolean`, `date`, and `choice`. + +**Key features:** + +- Add text validation hints: `min_length`, `max_length`, and `pattern` +- Add boolean fields represented as `"true"` or `"false"` +- Add date fields with optional `min` and `max` bounds +- Add choice fields with selectable `options[]` + +**Dependencies:** + +- Additional Fields Extension + +## Discovery + +Businesses advertise advanced additional field support in their profile. The +advanced capability extends the base additional fields capability: + +```json +{ + "ucp": { + "version": "{{ ucp_version }}", + "capabilities": { + "dev.ucp.shopping.additional_fields": [ + { + "version": "{{ ucp_version }}", + "extends": "dev.ucp.shopping.checkout", + "spec": "https://ucp.dev/{{ ucp_version }}/specification/additional-fields", + "schema": "https://ucp.dev/{{ ucp_version }}/schemas/shopping/additional_fields.json" + } + ], + "dev.ucp.shopping.additional_fields_advanced": [ + { + "version": "{{ ucp_version }}", + "extends": "dev.ucp.shopping.additional_fields", + "spec": "https://ucp.dev/{{ ucp_version }}/specification/additional-fields-advanced", + "schema": "https://ucp.dev/{{ ucp_version }}/schemas/shopping/additional_fields_advanced.json" + } + ] + } + } +} +``` + +Platforms SHOULD check that both the base and advanced capabilities are active +before submitting advanced additional field values. If the advanced capability +is not negotiated, businesses SHOULD only send `text` fields defined by the base +extension. The order read projection for advanced type identifiers is documented +in [Order Advanced Additional Fields](order-additional-fields-advanced.md). + +## Type Additions + +The base Additional Fields extension defines the shared `input` envelope and +value submission rules. This extension adds the following type-specific +semantics: + +| Type | What it adds | Example Value | +| :-------- | :---------------------------------------- | :------------- | +| `text` | Text validation hints | `"PO-12345"` | +| `boolean` | Boolean semantics represented as strings | `"true"` | +| `date` | Calendar date semantics and date bounds | `"2026-03-15"` | +| `choice` | Select-from-list semantics and `options[]` | `"birthday"` | + +### Text Input + +{{ extension_schema_fields('additional_fields_advanced.json#/$defs/text_input', 'additional-fields-advanced') }} + +### Boolean Input + +{{ extension_schema_fields('additional_fields_advanced.json#/$defs/boolean_input', 'additional-fields-advanced') }} + +### Date Input + +{{ extension_schema_fields('additional_fields_advanced.json#/$defs/date_input', 'additional-fields-advanced') }} + +### Choice Input + +{{ extension_schema_fields('additional_fields_advanced.json#/$defs/choice_input', 'additional-fields-advanced') }} + +### Choice Option + +{{ extension_schema_fields('additional_fields_advanced.json#/$defs/choice_option', 'additional-fields-advanced') }} + +## Operations + +This extension does not change checkout operations. Advanced additional field +values use the create and update behavior defined by the base +[Additional Fields Extension](additional-fields.md). + +## Examples + +### Checkout with advanced additional field definitions + +=== "Response" + + ```json + { + "additional_fields": [ + { + "key": "po_number", + "label": "Purchase Order Number", + "input": { "type": "text", "pattern": "^PO-\\d+$" }, + "required": true, + "description": "Required for B2B orders. Must match format PO-.", + "value": null + }, + { + "key": "gift_wrap", + "label": "Gift Wrap", + "input": { "type": "boolean" }, + "required": false, + "description": "Add gift wrapping for $5", + "value": null + }, + { + "key": "gift_wrap_type", + "label": "Gift Wrap Type", + "input": { + "type": "choice", + "options": [ + { "value": "classic", "label": "Classic" }, + { "value": "birthday", "label": "Birthday" }, + { "value": "holiday", "label": "Holiday" } + ] + }, + "required": false, + "description": "Select the gift wrap style", + "value": null + }, + { + "key": "delivery_date", + "label": "Preferred Delivery Date", + "input": { "type": "date", "min": "2026-03-15", "max": "2026-12-31" }, + "required": false, + "description": "Select a delivery date within the next 9 months", + "value": null + } + ] + } + ``` + +### Submitting advanced additional field values + +=== "Request" + + ```json + { + "additional_fields": [ + { "key": "po_number", "value": "PO-2026-001234" }, + { "key": "gift_wrap", "value": "true" }, + { "key": "gift_wrap_type", "value": "birthday" }, + { "key": "delivery_date", "value": "2026-04-01" } + ] + } + ``` diff --git a/docs/specification/additional-fields.md b/docs/specification/additional-fields.md new file mode 100644 index 000000000..ebdd50456 --- /dev/null +++ b/docs/specification/additional-fields.md @@ -0,0 +1,315 @@ + + +# Additional Fields Extension + +## Overview + +Additional Fields extension allows businesses to indicate that they need extra +checkout data collection fields, and specifies how field definitions and +submitted values are shared between the platform and the business. + +The base extension defines the additional field envelope and `text` input +semantics, including the `multiline` hint for multi-line text. Additional input +types and validation hints are defined by the +[Advanced Additional Fields Extension](additional-fields-advanced.md). + +**Key features:** + +- Receive business-defined required and optional text fields, including + multi-line text fields +- Submit additional field values as `key` plus `value` entries +- Preserve extensibility through an open `input.type` vocabulary +- Invalid values communicated via `messages[]` with detailed error codes + +**Dependencies:** + +- Checkout Capability + +## Discovery + +Businesses advertise additional field support in their profile. For checkout +data collection, the capability extends checkout: + +```json +{ + "ucp": { + "version": "{{ ucp_version }}", + "capabilities": { + "dev.ucp.shopping.additional_fields": [ + { + "version": "{{ ucp_version }}", + "extends": "dev.ucp.shopping.checkout", + "spec": "https://ucp.dev/{{ ucp_version }}/specification/additional-fields", + "schema": "https://ucp.dev/{{ ucp_version }}/schemas/shopping/additional_fields.json" + } + ] + } + } +} +``` + +Platforms SHOULD check that checkout is extended before submitting additional +field values. The order read projection for captured values is documented in +[Order Additional Fields](order-additional-fields.md). + +## Schema + +When this capability is active for checkout, checkout is extended with an +`additional_fields` array containing field definitions and current values. + +### Checkout Extension + +{{ extension_fields('additional_fields', 'additional-fields') }} + +### Additional Field + +{{ extension_schema_fields('additional_fields.json#/$defs/additional_field', 'additional-fields') }} + +## Input Type Details + +Each field definition includes an `input` object with a required `type` string. +Platforms SHOULD respect the declared type. Platforms that do not recognize a +type SHOULD NOT render it as a different supported type, coerce it to text, or +collect a value unless a negotiated extension defines that type. + +All non-null values are submitted as strings regardless of input type. + +### Additional Field Input + +{{ extension_schema_fields('additional_fields.json#/$defs/additional_field_input', 'additional-fields') }} + +### Text Input + +{{ extension_schema_fields('additional_fields.json#/$defs/text_input', 'additional-fields') }} + +## Extensibility + +The additional field envelope is independent from the input type vocabulary: + +- `additional_fields[].input.type` is an open string, not a closed enum. +- The base extension defines only `text` semantics. +- Future UCP or vendor extensions can extend + `dev.ucp.shopping.additional_fields` and introduce new type identifiers and + hints without changing the field envelope. + +For vendor-defined types, use the vendor namespace in the type identifier, for +example `com.example.file_upload`. + +## Operations + +Additional field values are submitted via standard checkout create and update +operations. + +**Request behavior:** + +- **Replacement semantics**: Submitting `additional_fields` replaces any + previously submitted additional field values +- **Retain values**: Platforms must include each field value they want to keep + in the replacement set +- **Clear values**: Omit a configured field from the replacement set, or send + `"value": null` for that field key +- **Request format**: Platforms submit only `key` and `value` for each entry. + Definition properties are ignored if present +- **Unknown values**: Entries whose `key` does not match a configured field are + ignored by the business + +**Response behavior:** + +- `additional_fields` contains all configured fields with current values merged + into each entry +- `value` is `null` for fields that have not been submitted or have been + cleared by replacement +- Invalid values communicated via `messages[]` (see below) + +## Invalid Values + +When a submitted additional field value cannot be accepted, businesses +communicate this via the `messages[]` array: + +```json +{ + "status": "incomplete", + "messages": [ + { + "type": "error", + "severity": "recoverable", + "code": "additional_field_invalid_value", + "path": "$.additional_fields[?@.key=='po_number']", + "content": "Purchase Order Number is required" + } + ] +} +``` + +> **Implementation guidance:** Required or invalid additional field values +> prevent checkout completion but can be corrected by the platform. Businesses +> SHOULD use `type: "error"` and `severity: "recoverable"` for these cases. + +| Severity | Scenario | Platform Action | +| :------------ | :------------------------------------------------------------ | :---------------------------------------- | +| `recoverable` | Value is missing, empty, or otherwise invalid | Resubmit with a corrected value via Update Checkout | + +**Error codes for additional field validation:** + +| Code | Description | +| :------------------------------- | :---------------------------------------------------------- | +| `additional_field_invalid_value` | Field value is missing, empty, or fails business validation | + +## Examples + +### Checkout with additional field definitions + +When a checkout is created or updated, the business response includes the +additional fields it needs. Values are `null` until submitted or when cleared by +replacement. + +=== "Response" + + ```json + { + "additional_fields": [ + { + "key": "po_number", + "label": "Purchase Order Number", + "input": { "type": "text" }, + "required": true, + "description": "Required for B2B orders.", + "value": null + }, + { + "key": "engraving", + "label": "Engraving Text", + "input": { "type": "text" }, + "required": false, + "description": "Custom engraving text.", + "value": null + }, + { + "key": "special_instructions", + "label": "Special Instructions", + "input": { "type": "text", "multiline": true }, + "required": false, + "description": "Any special delivery or handling instructions.", + "value": null + } + ] + } + ``` + +### Submitting additional field values + +The platform submits the full replacement set of additional field values it +wants to retain. Configured fields omitted from the request have no submitted +value after the update. + +=== "Request" + + ```json + { + "additional_fields": [ + { "key": "po_number", "value": "PO-2026-001234" }, + { "key": "engraving", "value": "Happy Birthday Sarah" }, + { + "key": "special_instructions", + "value": "Please leave at reception.\nRing buzzer #4." + } + ] + } + ``` + +=== "Response" + + ```json + { + "additional_fields": [ + { + "key": "po_number", + "label": "Purchase Order Number", + "input": { "type": "text" }, + "required": true, + "description": "Required for B2B orders.", + "value": "PO-2026-001234" + }, + { + "key": "engraving", + "label": "Engraving Text", + "input": { "type": "text" }, + "required": false, + "description": "Custom engraving text.", + "value": "Happy Birthday Sarah" + }, + { + "key": "special_instructions", + "label": "Special Instructions", + "input": { "type": "text", "multiline": true }, + "required": false, + "description": "Any special delivery or handling instructions.", + "value": "Please leave at reception.\nRing buzzer #4." + } + ] + } + ``` + +### Clearing a value + +Because `additional_fields` uses replacement semantics, the platform includes +the values it wants to retain. It can clear a field by sending `null` for that +key, or by omitting the configured field from the replacement set. + +=== "Request" + + ```json + { + "additional_fields": [ + { "key": "po_number", "value": "PO-2026-001234" }, + { "key": "engraving", "value": null } + ] + } + ``` + +=== "Response" + + ```json + { + "additional_fields": [ + { + "key": "po_number", + "label": "Purchase Order Number", + "input": { "type": "text" }, + "required": true, + "description": "Required for B2B orders.", + "value": "PO-2026-001234" + }, + { + "key": "engraving", + "label": "Engraving Text", + "input": { "type": "text" }, + "required": false, + "description": "Custom engraving text.", + "value": null + }, + { + "key": "special_instructions", + "label": "Special Instructions", + "input": { "type": "text", "multiline": true }, + "required": false, + "description": "Any special delivery or handling instructions.", + "value": null + } + ] + } + ``` diff --git a/docs/specification/checkout-mcp.md b/docs/specification/checkout-mcp.md index b4c51c8df..372f90ccd 100644 --- a/docs/specification/checkout-mcp.md +++ b/docs/specification/checkout-mcp.md @@ -139,6 +139,8 @@ Maps to the [Create Checkout](checkout.md#create-checkout) operation. * `checkout` ([Checkout](checkout.md#create-checkout)): **Required**. Contains the initial checkout session data and optional extensions. * Extensions (Optional): + * `dev.ucp.shopping.additional_fields`: [Additional Fields](additional-fields.md) + * `dev.ucp.shopping.additional_fields_advanced`: [Advanced Additional Fields](additional-fields-advanced.md) * `dev.ucp.shopping.buyer_consent`: [Buyer Consent](buyer-consent.md) * `dev.ucp.shopping.fulfillment`: [Fulfillment](fulfillment.md) * `dev.ucp.shopping.discount`: [Discount](discount.md) @@ -389,6 +391,8 @@ Maps to the [Update Checkout](checkout.md#update-checkout) operation. * `checkout` ([Checkout](checkout.md#update-checkout)): **Required**. Contains the updated checkout session data. * Extensions (Optional): + * `dev.ucp.shopping.additional_fields`: [Additional Fields](additional-fields.md) + * `dev.ucp.shopping.additional_fields_advanced`: [Advanced Additional Fields](additional-fields-advanced.md) * `dev.ucp.shopping.buyer_consent`: [Buyer Consent](buyer-consent.md) * `dev.ucp.shopping.fulfillment`: [Fulfillment](fulfillment.md) * `dev.ucp.shopping.discount`: [Discount](discount.md) diff --git a/docs/specification/checkout.md b/docs/specification/checkout.md index 007827bb0..3ec6dfb4f 100644 --- a/docs/specification/checkout.md +++ b/docs/specification/checkout.md @@ -51,6 +51,19 @@ Fulfillment is optional in the checkout object. This is done to enable a platform to perform checkout for digital goods without needing to furnish fulfillment details more relevant for physical goods. +### Additional Fields + +Additional checkout data collection is modelled as an extension. When the +[Additional Fields Extension](additional-fields.md) is negotiated, businesses +can return an `additional_fields` array containing field definitions and current +values. Platforms submit updates as `key` plus `value` entries through standard +checkout create and update operations. + +The base extension supports text fields, including multi-line text via the +`multiline` hint. Additional input types and validation hints are defined by +the [Advanced Additional Fields Extension](additional-fields-advanced.md), which +extends the base additional fields capability. + ### Checkout Status Lifecycle The checkout `status` field indicates the current phase of the session and diff --git a/docs/specification/order-additional-fields-advanced.md b/docs/specification/order-additional-fields-advanced.md new file mode 100644 index 000000000..7469104be --- /dev/null +++ b/docs/specification/order-additional-fields-advanced.md @@ -0,0 +1,113 @@ + + +# Order Advanced Additional Fields Extension + +## Overview + +Order Advanced Additional Fields is the order read projection of the +[Advanced Additional Fields Extension](additional-fields-advanced.md). It lets +order additional fields use the advanced input type identifiers negotiated for +checkout while keeping the order shape compact. + +Advanced order additional fields do not include checkout validation hints such +as `pattern`, `min`, or `max`. Choice fields include `input.value_label` for +the captured value label. + +**Key features:** + +- Surface advanced input type identifiers on order additional fields +- Include captured choice value labels +- Exclude checkout validation hints from order read responses + +**Dependencies:** + +- Order Additional Fields Extension + +## Discovery + +For order read responses, the base additional fields capability extends order +and the advanced capability extends the base additional fields capability: + +```json +{ + "ucp": { + "version": "{{ ucp_version }}", + "capabilities": { + "dev.ucp.shopping.additional_fields": [ + { + "version": "{{ ucp_version }}", + "extends": "dev.ucp.shopping.order", + "spec": "https://ucp.dev/{{ ucp_version }}/specification/order-additional-fields", + "schema": "https://ucp.dev/{{ ucp_version }}/schemas/shopping/additional_fields.json" + } + ], + "dev.ucp.shopping.additional_fields_advanced": [ + { + "version": "{{ ucp_version }}", + "extends": "dev.ucp.shopping.additional_fields", + "spec": "https://ucp.dev/{{ ucp_version }}/specification/order-additional-fields-advanced", + "schema": "https://ucp.dev/{{ ucp_version }}/schemas/shopping/additional_fields_advanced.json" + } + ] + } + } +} +``` + +Platforms SHOULD check that both the base and advanced capabilities are active +before interpreting advanced order additional field input type identifiers. + +## Operations + +This extension does not change order read behavior. It only broadens the +additional field `input.type` identifiers defined by +[Order Additional Fields](order-additional-fields.md). + +## Examples + +### Order with advanced additional field values + +```json +{ + "additional_fields": [ + { + "key": "gift_wrap", + "label": "Gift Wrap", + "description": "Add gift wrapping for $5", + "input": { "type": "boolean" }, + "value": "true" + }, + { + "key": "gift_wrap_type", + "label": "Gift Wrap Type", + "description": "Gift wrap style selected for the order.", + "input": { + "type": "choice", + "value_label": "Birthday" + }, + "value": "birthday" + }, + { + "key": "delivery_date", + "label": "Preferred Delivery Date", + "description": "Preferred delivery date.", + "input": { "type": "date" }, + "value": "2026-04-01" + } + ] +} +``` diff --git a/docs/specification/order-additional-fields.md b/docs/specification/order-additional-fields.md new file mode 100644 index 000000000..2c35890cc --- /dev/null +++ b/docs/specification/order-additional-fields.md @@ -0,0 +1,111 @@ + + +# Order Additional Fields Extension + +## Overview + +Order Additional Fields is the order read projection of the +[Additional Fields Extension](additional-fields.md). Checkout uses additional +fields to collect values; order uses compact read-only summaries of the values +captured during checkout. + +**Key features:** + +- Receive captured additional field values on order read responses +- Preserve field context with `key`, `label`, `label_content_type`, + `description`, and `input.type` +- Exclude checkout-only collection details such as `required` and validation + hints + +**Dependencies:** + +- Order Capability + +## Discovery + +For order read responses, the capability extends order: + +```json +{ + "ucp": { + "version": "{{ ucp_version }}", + "capabilities": { + "dev.ucp.shopping.additional_fields": [ + { + "version": "{{ ucp_version }}", + "extends": "dev.ucp.shopping.order", + "spec": "https://ucp.dev/{{ ucp_version }}/specification/order-additional-fields", + "schema": "https://ucp.dev/{{ ucp_version }}/schemas/shopping/additional_fields.json" + } + ] + } + } +} +``` + +Platforms SHOULD check that order is extended before expecting additional fields +on order read responses. + +## Schema + +When this capability is active for order, order is extended with an +`additional_fields` array containing compact read-only field summaries. + +### Order Additional Field + +{{ extension_schema_fields('additional_fields.json#/$defs/order_additional_field', 'order-additional-fields') }} + +### Order Additional Field Input + +{{ extension_schema_fields('additional_fields.json#/$defs/order_additional_field_input', 'order-additional-fields') }} + +## Operations + +Order additional fields are returned through order read responses and order +event payloads. They are read-only: + +- Businesses MAY return `additional_fields` with values captured during checkout +- Platforms do not update order additional fields through order operations +- Each entry contains only `key`, `label`, `label_content_type`, `description`, + `input`, and `value` +- Checkout collection details such as `required` and validation hints are not + included + +## Examples + +### Order with additional field values + +```json +{ + "additional_fields": [ + { + "key": "po_number", + "label": "Purchase Order Number", + "description": "Required for B2B orders.", + "input": { "type": "text" }, + "value": "PO-2026-001234" + }, + { + "key": "engraving", + "label": "Engraving Instructions", + "description": "Custom engraving text.", + "input": { "type": "text" }, + "value": null + } + ] +} +``` diff --git a/docs/specification/overview.md b/docs/specification/overview.md index 535c0ddbd..8eafaa4ac 100644 --- a/docs/specification/overview.md +++ b/docs/specification/overview.md @@ -70,12 +70,14 @@ All capability and service names **MUST** use the format: **Examples:** -| Name | Authority | Service | Capability | -| ----------------------------------- | ----------- | -------- | ---------------- | -| `dev.ucp.shopping.checkout` | ucp.dev | shopping | checkout | -| `dev.ucp.shopping.fulfillment` | ucp.dev | shopping | fulfillment | -| `dev.ucp.common.identity_linking` | ucp.dev | common | identity_linking | -| `com.example.payments.installments` | example.com | payments | installments | +| Name | Authority | Service | Capability | +| --------------------------------------------- | ----------- | -------- | -------------------------- | +| `dev.ucp.shopping.checkout` | ucp.dev | shopping | checkout | +| `dev.ucp.shopping.additional_fields` | ucp.dev | shopping | additional_fields | +| `dev.ucp.shopping.additional_fields_advanced` | ucp.dev | shopping | additional_fields_advanced | +| `dev.ucp.shopping.fulfillment` | ucp.dev | shopping | fulfillment | +| `dev.ucp.common.identity_linking` | ucp.dev | common | identity_linking | +| `com.example.payments.installments` | example.com | payments | installments | #### Spec URL Binding @@ -202,6 +204,8 @@ When an extension declares multiple parents: Extensions can be: +- **Official**: `dev.ucp.shopping.additional_fields` extends `dev.ucp.shopping.checkout` and `dev.ucp.shopping.order` +- **Official**: `dev.ucp.shopping.additional_fields_advanced` extends `dev.ucp.shopping.additional_fields` - **Official**: `dev.ucp.shopping.fulfillment` extends `dev.ucp.shopping.checkout` - **Vendor**: `com.example.installments` extends `dev.ucp.shopping.checkout` @@ -397,6 +401,22 @@ Businesses publish their profile at `/.well-known/ucp`. An example: "extends": "dev.ucp.shopping.checkout" } ], + "dev.ucp.shopping.additional_fields": [ + { + "version": "{{ ucp_version }}", + "spec": "https://ucp.dev/{{ ucp_version }}/specification/additional-fields", + "schema": "https://ucp.dev/{{ ucp_version }}/schemas/shopping/additional_fields.json", + "extends": ["dev.ucp.shopping.checkout", "dev.ucp.shopping.order"] + } + ], + "dev.ucp.shopping.additional_fields_advanced": [ + { + "version": "{{ ucp_version }}", + "spec": "https://ucp.dev/{{ ucp_version }}/specification/additional-fields-advanced", + "schema": "https://ucp.dev/{{ ucp_version }}/schemas/shopping/additional_fields_advanced.json", + "extends": "dev.ucp.shopping.additional_fields" + } + ], "dev.ucp.shopping.discount": [ { "version": "{{ ucp_version }}", @@ -509,6 +529,14 @@ example: "extends": "dev.ucp.shopping.checkout" } ], + "dev.ucp.shopping.additional_fields": [ + { + "version": "{{ ucp_version }}", + "spec": "https://ucp.dev/{{ ucp_version }}/specification/additional-fields", + "schema": "https://ucp.dev/{{ ucp_version }}/schemas/shopping/additional_fields.json", + "extends": ["dev.ucp.shopping.checkout", "dev.ucp.shopping.order"] + } + ], "dev.ucp.shopping.order": [ { "version": "{{ ucp_version }}", @@ -968,11 +996,11 @@ root capability. **Selection Examples:** -| Response Type | Includes | Does NOT Include | -| ------------- | ------------------------------- | ---------------------------- | -| Checkout | checkout, discount, fulfillment | cart, order | -| Cart | cart, discount | checkout, fulfillment, order | -| Order | order | checkout, cart, discount | +| Response Type | Includes | Does NOT Include | +| ------------- | --------------------------------------------------------------- | ---------------------------- | +| Checkout | checkout, additional_fields, additional_fields_advanced, discount, fulfillment | cart, order | +| Cart | cart, discount | checkout, fulfillment, order | +| Order | order, additional_fields, additional_fields_advanced | checkout, cart, discount | ## Identity & Authentication diff --git a/docs/specification/playground.md b/docs/specification/playground.md index 08db1d153..158eaaef0 100644 --- a/docs/specification/playground.md +++ b/docs/specification/playground.md @@ -527,6 +527,22 @@ const UcpData = { schema: "https://ucp.dev/{{ ucp_version }}/schemas/shopping/discount.json" } ], + "dev.ucp.shopping.additional_fields": [ + { + extends: ["dev.ucp.shopping.checkout", "dev.ucp.shopping.order"], + version: "{{ ucp_version }}", + spec: "https://ucp.dev/{{ ucp_version }}/specification/additional-fields", + schema: "https://ucp.dev/{{ ucp_version }}/schemas/shopping/additional_fields.json" + } + ], + "dev.ucp.shopping.additional_fields_advanced": [ + { + extends: "dev.ucp.shopping.additional_fields", + version: "{{ ucp_version }}", + spec: "https://ucp.dev/{{ ucp_version }}/specification/additional-fields-advanced", + schema: "https://ucp.dev/{{ ucp_version }}/schemas/shopping/additional_fields_advanced.json" + } + ], "dev.ucp.shopping.buyer_consent": [ { extends: "dev.ucp.shopping.checkout", @@ -553,8 +569,8 @@ const UcpData = { }, full: { label: "Full", - description: "Supports core + Fulfillment and Discount extensions.", - caps: ["dev.ucp.shopping.checkout", "dev.ucp.shopping.order", "dev.ucp.shopping.fulfillment", "dev.ucp.shopping.discount", "dev.ucp.shopping.buyer_consent", "dev.ucp.shopping.ap2_mandates"] + description: "Supports core checkout extensions.", + caps: ["dev.ucp.shopping.checkout", "dev.ucp.shopping.order", "dev.ucp.shopping.fulfillment", "dev.ucp.shopping.discount", "dev.ucp.shopping.additional_fields", "dev.ucp.shopping.additional_fields_advanced", "dev.ucp.shopping.buyer_consent", "dev.ucp.shopping.ap2_mandates"] } }, @@ -747,6 +763,14 @@ class UcpBackend { }; } + const additionalFields = (this.session.additional_fields || []).map(field => ({ + key: field.key, + label: field.label, + description: field.description, + type: field.input?.type || field.type || "text", + value: field.value ?? null + })); + const order = { ucp: { version: UcpData.version, @@ -761,6 +785,10 @@ class UcpBackend { totals: this.session.totals }; + if (additionalFields.length > 0) { + order.additional_fields = additionalFields; + } + this.session.status = "completed"; this.currentOrder = order; return order; diff --git a/mkdocs.yml b/mkdocs.yml index 3efe195e7..a2f781bc7 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -48,6 +48,9 @@ nav: - MCP: specification/checkout-mcp.md - A2A: specification/checkout-a2a.md - Embedded: specification/embedded-checkout.md + - Additional Fields Extension: + - Overview: specification/additional-fields.md + - Advanced Additional Fields Extension: specification/additional-fields-advanced.md - AP2 Mandates Extension: specification/ap2-mandates.md - Buyer Consent Extension: specification/buyer-consent.md - Discounts Extension: specification/discount.md @@ -71,6 +74,9 @@ nav: - Transports: - REST: specification/order-rest.md - MCP: specification/order-mcp.md + - Additional Fields Extension: + - Overview: specification/order-additional-fields.md + - Advanced Additional Fields Extension: specification/order-additional-fields-advanced.md - Identity Linking Capability: specification/identity-linking.md - Payment Handlers: - Guide: specification/payment-handler-guide.md @@ -299,6 +305,21 @@ plugins: per-session delegation negotiation, URL parameters, native UI event mappings, W3C Payment Request conceptual alignment, and core lifecycle messages. + - specification/additional-fields.md: >- + Additional Fields Extension, defining merchant-configured checkout + text fields, value submission semantics, replacement behavior, and + open input type extensibility. + - specification/additional-fields-advanced.md: >- + Advanced Additional Fields Extension, defining boolean, date, and + choice input types plus validation hints for additional checkout + fields. + - specification/order-additional-fields.md: >- + Order Additional Fields Extension, defining compact read-only + summaries of additional field values captured during checkout. + - specification/order-additional-fields-advanced.md: >- + Order Advanced Additional Fields Extension, defining compact + read-only order additional field summaries with advanced type + identifiers. - specification/ap2-mandates.md: >- AP2 Mandates Extension, detailing secure, cryptographically bound checkout commitments (using SD-JWT+kb digital credentials and diff --git a/source/schemas/shopping/additional_fields.json b/source/schemas/shopping/additional_fields.json new file mode 100644 index 000000000..59305851e --- /dev/null +++ b/source/schemas/shopping/additional_fields.json @@ -0,0 +1,242 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://ucp.dev/schemas/shopping/additional_fields.json", + "name": "dev.ucp.shopping.additional_fields", + "title": "Additional Fields Extension", + "description": "Extends Checkout with merchant-defined additional data collection fields and Order with read-only captured additional field values. This base extension defines the field envelope and text input semantics, including the multiline text hint.", + "$defs": { + "additional_field_input": { + "title": "Additional Field Input", + "type": "object", + "description": "Input configuration for an additional field. The type value is an open field type identifier. This extension defines text semantics; child extensions can define additional types.", + "required": [ + "type" + ], + "additionalProperties": true, + "allOf": [ + { + "if": { + "properties": { + "type": { + "const": "text" + } + }, + "required": [ + "type" + ] + }, + "then": { + "$ref": "#/$defs/text_input" + } + } + ], + "properties": { + "type": { + "type": "string", + "description": "Field type identifier. This extension defines text. Vendors MAY define custom type identifiers using their own namespace." + } + } + }, + "text_input": { + "title": "Text Input", + "type": "object", + "description": "Text field semantics. Set multiline to true for multi-line text.", + "required": [ + "type" + ], + "additionalProperties": true, + "properties": { + "type": { + "type": "string", + "const": "text", + "description": "Use text for text fields. Set multiline to true for multi-line text.", + "examples": [ + "text" + ] + }, + "multiline": { + "type": "boolean", + "default": false, + "description": "True when the text value is expected to contain multiple lines." + } + } + }, + "additional_field": { + "title": "Additional Field", + "type": "object", + "description": "Definition and current value for a merchant-defined checkout field.", + "additionalProperties": true, + "required": [ + "key", + "label", + "required", + "description", + "input", + "value" + ], + "properties": { + "key": { + "type": "string", + "description": "Unique identifier for the field. Must be unique within the additional_fields array.", + "ucp_request": { + "create": "required", + "update": "required", + "complete": "omit" + } + }, + "label": { + "type": "string", + "description": "Human-readable display name for the field.", + "ucp_request": "omit" + }, + "label_content_type": { + "type": "string", + "enum": [ + "plain", + "markdown" + ], + "default": "plain", + "description": "Format for the label. Defaults to plain.", + "ucp_request": "omit" + }, + "required": { + "type": "boolean", + "description": "Whether the field must be filled before checkout can complete.", + "ucp_request": "omit" + }, + "description": { + "type": "string", + "description": "Help text or instructions for the field.", + "ucp_request": "omit" + }, + "input": { + "$ref": "#/$defs/additional_field_input", + "description": "Input control configuration.", + "ucp_request": "omit" + }, + "value": { + "type": [ + "string", + "null" + ], + "description": "Current value for the field. Null when no value has been submitted or the value has been cleared by replacement.", + "ucp_request": { + "create": "required", + "update": "required", + "complete": "omit" + } + } + } + }, + "order_additional_field_input": { + "title": "Order Additional Field Input", + "type": "object", + "description": "Input metadata for a read-only order additional field. Order input metadata includes the field type.", + "required": [ + "type" + ], + "additionalProperties": true, + "properties": { + "type": { + "type": "string", + "description": "Field type identifier. This extension defines text. Child extensions can define additional type identifiers." + } + } + }, + "order_additional_field": { + "title": "Order Additional Field", + "type": "object", + "description": "Read-only additional field value captured for an order.", + "additionalProperties": false, + "required": [ + "key", + "label", + "description", + "input", + "value" + ], + "properties": { + "key": { + "type": "string", + "description": "Unique identifier for the captured field." + }, + "label": { + "type": "string", + "description": "Human-readable display name for the captured field." + }, + "label_content_type": { + "type": "string", + "enum": [ + "plain", + "markdown" + ], + "default": "plain", + "description": "Format for the label. Defaults to plain." + }, + "description": { + "type": "string", + "description": "Help text or instructions associated with the captured field." + }, + "input": { + "$ref": "#/$defs/order_additional_field_input", + "description": "Input metadata for the captured field." + }, + "value": { + "type": [ + "string", + "null" + ], + "description": "Captured value for the field. Null when no value was submitted or the value was cleared before order creation." + } + } + }, + "dev.ucp.shopping.checkout": { + "title": "Checkout with Additional Fields", + "description": "Checkout extended with merchant-defined additional fields.", + "allOf": [ + { + "$ref": "checkout.json" + }, + { + "type": "object", + "properties": { + "additional_fields": { + "type": "array", + "description": "Merchant-defined additional checkout fields with current values. Requests replace the previously submitted additional field value set; configured fields omitted from the replacement set have no submitted value.", + "items": { + "$ref": "#/$defs/additional_field" + }, + "ucp_request": { + "create": "optional", + "update": "optional", + "complete": "omit" + } + } + } + } + ] + }, + "dev.ucp.shopping.order": { + "title": "Order with Additional Fields", + "description": "Order extended with read-only additional field values captured during checkout.", + "allOf": [ + { + "$ref": "order.json" + }, + { + "type": "object", + "properties": { + "additional_fields": { + "type": "array", + "description": "Read-only additional field values captured during checkout.", + "items": { + "$ref": "#/$defs/order_additional_field" + }, + "ucp_request": "omit" + } + } + } + ] + } + } +} diff --git a/source/schemas/shopping/additional_fields_advanced.json b/source/schemas/shopping/additional_fields_advanced.json new file mode 100644 index 000000000..c311f9ed6 --- /dev/null +++ b/source/schemas/shopping/additional_fields_advanced.json @@ -0,0 +1,358 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://ucp.dev/schemas/shopping/additional_fields_advanced.json", + "name": "dev.ucp.shopping.additional_fields_advanced", + "title": "Advanced Additional Fields Extension", + "description": "Extends Additional Fields with advanced input types and validation hints for checkout, and advanced input type identifiers for read-only order additional fields.", + "$defs": { + "choice_option": { + "title": "Choice Option", + "type": "object", + "description": "A selectable option for a choice additional field.", + "required": [ + "value", + "label" + ], + "properties": { + "value": { + "type": "string", + "description": "Submitted value for this option." + }, + "label": { + "type": "string", + "description": "Human-readable option label." + } + } + }, + "text_input": { + "title": "Text Input", + "type": "object", + "description": "Text field semantics with optional validation hints.", + "required": [ + "type" + ], + "additionalProperties": true, + "properties": { + "type": { + "type": "string", + "const": "text", + "description": "Use text for text fields.", + "examples": [ + "text" + ] + }, + "min_length": { + "type": "integer", + "minimum": 0, + "description": "Minimum number of characters accepted by the business." + }, + "max_length": { + "type": "integer", + "minimum": 0, + "description": "Maximum number of characters accepted by the business." + }, + "pattern": { + "type": "string", + "format": "regex", + "description": "Regular expression the submitted value should match." + } + } + }, + "boolean_input": { + "title": "Boolean Input", + "type": "object", + "description": "Boolean field semantics. Values are still submitted as strings.", + "required": [ + "type" + ], + "additionalProperties": true, + "properties": { + "type": { + "type": "string", + "const": "boolean", + "description": "Use boolean for true/false fields. Submit checked as true and unchecked as false.", + "examples": [ + "boolean" + ] + } + } + }, + "date_input": { + "title": "Date Input", + "type": "object", + "description": "Date field semantics with optional date range hints.", + "required": [ + "type" + ], + "additionalProperties": true, + "properties": { + "type": { + "type": "string", + "const": "date", + "description": "Use date for calendar-date fields.", + "examples": [ + "date" + ] + }, + "min": { + "type": "string", + "format": "date", + "description": "Earliest accepted date in YYYY-MM-DD format." + }, + "max": { + "type": "string", + "format": "date", + "description": "Latest accepted date in YYYY-MM-DD format." + } + } + }, + "choice_input": { + "title": "Choice Input", + "type": "object", + "description": "Select-from-list field semantics.", + "required": [ + "type", + "options" + ], + "additionalProperties": true, + "properties": { + "type": { + "type": "string", + "const": "choice", + "description": "Use choice when the submitted value must be selected from options.", + "examples": [ + "choice" + ] + }, + "options": { + "type": "array", + "description": "Available choices. Platforms display label and submit value.", + "items": { + "$ref": "#/$defs/choice_option" + }, + "minItems": 1 + } + } + }, + "order_choice_input": { + "title": "Order Choice Input", + "type": "object", + "description": "Choice input metadata included on order additional fields.", + "required": [ + "type", + "value_label" + ], + "additionalProperties": true, + "properties": { + "type": { + "type": "string", + "const": "choice", + "description": "Use choice when the captured value was selected from options.", + "examples": [ + "choice" + ] + }, + "value_label": { + "type": [ + "string", + "null" + ], + "description": "Display label for the captured value. Null when value is null." + } + } + }, + "dev.ucp.shopping.additional_fields": { + "title": "Additional Fields with Advanced Types", + "description": "Additional Fields extended with advanced input types and validation hints.", + "type": "object", + "properties": { + "additional_fields": { + "type": "array", + "description": "Merchant-defined additional checkout fields with advanced input semantics.", + "items": { + "allOf": [ + { + "$ref": "additional_fields.json#/$defs/additional_field" + }, + { + "type": "object", + "properties": { + "input": { + "description": "Input control configuration.", + "ucp_request": "omit", + "allOf": [ + { + "$ref": "additional_fields.json#/$defs/additional_field_input" + }, + { + "type": "object", + "additionalProperties": true, + "allOf": [ + { + "if": { + "properties": { + "type": { + "const": "text" + } + }, + "required": [ + "type" + ] + }, + "then": { + "$ref": "#/$defs/text_input" + } + }, + { + "if": { + "properties": { + "type": { + "const": "boolean" + } + }, + "required": [ + "type" + ] + }, + "then": { + "$ref": "#/$defs/boolean_input" + } + }, + { + "if": { + "properties": { + "type": { + "const": "date" + } + }, + "required": [ + "type" + ] + }, + "then": { + "$ref": "#/$defs/date_input" + } + }, + { + "if": { + "properties": { + "type": { + "const": "choice" + } + }, + "required": [ + "type" + ] + }, + "then": { + "$ref": "#/$defs/choice_input" + } + } + ], + "properties": { + "type": { + "type": "string", + "description": "Field type identifier. Advanced values include text, boolean, date, and choice." + } + } + } + ] + } + } + } + ] + }, + "ucp_request": { + "create": "optional", + "update": "optional", + "complete": "omit" + } + } + } + }, + "dev.ucp.shopping.checkout": { + "title": "Checkout with Advanced Additional Fields", + "description": "Checkout additional fields extended with advanced input types and validation hints.", + "type": "object", + "properties": { + "additional_fields": { + "type": "array", + "description": "Merchant-defined additional checkout fields with advanced input semantics.", + "items": { + "$ref": "#/$defs/dev.ucp.shopping.additional_fields/properties/additional_fields/items" + }, + "ucp_request": { + "create": "optional", + "update": "optional", + "complete": "omit" + } + } + } + }, + "dev.ucp.shopping.order": { + "title": "Order with Advanced Additional Fields", + "description": "Order additional fields extended with advanced input type identifiers.", + "type": "object", + "properties": { + "additional_fields": { + "type": "array", + "description": "Read-only additional field values captured during checkout, with advanced input type identifiers.", + "items": { + "allOf": [ + { + "$ref": "additional_fields.json#/$defs/order_additional_field" + }, + { + "type": "object", + "properties": { + "input": { + "description": "Input metadata for the captured field. Choice fields include the display label for the captured value.", + "allOf": [ + { + "$ref": "additional_fields.json#/$defs/order_additional_field_input" + }, + { + "type": "object", + "additionalProperties": true, + "allOf": [ + { + "if": { + "properties": { + "type": { + "const": "choice" + } + }, + "required": [ + "type" + ] + }, + "then": { + "$ref": "#/$defs/order_choice_input" + } + } + ], + "properties": { + "type": { + "type": "string", + "description": "Field type identifier. Advanced values include text, boolean, date, and choice.", + "examples": [ + "text", + "boolean", + "date", + "choice" + ] + } + } + } + ] + } + } + } + ] + }, + "ucp_request": "omit" + } + } + } + } +}