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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -355,6 +355,7 @@ image: assets/banner.png
"selected_destination_id": "dest_1",
"destinations": [
{
"type": "shipping_address",
"id": "dest_1",
"first_name": "Elisa",
"last_name": "Beckett",
Expand Down
2 changes: 2 additions & 0 deletions docs/specification/checkout-mcp.md
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,7 @@ Maps to the [Create Checkout](checkout.md#create-checkout) operation.
"selected_destination_id": "dest_home",
"destinations": [
{
"type": "shipping_address",
"id": "dest_home",
"street_address": "123 Main St",
"address_locality": "Springfield",
Expand Down Expand Up @@ -536,6 +537,7 @@ unchanged and return the current Checkout with a recoverable error Message.
"selected_destination_id": "dest_home",
"destinations": [
{
"type": "shipping_address",
"id": "dest_home",
"street_address": "123 Main St",
"address_locality": "Springfield",
Expand Down
12 changes: 9 additions & 3 deletions docs/specification/checkout-rest.md
Original file line number Diff line number Diff line change
Expand Up @@ -371,9 +371,10 @@ include all previously set fields it intends to retain.

#### Update Fulfillment

Fulfillment is an extension to the checkout capability. Most fields are provided
by the business based on buyer inputs, which includes desired fulfillment
type & addresses.
Fulfillment is an extension to the checkout capability. The Business provides
most fields based on Buyer inputs, including desired fulfillment method types
and typed destinations such as shipping addresses and Business Location
references.

=== "Request"

Expand Down Expand Up @@ -513,6 +514,7 @@ type & addresses.
"selected_destination_id": "dest_home",
"destinations": [
{
"type": "shipping_address",
"id": "dest_home",
"street_address": "123 Main St",
"address_locality": "Springfield",
Expand Down Expand Up @@ -712,6 +714,7 @@ 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 Expand Up @@ -913,6 +916,7 @@ for status and `order` semantics.
"selected_destination_id": "dest_home",
"destinations": [
{
"type": "shipping_address",
"id": "dest_home",
"street_address": "123 Main St",
"address_locality": "Springfield",
Expand Down Expand Up @@ -1072,6 +1076,7 @@ for status and `order` semantics.
"selected_destination_id": "dest_home",
"destinations": [
{
"type": "shipping_address",
"id": "dest_home",
"street_address": "123 Main St",
"address_locality": "Springfield",
Expand Down Expand Up @@ -1229,6 +1234,7 @@ for status and `order` semantics.
"selected_destination_id": "dest_home",
"destinations": [
{
"type": "shipping_address",
"id": "dest_home",
"street_address": "123 Main St",
"address_locality": "Springfield",
Expand Down
163 changes: 156 additions & 7 deletions docs/specification/fulfillment.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,9 +81,13 @@ method.

{{ schema_fields('types/shipping_destination_resp', 'fulfillment') }}

#### Retail Location
#### Business Location Destination

{{ schema_fields('types/retail_location_resp', 'fulfillment') }}
{{ schema_fields('types/location_destination_resp', 'fulfillment') }}

#### Location Summary

{{ schema_fields('location_summary', 'fulfillment') }}

#### Fulfillment Group

Expand Down Expand Up @@ -126,6 +130,7 @@ method.
"selected_destination_id": "dest_1",
"destinations": [
{
"type": "shipping_address",
"id": "dest_1",
"street_address": "123 Main St",
"address_locality": "Springfield",
Expand Down Expand Up @@ -171,6 +176,143 @@ method.
}
```

## Destinations

A fulfillment method's `type` describes how items are fulfilled, while each
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.

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 |
| --- | --- |
| `shipping_address` | A Shipping Destination with flat Postal Address fields. |
| `business_location` | A Business Location Destination identified by a Business-scoped `id`. |

Additional values are defined by negotiated extensions. Destination fields
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 **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

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/fulfillment_method op=update direction=request -->
```json
{
"type": "pickup",
"line_item_ids": ["shirt", "pants"],
"selected_destination_id": "loc_downtown"
}
```

#### Business Response

<!-- ucp:example schema=shopping/types/location_destination op=read direction=response -->
```json
{
"type": "business_location",
"id": "loc_downtown",
"name": "Downtown Store",
"address": {
"street_address": "123 Main St",
"address_locality": "Springfield",
"address_region": "IL",
"postal_code": "62701",
"address_country": "US"
}
}
```

### Selection and Location Identity

`selected_destination_id` identifies the selected destination in a method by
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

Fulfillment options are designed for **method-agnostic rendering**. Platforms
Expand Down Expand Up @@ -311,11 +453,14 @@ method has:
renderable; see [Rendering](#rendering).
* `availability` — whether the variant is available via this method at the
specified or inferred location.
* `location` — for place-based methods (e.g. `pickup`), the resolved
location id, and the business's stable identifier for that location. A
business that advertises pickup at a `location` MUST accept the same id
as `selected_destination_id` for that method, so a discovered location
can be used in cart and checkout.
* `location` — when the method is scoped to a specific Business Location,
that location's stable, opaque, Business-scoped identifier. When Catalog
represents the location as applicable to a particular method, the Business
**MUST** recognize the same ID when the Platform submits it for that
method, including as `selected_destination_id`. Recognition is
method-scoped and does not reserve inventory or guarantee eligibility; the
Business revalidates current availability and terms during Checkout. See
[Selection and Location Identity](#selection-and-location-identity).
* `options` — concrete fulfillment choices within this method (e.g.
Standard, Express); see [Options](#options). Optional.

Expand Down Expand Up @@ -652,6 +797,7 @@ so a cart can mix shipped and installed items (see
"selected_destination_id": "dest_1",
"destinations": [
{
"type": "shipping_address",
"id": "dest_1",
"street_address": "123 Main St",
"address_locality": "Springfield",
Expand Down Expand Up @@ -723,6 +869,7 @@ package.
"selected_destination_id": "dest_1",
"destinations": [
{
"type": "shipping_address",
"id": "dest_1",
"street_address": "123 Main St",
"address_locality": "Springfield",
Expand Down Expand Up @@ -799,6 +946,7 @@ same type, each with its own destination.
"selected_destination_id": "dest_mom",
"destinations": [
{
"type": "shipping_address",
"id": "dest_mom",
"street_address": "123 Mom St",
"address_locality": "Springfield",
Expand Down Expand Up @@ -844,6 +992,7 @@ same type, each with its own destination.
"selected_destination_id": "dest_grandma",
"destinations": [
{
"type": "shipping_address",
"id": "dest_grandma",
"street_address": "88 Queensway",
"address_locality": "Hong Kong",
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: [{ id: "addr_1", street: "123 Main St", city: "Tech City", 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: [{ id: "addr_1", street: "123 Main St", city: "Tech City", 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
26 changes: 26 additions & 0 deletions source/schemas/common/types/location_summary.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://ucp.dev/schemas/common/types/location_summary.json",
"title": "Location Summary",
"description": "A summary of a business location.",
"type": "object",
"ucp_shared_request": true,
"required": ["id", "name"],
"properties": {
"id": {
"type": "string",
"description": "Stable, opaque, Business-scoped Location identifier.",
"ucp_request": "required"
},
"name": {
"type": "string",
"description": "Buyer-facing, Business-owned display name.",
"ucp_request": "omit"
},
"address": {
"$ref": "postal_address.json",
"description": "Physical address of the location.",
"ucp_request": "omit"
}
}
}
2 changes: 1 addition & 1 deletion source/schemas/shopping/fulfillment.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
},
"location": {
"type": "string",
"description": "The location resolved for this method, where appropriate (e.g. the pickup store or location), as a location id."
"description": "Stable, opaque, Business-scoped Location ID. The Business recognizes this ID when the Platform submits it for this method. Recognition does not reserve inventory or guarantee eligibility, and current terms are revalidated."
},
"options": {
"type": "array",
Expand Down
31 changes: 28 additions & 3 deletions source/schemas/shopping/types/fulfillment_destination.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,37 @@
"description": "A destination for fulfillment.",
"type": "object",
"ucp_shared_request": true,
"oneOf": [
"required": ["type", "id"],
"properties": {
"type": {
"type": "string",
"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",
"description": "Fulfillment destination identifier.",
"ucp_request": "optional"
}
},
"allOf": [
{
"$ref": "shipping_destination.json"
"if": {
"properties": { "type": { "const": "shipping_address" } },
"required": ["type"]
},
"then": {
"$ref": "shipping_destination.json"
}
},
{
"$ref": "retail_location.json"
"if": {
"properties": { "type": { "const": "business_location" } },
"required": ["type"]
},
"then": {
"$ref": "location_destination.json"
}
}
]
}
Loading
Loading