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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
105 changes: 94 additions & 11 deletions docs/specification/shopping/extensions/discount.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,25 +18,25 @@

## Overview

Discount extension allows businesses to indicate that they support discount
codes on cart and checkout sessions, and specifies how the discount codes are
to be shared between the platform and the business.
The Discount extension allows Businesses to surface discoverable promotions on
product details and to support discount codes on Cart and Checkout sessions.

**Key features:**

- Submit one or more discount codes
- Discover public promotion previews before Cart creation
- Receive applied discounts with human-readable titles and amounts
- Rejected codes communicated via `messages[]` with detailed error codes
- Automatic discounts surfaced alongside code-based discounts

**Dependencies:**

- Cart Capability or Checkout Capability
- Catalog Lookup, Cart, or Checkout Capability

## Discovery

Businesses advertise discount support in their profile. The capability can
extend cart, checkout, or both:
extend Catalog Lookup, Cart, Checkout, or any combination of them:

<!-- ucp:example schema=profile def=business_schema extract=$.ucp.capabilities target=$.ucp.capabilities -->
```json
Expand All @@ -47,7 +47,11 @@ extend cart, checkout, or both:
"dev.ucp.shopping.discount": [
{
"version": "{{ ucp_version }}",
"extends": ["dev.ucp.shopping.cart", "dev.ucp.shopping.checkout"],
"extends": [
"dev.ucp.shopping.catalog.lookup",
"dev.ucp.shopping.cart",
"dev.ucp.shopping.checkout"
],
"spec": "https://ucp.dev/{{ ucp_version }}/specification/shopping/extensions/discount",
"schema": "https://ucp.dev/{{ ucp_version }}/schemas/shopping/discount.json"
}
Expand All @@ -57,19 +61,29 @@ extend cart, checkout, or both:
}
```

Businesses MAY advertise discount support for cart only, checkout only, or
both. Platforms SHOULD check which resources are extended before submitting
discount codes.
Businesses MAY advertise discount support for any subset of these capabilities.
Platforms SHOULD check which resources are extended before presenting promotion
previews or submitting discount codes.

## Schema

When this capability is active, cart and/or checkout are extended with a
`discounts` object.
When this capability is active, the negotiated Catalog Lookup, Cart, and/or
Checkout operations are extended with a `discounts` object. Catalog adds the
object only to `get_product` responses; Cart and Checkout also accept discount
codes in requests.

### Discounts Object

{{ extension_schema_fields('discount.json#/$defs/discounts_object', 'shopping/extensions/discount') }}

### Catalog Discounts Object

{{ extension_schema_fields('discount.json#/$defs/catalog_discounts_object', 'shopping/extensions/discount') }}

### Available Discount

{{ extension_schema_fields('discount.json#/$defs/available_discount', 'shopping/extensions/discount') }}

### Applied Discount

{{ extension_schema_fields('discount.json#/$defs/applied_discount', 'shopping/extensions/discount') }}
Expand All @@ -78,6 +92,75 @@ When this capability is active, cart and/or checkout are extended with a

{{ extension_schema_fields('discount.json#/$defs/allocation', 'shopping/extensions/discount') }}

## Catalog Promotion Previews

When the Discount extension is negotiated for Catalog Lookup, a Business MAY
return `discounts.available` in a `get_product` response. These entries are
public promotion previews. They allow a Platform to describe a potentially
applicable offer before the buyer creates a Cart or Checkout.

Catalog previews are advisory. Cart or Checkout evaluation is authoritative for
eligibility, application, calculated amounts, and allocations. When a Business
returns `conditions`, a Platform MUST present them and MUST NOT represent a
preview as a final eligibility decision.

Catalog Lookup's existing `selected`, `filters`, `context`, `signals`,
`attribution`, and `actions` semantics remain unchanged. A Business MAY use the
existing request context to determine which public previews to return, but a
Platform MUST NOT reuse a context-specific preview as a context-free eligibility
decision. This extension does not define new request inputs or Action types.

The Catalog composition applies only to `get_product` responses:

- `lookup_catalog` requests and responses are unchanged.
- `get_product` requests are unchanged.
- `discounts.codes` and `discounts.applied` are not available in Catalog.
- `applied` and `remaining_amount` are omitted from Catalog preview entries.
- For a promotion with `type: "code"`, the Business discloses that an offer
exists but does not disclose the code itself.

<!-- ucp:example schema=shopping/discount def=discount_get_product_response -->
```json
{
"ucp": {
"version": "{{ ucp_version }}"
},
"product": {
"id": "prod_jacket",
"title": "Unisex Jacket",
"description": { "plain": "A lightweight everyday jacket." },
"price_range": {
"min": { "amount": 7900, "currency": "USD" },
"max": { "amount": 7900, "currency": "USD" }
},
"variants": [
{
"id": "var_jacket_black",
"title": "Black",
"description": { "plain": "Black lightweight jacket." },
"price": { "amount": 7900, "currency": "USD" }
}
]
},
"discounts": {
"available": [
{
"id": "promo_category_20",
"title": "20% off selected styles",
"type": "automatic",
"conditions": "Available on eligible styles for a limited time"
},
{
"id": "promo_member",
"title": "Member offer available",
"type": "code",
"conditions": "Eligible members can enter their offer code at checkout"
}
]
}
}
```

## Allocation Details

The `applied` array explains how discounts were calculated and distributed.
Expand Down
111 changes: 110 additions & 1 deletion source/schemas/shopping/discount.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"$id": "https://ucp.dev/schemas/shopping/discount.json",
"name": "dev.ucp.shopping.discount",
"title": "Discount Extension",
"description": "Extends Cart and Checkout with discount support, including discount codes, automatic discounts, and eligibility-triggered provisional discounts.",
"description": "Extends Catalog Lookup, Cart, and Checkout with discount support, including discoverable promotions, discount codes, automatic discounts, and eligibility-triggered provisional discounts.",
"$defs": {
"allocation": {
"type": "object",
Expand Down Expand Up @@ -79,6 +79,61 @@
}
}
},
"available_discount": {
"type": "object",
"description": "A discount available for discovery in the current context.",
"required": [
"title",
"type"
],
"properties": {
"id": {
"type": "string",
"description": "Stable discount identifier."
},
"title": {
"type": "string",
"description": "Human-readable promotion name (e.g., 'Summer Sale — 15% off')."
},
"type": {
"type": "string",
"enum": ["automatic", "code"],
"description": "'automatic' — business applies when conditions are met, no code needed. 'code' — buyer must submit a code (the code itself is NOT exposed)."
},
"conditions": {
"type": "string",
"description": "Human-readable summary of conditions to qualify (e.g., 'Orders over $50')."
},
"applied": {
"type": "boolean",
"description": "Whether this discount is already applied to the current cart/checkout."
},
"remaining_amount": {
"$ref": "../common/types/amount.json",
"description": "How much more the buyer needs to spend to qualify, in ISO 4217 minor units. Useful for 'add $12 more for free shipping' prompts."
}
}
},
"catalog_available_discount": {
"description": "A discoverable discount preview for a catalog product. Application state and cart-relative thresholds are not available in Catalog.",
"allOf": [
{
"$ref": "#/$defs/available_discount"
},
{
"not": {
"anyOf": [
{
"required": ["applied"]
},
{
"required": ["remaining_amount"]
}
]
}
}
]
},
"discounts_object": {
"type": "object",
"description": "Discount codes input and applied discounts output.",
Expand All @@ -97,6 +152,60 @@
},
"description": "Discounts successfully applied (code-based and automatic).",
"ucp_request": "omit"
},
"available": {
"type": "array",
"items": {
"$ref": "#/$defs/available_discount"
},
"description": "Discounts available for this cart or checkout context. Business-optional. Includes both automatic promotions and discoverable code-based offers.",
"ucp_request": "omit"
}
}
},
"catalog_discounts_object": {
"type": "object",
"description": "Publicly discoverable discounts for a catalog product detail response.",
"properties": {
"available": {
"type": "array",
"items": {
"$ref": "#/$defs/catalog_available_discount"
},
"description": "Promotions that may apply to the requested product. Cart or Checkout evaluation remains authoritative."
}
}
},
"discount_get_product_response": {
"allOf": [
{
"$ref": "catalog_lookup.json#/$defs/get_product_response"
},
{
"type": "object",
"properties": {
"discounts": {
"$ref": "#/$defs/catalog_discounts_object",
"ucp_request": "omit"
}
}
}
]
},
"dev.ucp.shopping.catalog.lookup": {
"description": "Catalog Lookup composition with discoverable discounts on product detail responses.",
"$defs": {
"lookup_request": {
"$ref": "catalog_lookup.json#/$defs/lookup_request"
},
"lookup_response": {
"$ref": "catalog_lookup.json#/$defs/lookup_response"
},
"get_product_request": {
"$ref": "catalog_lookup.json#/$defs/get_product_request"
},
"get_product_response": {
"$ref": "#/$defs/discount_get_product_response"
}
}
},
Expand Down
Loading