diff --git a/docs/specification/approaching-discounts.md b/docs/specification/approaching-discounts.md new file mode 100644 index 000000000..b7fb92c2f --- /dev/null +++ b/docs/specification/approaching-discounts.md @@ -0,0 +1,224 @@ + + +# Approaching Discounts (Part of the Discounts Extension) + +## Overview + +Approaching discounts enables buyers to be informed when they are approaching +a cart (\$10 off orders >= \$100) or shipping discount (free shipping on orders >= $100). + +Approaching discounts are for promotions that trigger based on a qualifying amount +and contain an upsell threshold, enabling the merchant to specify when the approaching discount +message is shown to the buyer. + +This enhancement to the discount extension, enables merchants to upsell buyers and increase +both conversion and average order value. + +**Key features:** + +- Promotional messaging for promotions the buyer is near (within the upsell threshold) + +**Dependencies:** + +- Cart or Checkout Capability with Discount Extension + +## Discovery + +Businesses advertise discount extension support in their profile. Approaching discounts +are part of the base discount extension. + +```json +{ + "ucp": { + "version": "{{ ucp_version }}", + "capabilities": { + "dev.ucp.shopping.discount": [ + { + "extends": "dev.ucp.shopping.checkout", + "schema": "https://ucp.dev/schemas/shopping/discount.json", + "spec": "https://ucp.dev/specs/shopping/discount", + "version": "2026-04-08" + } + ] + } + } +} +``` + +## Schema + +When the discount extension capability is active, an additional object, approaching, is added to the discount response within +the cart and checkout APIs. This object contains up to two sections: cart and shipping. + +### Approaching Discount + +{{ schema_fields('types/approaching-discount', 'approaching') }} + +### Cart + +Cart contains approaching discounts that apply to the entire cart (i.e. $10 off ) + +{{ extension_schema_fields('types/approaching-discount.json#/$defs/cart', 'cart') }} + +### Shipping + +Shipping contains approaching discounts that apply to an individual fulfillment group (or shipment). + +{{ extension_schema_fields('types/approaching-discount.json#/$defs/shipping', 'shipping') }} + +## How It Works + +### Cart + +Expanding on the above example, \$10 off orders >= \$100, the qualifying amount is \$100. +If the upsell threshold is set to \$25, the message will be returned once the cart exceeds +\$75 in value (but less than \$100 when the promotion applies). + +### Shipping + +Similarly, shipping approaching discounts are used to convey discounted or free shipping. +Shipping approaching discounts are calculated based on the amount of qualifying products within the fulfillment group. +i.e. Free shipping on orders >= \$100, upsell threshold \$25 + +### Re-evaluation + +As with discounts, approaching discounts are re-evaluated each time the cart is accessed. If the cart no longer meets the +threshold for the approaching discount, qualifies for the promotion, or the promotion has expired, the approaching message +is not returned. + +## Examples + +### Cart Approaching Discount + + $10 off orders >= $100, upsell threshold $25 + Cart contains 1, $75 item + + Checkout / Cart response + +```json + "discounts": { + "approaching": { + "cart": [ + { + "threshold": 10000, + "title": "$10 off orders over $100", + "total": 7500 + } + ] + } + } +``` +The quantity of the cart is increased to 2, the cart now contains $150. The approaching message disappears and is +replaced with the discount. + +```json +"discounts": { + "applied": [ + { + "allocations": [ + { + "amount": 1000, + "path": "$.line_items[0]" + } + ], + "amount": 1000, + "automatic": true, + "method": "across", + "title": "$10 off orders over $100" + } + ] + } +``` +### Shipping Approaching Discount + + Free shipping on orders >= $100, upsell threshold $25 + Cart contains 1, $75 item + + Checkout / Cart response (relevant sections) + +Groups +```json + "groups": [ + { + "id": "group1", + "line_item_ids": [ + "a2d4b6fe79289963ac7543bcf6" + ], + "options": [ + { + "description": "Order received within 7-10 business days", + "id": "001", + "title": "Ground", + "totals": [ + { + "amount": 599, + "type": "subtotal" + }, + { + "amount": 30, + "type": "tax" + }, + { + "amount": 629, + "type": "total" + } + ] + } + ], + "selected_option_id": "001" + } + ] +``` + +Discounts +```json + "discounts": { + "approaching": { + "shipping": [ + { + "fulfillment_group_id": "group1", + "threshold": 10000, + "title": "Free Shipping on Orders over $100", + "total": 7500 + } + ] + } + } +``` + +The quantity of the cart is increased to 2, the cart now contains $150. The approaching message disappears and is +replaced with the discount. + +```json + "discounts": { + "applied": [ + { + "allocations": [ + { + "amount": 599, + "path": "$.fulfillment.methods[0]" + } + ], + "amount": 799, + "automatic": true, + "method": "across", + "title": "Free Shipping on Orders over $100" + } + ] + } +``` + diff --git a/docs/specification/discount.md b/docs/specification/discount.md index 42bb5d940..a828c93eb 100644 --- a/docs/specification/discount.md +++ b/docs/specification/discount.md @@ -191,6 +191,16 @@ segment, or promotional rules: - Cannot be removed by the platform - Surfaced for transparency (platform can explain to user why discount was applied) +## Approaching Discounts + +Businesses may return details of approaching promotions. This is used to entice the buyer +to buy more to receive a discount (i.e. \$10 off or free shipping) when within a threshold +of the qualifying amount (i.e. >= \$100)) + +- Appear in `discounts.approaching` + +For more details, see [approaching-discounts.md] (approaching-discounts.md) + ## Eligibility Claims Eligibility claims are buyer claims about eligible benefits (see diff --git a/source/schemas/shopping/discount.json b/source/schemas/shopping/discount.json index 04c81dd0f..099ee4808 100644 --- a/source/schemas/shopping/discount.json +++ b/source/schemas/shopping/discount.json @@ -97,6 +97,11 @@ }, "description": "Discounts successfully applied (code-based and automatic).", "ucp_request": "omit" + }, + "approaching": { + "$ref": "types/approaching_discount.json", + "description": "Promotions the buyer is close to unlocking. Omitted in requests.", + "ucp_request": "omit" } } }, diff --git a/source/schemas/shopping/types/approaching_discount.json b/source/schemas/shopping/types/approaching_discount.json new file mode 100644 index 000000000..b2bc3126f --- /dev/null +++ b/source/schemas/shopping/types/approaching_discount.json @@ -0,0 +1,75 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://ucp.dev/schemas/shopping/types/approaching_discount.json", + "title": "Approaching Discount", + "type": "object", + "description": "Promotions the buyer is close to unlocking, grouped by promotion class. Each array entry surfaces the gap between the current cart total and the promotion's purchase-condition threshold, enabling upsell messaging (e.g., 'Add $10 more to get free shipping'). Omitted in requests.", + "properties": { + "cart": { + "type": "array", + "description": "Order-level promotions the buyer is close to unlocking.", + "items": { + "type": "object", + "required": [ + "title", + "threshold", + "total" + ], + "properties": { + "title": { + "type": "string", + "description": "Discount name (e.g., '$10 off orders >= $50').", + "minLength": 1, + "maxLength": 200 + }, + "threshold": { + "type": "integer", + "description": "The amount (in ISO 4217 minor currency units) the buyer must have in their cart to unlock the promotion. Always non-negative.", + "minimum": 0 + }, + "total": { + "type": "integer", + "description": "Total merchandise amount (in ISO 4217 minor currency units) in the cart that qualifies toward this promotion's purchase condition. May represent the entire cart subtotal or a subset (e.g., items in a specific category) depending on the promotion's scope. Always non-negative.", + "minimum": 0 + } + } + } + }, + "shipping": { + "type": "array", + "description": "Shipping-level promotions the buyer is close to unlocking.", + "items": { + "type": "object", + "required": [ + "title", + "threshold", + "total" + ], + "properties": { + "title": { + "type": "string", + "description": "Human-readable discount name (e.g., '$10 off orders >= $50').", + "minLength": 1, + "maxLength": 200 + }, + "threshold": { + "type": "integer", + "description": "The amount (in ISO 4217 minor currency units) the buyer must have in their cart to unlock the promotion. Always non-negative.", + "minimum": 0 + }, + "total": { + "type": "integer", + "description": "Total merchandise amount (in ISO 4217 minor currency units) in the cart that qualifies toward this promotion's purchase condition. May represent the entire cart subtotal or a subset (e.g., items in a specific category) depending on the promotion's scope. Always non-negative.", + "minimum": 0 + }, + "fulfillment_group_id": { + "type": "string", + "description": "Identifier of the fulfillment group this shipping promotion applies to. When present, the promotion is scoped to that fulfillment group rather than the entire order's shipping cost.", + "minLength": 1, + "maxLength": 256 + } + } + } + } + } +}