From 39076338fc0d55439e195a31b88b28cd695b40d4 Mon Sep 17 00:00:00 2001 From: SaifBenChahed Date: Thu, 21 May 2026 15:15:05 -0400 Subject: [PATCH] Add additional fields extensions --- docs/documentation/core-concepts.md | 1 + docs/specification/additional-fields.md | 445 +++++++++++++++++ docs/specification/checkout-mcp.md | 2 + docs/specification/checkout.md | 8 + docs/specification/order-additional-fields.md | 142 ++++++ docs/specification/overview.md | 24 +- docs/specification/playground.md | 12 +- mkdocs.yml | 10 + .../schemas/shopping/additional_fields.json | 447 ++++++++++++++++++ 9 files changed, 1078 insertions(+), 13 deletions(-) create mode 100644 docs/specification/additional-fields.md create mode 100644 docs/specification/order-additional-fields.md create mode 100644 source/schemas/shopping/additional_fields.json diff --git a/docs/documentation/core-concepts.md b/docs/documentation/core-concepts.md index 711d6470a..c3f4de0b4 100644 --- a/docs/documentation/core-concepts.md +++ b/docs/documentation/core-concepts.md @@ -181,6 +181,7 @@ up-to-date list. | Extension | Extends | Description | | :--- | :--- | :--- | +| `dev.ucp.shopping.additional_fields` | checkout, order | Merchant-configured checkout data collection fields and read-only order values | | `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.md b/docs/specification/additional-fields.md new file mode 100644 index 000000000..b1bd6b1b8 --- /dev/null +++ b/docs/specification/additional-fields.md @@ -0,0 +1,445 @@ + + +# 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. + +**Key features:** + +- Receive business-defined required and optional fields +- Submit additional field values as `key` plus `value` entries +- Apply standard input hints for `text`, `boolean`, `date`, and `choice` +- 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. 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, checkout is extended with an +`additional_fields` array. + +### 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') }} + +### Standard Input Types + +This version defines the following standard types: + +| Type | What it adds | Example Value | +| :-------- | :----------------------------------------------------------------- | :----------------- | +| `text` | Text semantics and text validation hints; set `multiline: true` for multi-line text | `"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.json#/$defs/text_input', 'additional-fields') }} + +#### Boolean Input + +{{ extension_schema_fields('additional_fields.json#/$defs/boolean_input', 'additional-fields') }} + +#### Date Input + +{{ extension_schema_fields('additional_fields.json#/$defs/date_input', 'additional-fields') }} + +#### Choice Input + +{{ extension_schema_fields('additional_fields.json#/$defs/choice_input', 'additional-fields') }} + +#### Choice Option + +{{ extension_schema_fields('additional_fields.json#/$defs/choice_option', '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. +- Standard hints are defined only for the four types above. +- Future UCP or vendor extensions can extend + `dev.ucp.shopping.additional_fields` and introduce new type identifiers and + hints without changing the field envelope. +- Child type extensions should add conditional constraints for their + `input.type` value using `allOf`, following the normal UCP schema + composition pattern. + +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, too long, in the wrong format, 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", "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 + }, + { + "key": "special_instructions", + "label": "Special Instructions", + "input": { "type": "text", "multiline": true, "max_length": 500 }, + "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": "gift_wrap", "value": "true" }, + { "key": "gift_wrap_type", "value": "birthday" }, + { "key": "delivery_date", "value": "2026-04-01" }, + { + "key": "special_instructions", + "value": "Please leave at the reception desk.\nRing buzzer #4." + } + ] + } + ``` + +=== "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": "PO-2026-001234" + }, + { + "key": "gift_wrap", + "label": "Gift Wrap", + "input": { "type": "boolean" }, + "required": false, + "description": "Add gift wrapping for $5", + "value": "true" + }, + { + "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": "birthday" + }, + { + "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": "2026-04-01" + }, + { + "key": "special_instructions", + "label": "Special Instructions", + "input": { "type": "text", "multiline": true, "max_length": 500 }, + "required": false, + "description": "Any special delivery or handling instructions", + "value": "Please leave at the reception desk.\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": "gift_wrap", "value": "true" }, + { "key": "gift_wrap_type", "value": "birthday" }, + { "key": "special_instructions", "value": null } + ] + } + ``` + +=== "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": "PO-2026-001234" + }, + { + "key": "gift_wrap", + "label": "Gift Wrap", + "input": { "type": "boolean" }, + "required": false, + "description": "Add gift wrapping for $5", + "value": "true" + }, + { + "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": "birthday" + }, + { + "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 + }, + { + "key": "special_instructions", + "label": "Special Instructions", + "input": { "type": "text", "multiline": true, "max_length": 500 }, + "required": false, + "description": "Any special delivery or handling instructions", + "value": null + } + ] + } + ``` + +### Invalid additional field value + +When an additional field cannot be accepted, the field still appears in +`additional_fields` and the rejection is communicated via `messages[]`. + +=== "Response" + + ```json + { + "status": "incomplete", + "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 + } + ], + "messages": [ + { + "type": "error", + "severity": "recoverable", + "code": "additional_field_invalid_value", + "path": "$.additional_fields[?@.key=='po_number']", + "content": "Purchase Order Number is required" + } + ] + } + ``` diff --git a/docs/specification/checkout-mcp.md b/docs/specification/checkout-mcp.md index b4c51c8df..f22884a46 100644 --- a/docs/specification/checkout-mcp.md +++ b/docs/specification/checkout-mcp.md @@ -139,6 +139,7 @@ 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.buyer_consent`: [Buyer Consent](buyer-consent.md) * `dev.ucp.shopping.fulfillment`: [Fulfillment](fulfillment.md) * `dev.ucp.shopping.discount`: [Discount](discount.md) @@ -389,6 +390,7 @@ 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.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..15c659d1e 100644 --- a/docs/specification/checkout.md +++ b/docs/specification/checkout.md @@ -51,6 +51,14 @@ 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. + ### Checkout Status Lifecycle The checkout `status` field indicates the current phase of the session and diff --git a/docs/specification/order-additional-fields.md b/docs/specification/order-additional-fields.md new file mode 100644 index 000000000..6efbb8939 --- /dev/null +++ b/docs/specification/order-additional-fields.md @@ -0,0 +1,142 @@ + + +# 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 + +Businesses advertise additional field support in their profile. 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" + } + ] + } + } +} +``` + +Businesses MAY advertise additional field support for checkout only, order only, +or both. Platforms SHOULD check which resources are 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') }} + +### Order Choice Input + +{{ extension_schema_fields('additional_fields.json#/$defs/order_choice_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 +- Choice fields include `input.value_label` for the captured value label + +## Examples + +### Order with additional field values + +```json +{ + "additional_fields": [ + { + "key": "po_number", + "label": "Purchase Order Number", + "description": "Required for B2B orders. Must match format PO-.", + "input": { "type": "text" }, + "value": "PO-2026-001234" + }, + { + "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" + }, + { + "key": "special_instructions", + "label": "Special Instructions", + "description": "Any special delivery or handling instructions", + "input": { "type": "text" }, + "value": null + } + ] +} +``` diff --git a/docs/specification/overview.md b/docs/specification/overview.md index 535c0ddbd..64a6a4fd1 100644 --- a/docs/specification/overview.md +++ b/docs/specification/overview.md @@ -70,12 +70,13 @@ 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.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 +203,7 @@ 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.fulfillment` extends `dev.ucp.shopping.checkout` - **Vendor**: `com.example.installments` extends `dev.ucp.shopping.checkout` @@ -968,11 +970,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, discount, fulfillment | cart, order | +| Cart | cart, discount | checkout, fulfillment, order | +| Order | order, additional_fields | checkout, cart, discount | ## Identity & Authentication diff --git a/docs/specification/playground.md b/docs/specification/playground.md index 08db1d153..05d1bdb4e 100644 --- a/docs/specification/playground.md +++ b/docs/specification/playground.md @@ -527,6 +527,14 @@ const UcpData = { schema: "https://ucp.dev/{{ ucp_version }}/schemas/shopping/discount.json" } ], + "dev.ucp.shopping.additional_fields": [ + { + extends: "dev.ucp.shopping.checkout", + 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.buyer_consent": [ { extends: "dev.ucp.shopping.checkout", @@ -553,8 +561,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.buyer_consent", "dev.ucp.shopping.ap2_mandates"] } }, diff --git a/mkdocs.yml b/mkdocs.yml index 3efe195e7..e6fcc82e7 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -48,6 +48,7 @@ nav: - MCP: specification/checkout-mcp.md - A2A: specification/checkout-a2a.md - Embedded: specification/embedded-checkout.md + - Additional Fields Extension: specification/additional-fields.md - AP2 Mandates Extension: specification/ap2-mandates.md - Buyer Consent Extension: specification/buyer-consent.md - Discounts Extension: specification/discount.md @@ -71,6 +72,7 @@ nav: - Transports: - REST: specification/order-rest.md - MCP: specification/order-mcp.md + - Additional Fields Extension: specification/order-additional-fields.md - Identity Linking Capability: specification/identity-linking.md - Payment Handlers: - Guide: specification/payment-handler-guide.md @@ -299,6 +301,11 @@ 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 + data collection fields, value submission semantics, open input + type extensibility, and standard text, boolean, date, and choice + input hints. - specification/ap2-mandates.md: >- AP2 Mandates Extension, detailing secure, cryptographically bound checkout commitments (using SD-JWT+kb digital credentials and @@ -377,6 +384,9 @@ plugins: Model Context Protocol (MCP) transport binding for the Order Capability, detailing the JSON-RPC tool interface and parameters for fetching order states. + - specification/order-additional-fields.md: >- + Order Additional Fields Extension, defining compact read-only + summaries of additional field values captured during checkout. - specification/identity-linking.md: >- User authentication via the Identity Linking Capability, specifying direct B2C/B2B OAuth 2.0 flows (with PKCE and diff --git a/source/schemas/shopping/additional_fields.json b/source/schemas/shopping/additional_fields.json new file mode 100644 index 000000000..ce7155f31 --- /dev/null +++ b/source/schemas/shopping/additional_fields.json @@ -0,0 +1,447 @@ +{ + "$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. The field envelope is stable while input.type remains open for future type extensions.", + "$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." + } + } + }, + "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. Platforms MUST respect the declared type and MUST NOT render or coerce unrecognized types as another supported input.", + "required": [ + "type" + ], + "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. Standard values defined by this extension include text, boolean, date, and choice. Vendors MAY define custom type identifiers using their own namespace." + } + } + }, + "text_input": { + "title": "Text Input", + "type": "object", + "description": "Text field semantics with optional validation hints. 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" + ] + }, + "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." + }, + "multiline": { + "type": "boolean", + "default": false, + "description": "True when the text value is expected to contain multiple lines." + } + } + }, + "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 + } + } + }, + "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 and, for choice fields, the display label for the captured value.", + "required": [ + "type" + ], + "additionalProperties": true, + "allOf": [ + { + "if": { + "properties": { + "type": { + "const": "choice" + } + }, + "required": [ + "type" + ] + }, + "then": { + "$ref": "#/$defs/order_choice_input" + } + } + ], + "properties": { + "type": { + "type": "string", + "description": "Field type identifier. Standard values defined by this extension include text, boolean, date, and choice. Future extensions can define additional type identifiers.", + "examples": [ + "text", + "boolean", + "date", + "choice" + ] + } + } + }, + "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." + } + } + }, + "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" + } + } + } + ] + } + } +}