Skip to content
Merged
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
1 change: 0 additions & 1 deletion docs/specification/checkout-mcp.md
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,6 @@ Maps to the [Create Checkout](checkout.md#create-checkout) operation.
"type": "shipping",
"destinations": [
{
"type": "shipping_address",
"street_address": "123 Main St",
"address_locality": "Springfield",
"address_region": "IL",
Expand Down
2 changes: 0 additions & 2 deletions docs/specification/checkout-rest.md
Original file line number Diff line number Diff line change
Expand Up @@ -406,7 +406,6 @@ references.
"type": "shipping",
"destinations": [
{
"type": "shipping_address",
"street_address": "123 Main St",
"address_locality": "Springfield",
"address_region": "IL",
Expand Down Expand Up @@ -613,7 +612,6 @@ Follow-up calls after initial `fulfillment` data to update selection.
"selected_destination_id": "dest_home",
"destinations": [
{
"type": "shipping_address",
"id": "dest_home",
"street_address": "123 Main St",
"address_locality": "Springfield",
Expand Down
2 changes: 0 additions & 2 deletions docs/specification/embedded-checkout.md
Original file line number Diff line number Diff line change
Expand Up @@ -1306,7 +1306,6 @@ method.
"selected_destination_id": "address_123",
"destinations": [
{
"type": "shipping_address",
"id": "address_123",
"street_address": "456 Old Street"
// ...
Expand Down Expand Up @@ -1354,7 +1353,6 @@ rather than attempting to merge the new data with existing state.
"selected_destination_id": "address_789",
"destinations": [
{
"type": "shipping_address",
"id": "address_789",
"first_name": "John",
"last_name": "Doe",
Expand Down
106 changes: 86 additions & 20 deletions docs/specification/fulfillment.md
Original file line number Diff line number Diff line change
Expand Up @@ -179,10 +179,39 @@ method.
## Destinations

A fulfillment method's `type` describes how items are fulfilled, while each
destination's `type` describes where fulfillment occurs. These discriminators
are independent; neither implies the other.
destination's `type` describes where fulfillment occurs. The two
discriminators play different roles by direction: in Business responses,
every destination carries a required `type`, so responses are
self-describing; in Platform requests, the method's contract determines who
authors `destinations[]` and MAY define a default destination shape when
`type` is omitted.

Every destination has a required, open `type`. The well-known values are:
Destination authorship — whether the Platform may write entries into a
method's `destinations[]` — is keyed by the method's `type`:

| Method `type` | Request `destinations[]` |
| --- | --- |
| `shipping` | Platform-writable. The Platform writes the Buyer's shipping-address facts. |
| `pickup` | Not Platform-writable (schema-enforced). The Business enumerates locations in its response; the Platform selects one by ID (see [Selection and Location Identity](#selection-and-location-identity)). |
| other method type | The method's defining contract specifies the destination shape and whether it is Platform-writable. |

Destination `type` is **required in responses and optional in requests**. In
responses, every destination carries a required, open `type`, so destinations
remain self-describing wherever they appear. In requests, the method's
contract defines the destination shape:

* Under a well-known `shipping` method, every destination is a Shipping
Destination: a destination that omits `type` defaults to
`shipping_address`.
* Under a well-known `pickup` method, destinations are response-only; the
Platform selects a location via `selected_destination_id` (see
[Selection and Location Identity](#selection-and-location-identity)).
* Under any other method type, the method's defining contract — a future
revision of this specification or a negotiated extension — specifies the
request destination shape and whether it is Platform-writable.

A request that includes `destinations[]` MUST also include the method's
`type`. The well-known values are:

| Value | Meaning |
| --- | --- |
Expand All @@ -195,27 +224,59 @@ specific to such a value are validated by the negotiated extension's schema.
### Shipping Destination

For a Shipping Destination, the Platform supplies shipping-address facts as
flat Postal Address fields directly on the destination. The Platform **MUST**
include `type: "shipping_address"` and **MAY** include `id` in its request. The
Business **MUST** include `type: "shipping_address"` and assign `id` in its
response.
flat Postal Address fields directly on the destination. The Platform **MAY**
include `id` and **MAY** include `type: "shipping_address"` in its request.
The Business **MUST** include `type: "shipping_address"` and assign `id` in
its response. An `id`-only destination that references a saved or
provider-held address is still a Shipping Destination; conveying provider
provenance or additional fields requires a negotiated extension contract.

#### Platform Request

<!-- ucp:example schema=shopping/types/shipping_destination op=update direction=request -->
```json
{
"street_address": "123 Main St",
"address_locality": "Springfield",
"address_region": "IL",
"postal_code": "62701",
"address_country": "US"
}
```

#### Business Response

<!-- ucp:example schema=shopping/types/shipping_destination op=read direction=response -->
```json
{
"type": "shipping_address",
"id": "dest_1",
"street_address": "123 Main St",
"address_locality": "Springfield",
"address_region": "IL",
"postal_code": "62701",
"address_country": "US"
}
```

### Business Location Destination

For a Business Location Destination, the Platform selects the location with a
stable, opaque, Business-scoped `id`. The Platform **MUST** include
`type: "business_location"` and `id` in its request and **MUST NOT** include the
Business-owned `name` or `address`. The Business **MUST** return
Business Location Destinations appear only in responses; `destinations[]` on a
`pickup` method is not a request field. The Platform **MUST NOT** write
Business Location Destinations into `destinations[]`; it selects a location by
submitting its stable, opaque, Business-scoped ID as
`selected_destination_id`. The Business **MUST** return
`type: "business_location"`, `id`, and its Buyer-facing `name`, and **MAY**
return its Postal Address in `address`.

#### Platform Request

<!-- ucp:example schema=shopping/types/location_destination op=update direction=request -->
<!-- ucp:example schema=shopping/types/fulfillment_method op=update direction=request -->
```json
{
"type": "business_location",
"id": "loc_downtown"
"type": "pickup",
"line_item_ids": ["shirt", "pants"],
"selected_destination_id": "loc_downtown"
}
```

Expand All @@ -240,12 +301,17 @@ return its Postal Address in `address`.
### Selection and Location Identity

`selected_destination_id` identifies the selected destination in a method by
its `id`. When Catalog represents a location as applicable to a particular
fulfillment method, the Business **MUST** recognize the same Business-scoped ID
when the Platform submits it in Checkout for that method, including as
`selected_destination_id`. The Business **MUST** revalidate current availability
and terms during Checkout; recognition does not reserve inventory or guarantee
eligibility.
its `id`, and is the sole channel for selecting a Business Location. It accepts
any stable, Business-scoped Location ID the Business recognizes for that
method, including IDs the Business has not (yet) enumerated in that method's
`destinations[]` — for example an ID discovered through Catalog or a separately
negotiated Location capability. When Catalog represents a location as
applicable to a particular fulfillment method, the Business **MUST** recognize
the same Business-scoped ID when the Platform submits it in Checkout for that
method, including as `selected_destination_id`, and **MUST** return the
corresponding typed destination in `destinations[]` in its response. The
Business **MUST** revalidate current availability and terms during Checkout;
recognition does not reserve inventory or guarantee eligibility.

## Rendering

Expand Down
4 changes: 2 additions & 2 deletions docs/specification/playground.md
Original file line number Diff line number Diff line change
Expand Up @@ -971,7 +971,7 @@ class UcpApp {
req.buyer.email = "test@example.com";
req.buyer.name = "Test User";
} else {
req.fulfillment.methods = [{ type: 'shipping', destinations: [{ type: "shipping_address", id: "addr_1", street_address: "123 Main St", address_locality: "Tech City", address_country: "US", postal_code: "94103" }] }];
req.fulfillment.methods = [{ type: 'shipping', destinations: [{ id: "addr_1", street_address: "123 Main St", address_locality: "Tech City", address_country: "US", postal_code: "94103" }] }];
}
}

Expand All @@ -998,7 +998,7 @@ class UcpApp {
patch.fulfillment = {
methods: [{
type: "shipping",
destinations: [{ type: "shipping_address", id: "addr_1", street_address: "123 Main St", address_locality: "Tech City", address_country: "US", postal_code: "94103" }]
destinations: [{ id: "addr_1", street_address: "123 Main St", address_locality: "Tech City", address_country: "US", postal_code: "94103" }]
}]
};
}
Expand Down
4 changes: 2 additions & 2 deletions source/schemas/shopping/types/fulfillment_destination.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@
"properties": {
"type": {
"type": "string",
"description": "Fulfillment destination type. Well-known values: `shipping_address`, `business_location`. Additional values are extension-defined and active only when their defining extension is negotiated.",
"ucp_request": "required"
"description": "Destination contract discriminator. Required in Business responses and optional in Platform requests. Well-known values: `shipping_address`, `business_location`. The enclosing method contract defines request defaults and which fields the Platform may write; negotiated extensions define additional values.",
"ucp_request": "optional"
},
"id": {
"type": "string",
Expand Down
98 changes: 83 additions & 15 deletions source/schemas/shopping/types/fulfillment_method.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,38 +4,106 @@
"title": "Fulfillment Method",
"description": "A fulfillment method with destinations and groups.",
"type": "object",
"required": ["id", "type", "line_item_ids"],
"additionalProperties": true,
"required": [
"id",
"type",
"line_item_ids"
],
"properties": {
"id": {
"type": "string",
"description": "Unique fulfillment method identifier.",
"ucp_request": {"create": "omit", "update": "optional"}
"ucp_request": {
"create": "omit",
"update": "optional"
}
},
"type": {
"type": "string",
"description": "Fulfillment method type. Well-known values: `shipping`, `pickup`. Businesses MAY use additional values.",
"ucp_request": {"create": "required", "update": "optional"}
"ucp_request": {
"create": "required",
"update": "optional"
}
},
"line_item_ids": {
"type": "array",
"description": "Line item IDs fulfilled via this method.",
"items": { "type": "string" },
"ucp_request": {"create": "optional", "update": "required"}
},
"destinations": {
"type": "array",
"description": "Available destinations. For shipping: shipping addresses. For pickup: business locations.",
"items": { "$ref": "fulfillment_destination.json" }
"items": {
"type": "string"
},
"ucp_request": {
"create": "optional",
"update": "required"
}
},
"selected_destination_id": {
"type": ["string", "null"],
"description": "ID of the selected destination."
"type": [
"string",
"null"
],
"description": "ID of the selected destination. Accepts any stable, Business-scoped ID the Business recognizes for this method, including Location IDs not yet enumerated in `destinations`."
},
"groups": {
"type": "array",
"description": "Fulfillment groups for selecting options. Agent sets selected_option_id on groups to choose shipping method.",
"items": { "$ref": "fulfillment_group.json" }
"items": {
"$ref": "fulfillment_group.json"
}
}
},
"allOf": [
{
"if": {
"properties": {
"type": {
"const": "shipping"
}
},
"required": [
"type"
]
},
"then": {
"properties": {
"destinations": {
"type": "array",
"description": "Platform-authored shipping addresses for this method.",
"items": {
"$ref": "shipping_destination.json"
}
}
}
}
},
{
"if": {
"properties": {
"type": {
"const": "pickup"
}
},
"required": [
"type"
]
},
"then": {
"properties": {
"destinations": {
"type": "array",
"description": "Business-authored business locations able to fulfill this method. Response-only: the Platform selects one via `selected_destination_id`, never writes one.",
"ucp_request": "omit",
"items": {
"$ref": "location_destination.json"
}
}
}
}
}
],
"dependentRequired": {
"destinations": [
"type"
]
}
}
}
6 changes: 3 additions & 3 deletions source/schemas/shopping/types/location_destination.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://ucp.dev/schemas/shopping/types/location_destination.json",
"title": "Business Location Destination",
"description": "A business location fulfillment destination.",
"description": "A business location fulfillment destination. Business-authored and response-only: the Platform selects a location via `selected_destination_id` rather than writing destinations.",
"type": "object",
"ucp_shared_request": true,
"allOf": [
Expand All @@ -16,8 +16,8 @@
"type": {
"type": "string",
"const": "business_location",
"description": "Destination type discriminator.",
"ucp_request": "required"
"description": "Destination type discriminator. Response-only.",
"ucp_request": "omit"
}
}
}
Expand Down
4 changes: 2 additions & 2 deletions source/schemas/shopping/types/shipping_destination.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@
"type": {
"type": "string",
"const": "shipping_address",
"description": "Destination type discriminator.",
"ucp_request": "required"
"description": "Destination type discriminator. Required in responses; optional in requests.",
"ucp_request": "optional"
}
},
"required": [
Expand Down
Loading