diff --git a/docs/specification/shopping/extensions/discount.md b/docs/specification/shopping/extensions/discount.md index 4b538d503..34f628ea5 100644 --- a/docs/specification/shopping/extensions/discount.md +++ b/docs/specification/shopping/extensions/discount.md @@ -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: ```json @@ -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" } @@ -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') }} @@ -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. + + +```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. diff --git a/source/schemas/shopping/discount.json b/source/schemas/shopping/discount.json index a0d48a74e..8a57c5cdf 100644 --- a/source/schemas/shopping/discount.json +++ b/source/schemas/shopping/discount.json @@ -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", @@ -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.", @@ -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" } } },