-
Notifications
You must be signed in to change notification settings - Fork 317
feat: Add basic schema for loyalty extension #251
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
ziwuzhou-google
wants to merge
5
commits into
Universal-Commerce-Protocol:main
Choose a base branch
from
ziwuzhou-google:feat/loyalty
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+754
−0
Open
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
88d8c08
feat: Add documentation and basic schema for loyalty extension.
ziwuzhou-google c268234
feat (loyalty): fix reviewer comments.
ziwuzhou-google b712f66
feat (loyalty): minor updates for naming changes, schema definition, …
ziwuzhou-google 97167b9
feat (loyalty): Add the missing documentation update.
ziwuzhou-google 32d1f8a
Add more examples in the document and address some comments from the …
ziwuzhou-google File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,394 @@ | ||
| <!-- | ||
| Copyright 2026 UCP Authors | ||
|
|
||
| Licensed under the Apache License, Version 2.0 (the "License"); | ||
| you may not use this file except in compliance with the License. | ||
| You may obtain a copy of the License at | ||
|
|
||
| http://www.apache.org/licenses/LICENSE-2.0 | ||
|
|
||
| Unless required by applicable law or agreed to in writing, software | ||
| distributed under the License is distributed on an "AS IS" BASIS, | ||
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| See the License for the specific language governing permissions and | ||
| limitations under the License. | ||
| --> | ||
|
|
||
| # Loyalty Extension | ||
|
|
||
| ## Overview | ||
|
|
||
| The loyalty extension is designed to facilitate high-fidelity loyalty experiences across various commerce journeys, ensuring that shoppers can for example sign-up for membership, access personalized benefits, redeem rewards, and manage memberships seamlessly across various capabilities. | ||
ziwuzhou-google marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| In its current format, it can be used to decorate the following Capabilities for example: | ||
|
|
||
| * **Checkout**: For applying member-specific discounts/fulfillment benefits, calculating point earnings, or initiating reward redemptions, in conjunction with discount extension to specify the monetary effect of applicable loyalty benefits | ||
| * **Cart**: For displaying the loyalty benefits for potential usage on subsequent checkout (e.g. redeemable balance, tier evaluations), with or without the help of discount extension to upsell potential savings and non-monetary benefits from membership | ||
|
|
||
| In the future, for membership lifecycle management by agents (e.g. membership status fetching, program sign-up, balance transfer, etc.). it is possible that loyalty could be considered an independent capability. The other direction is fitting them within higher-order account and profile management capabilities that allow for broader operations, with loyalty being a subset and hence potentially represented by the same extension as it. This is not yet finalized among community discussion and pending future updates to achieve. | ||
|
|
||
| ## Key Concepts | ||
|
|
||
| Loyalty has four main components: | ||
|
|
||
| **Memberships**: the overarching framework of a loyalty program, as well as the specific enrollment status and standing of a customer within it | ||
|
|
||
| * Usually represents a customer lifecycle solution the businesses have, which offer a tiered ecosystem of access, convenience, and identity. | ||
| * Encompasses the metadata of the concrete offerings as well as the customer level information associated with (if applicable). | ||
| * Customers can have multiple memberships that are relevant to the business (e.g. self-owned program and third-party program). | ||
|
|
||
| **Tiers**: progressive achievement levels within a membership that unlock increasing value based on activity or spend/fee payment | ||
|
|
||
| * Defines achievable levels within a membership for a customer, with various types of qualification methods and conditions. | ||
| * Customers can simultaneously be activated on and eligible for multiple tiers if the program allows. | ||
|
|
||
| **Benefits**: ongoing perks and privileges granted to a customer based on their current tier or membership status | ||
|
|
||
| * Contains both delayed (e.g. “Members have access to dedicated customer service”) and immediate-value (e.g. “Members get 5% off”) benefits. | ||
|
|
||
| **Rewards**: the accumulated balances and/or stored value available for the customer to redeem on transactions | ||
|
|
||
| * One membership can offer multiple types of accumulable/collectable rewards, each having its own usage and redemption rules. | ||
|
|
||
| ## Discovery | ||
|
|
||
| Businesses can follow standard advertisement mechanism to advertise loyalty support in the Business profile | ||
|
|
||
| ```json | ||
| { | ||
| "ucp": { | ||
| "version": "2026-01-23", | ||
| "capabilities": { | ||
| "dev.ucp.common.loyalty": [ | ||
| { | ||
| "version": "2026-01-23", | ||
| "extends": "dev.ucp.shopping.checkout", | ||
| "spec": "https://ucp.dev/2026-01-23/specification/loyalty", | ||
| "schema": "https://ucp.dev/2026-01-23/schemas/common/loyalty.json" | ||
| } | ||
| ] | ||
| } | ||
| } | ||
| } | ||
| ``` | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There's no mention/ link to identity linking or |
||
|
|
||
| ## Schema | ||
|
|
||
| ### Entities | ||
|
|
||
| #### Loyalty | ||
|
|
||
| {{ schema_fields('types/loyalty', 'loyalty') }} | ||
|
|
||
| #### Loyalty Membership | ||
|
|
||
| {{ schema_fields('types/loyalty_membership', 'loyalty') }} | ||
|
|
||
| #### Membership Tier | ||
|
|
||
| {{ schema_fields('types/membership_tier', 'loyalty') }} | ||
|
|
||
| #### Membership Tier Condition | ||
|
|
||
| {{ schema_fields('types/membership_tier_condition', 'loyalty') }} | ||
|
|
||
| #### Membership Tier Benefit | ||
|
|
||
| {{ schema_fields('types/membership_tier_benefit', 'loyalty') }} | ||
|
|
||
| #### Membership Reward | ||
|
|
||
| {{ schema_fields('types/membership_reward', 'loyalty') }} | ||
|
|
||
| #### Balance Currency | ||
|
|
||
| {{ schema_fields('types/balance_currency', 'loyalty') }} | ||
|
|
||
| #### Membership Balance | ||
|
|
||
| {{ schema_fields('types/membership_balance', 'loyalty') }} | ||
|
|
||
| #### Expiring Balance | ||
|
|
||
| {{ schema_fields('types/expiring_balance', 'loyalty') }} | ||
|
|
||
| #### Balance Redemption | ||
|
|
||
| {{ schema_fields('types/balance_redemption', 'loyalty') }} | ||
|
|
||
| ## Example | ||
|
|
||
| ### Membership sign-up | ||
|
|
||
| Buyer wishes to sign up to be a GOLD tier member of the loyalty program offered by the business. | ||
|
|
||
| === "Request" | ||
|
|
||
| ```json | ||
| { | ||
| "loyalty": { | ||
| "memberships": [ | ||
| { | ||
| "id": "membership_1", | ||
| "member_id": "member_id_1" | ||
| } | ||
| ], | ||
| "activated_memberships": ["membership_1"] | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| === "Response" | ||
|
|
||
| ```json | ||
| { | ||
| "memberships": [ | ||
| { | ||
| "id": "membership_1", | ||
| "member_id": "member_id_1", | ||
| "name": "My Loyalty Program", | ||
| "enrollment_date": "2026-03-20T00:00:00Z", | ||
| "last_activity_date": "2026-03-20T00:00:00Z", | ||
| "tiers": [ | ||
| { | ||
| "id": "tier_1", | ||
| "name": "GOLD", | ||
| "level": 1, | ||
| "benefits": [ | ||
| { | ||
| "id": "BEN_001", | ||
| "title": "24-hour Early access", | ||
| "description": "24-hour early access to seasonal sales" | ||
| } | ||
| ], | ||
| "conditions": [ | ||
| { | ||
| "id": "CON_001", | ||
| "description": "Free to join", | ||
| "condition_type": "free" | ||
| } | ||
| ], | ||
| "links": [ | ||
| { | ||
| "type": "terms_of_service", | ||
| "url": "loyalty.com/terms", | ||
| "title": "Sign-up Terms" | ||
| } | ||
| ] | ||
| } | ||
| ], | ||
| "activated_tiers": ["tier_1"], | ||
| "rewards": [ | ||
| { | ||
| "currency": { | ||
| "name": "LoyaltyStars", | ||
| "code": "LST", | ||
| }, | ||
| "balance": { | ||
| "available": 0 | ||
| } | ||
| } | ||
| ] | ||
| } | ||
| ], | ||
| "activated_memberships": ["membership_1"] | ||
| } | ||
| ``` | ||
|
|
||
| ### Point redemption | ||
|
|
||
| Redeem loyalty point balance for a discount that leads to reduction of total cost. | ||
|
|
||
| === "Request" | ||
|
|
||
| ```json | ||
| { | ||
| "context": { | ||
| "eligibility": ["com.example.loyalty_tier_1"] | ||
| }, | ||
| "line_items": [ | ||
| { | ||
| "item": { | ||
| "id": "prod_shirt", | ||
| "quantity": 2, | ||
| "price": 2500 | ||
| } | ||
| } | ||
| ], | ||
| "loyalty": { | ||
| "memberships": [ | ||
| { | ||
| "id": "membership_1", | ||
| "member_id": "member_id_1", | ||
| "rewards": [ | ||
| { | ||
| "currency": { | ||
| "code": "LST" | ||
| }, | ||
| "redemption": { | ||
| "amount": 100 | ||
| } | ||
| } | ||
| ] | ||
| } | ||
| ] | ||
| } | ||
| } | ||
| ``` | ||
| === "Response" | ||
|
|
||
| ```json | ||
| { | ||
| "discounts": { | ||
| "applied": [ | ||
| { | ||
| "title": "Point Redemption", | ||
| "amount": 250, | ||
| "automatic": true, | ||
| "provisional": true, | ||
| "eligibility": "com.example.loyalty_tier_1", | ||
| "priority": 1, | ||
| "method": "each", | ||
| "allocations": [ | ||
| {"path": "$.line_items[0]", "amount": 250} | ||
| ] | ||
| } | ||
| ] | ||
| }, | ||
| "loyalty": { | ||
| "memberships": [ | ||
| { | ||
| "id": "membership_1", | ||
| "member_id": "member_id_1", | ||
| "name": "My Loyalty Program", | ||
| "tiers": [ | ||
| { | ||
| "id": "tier_1", | ||
| "name": "GOLD", | ||
| "level": 1, | ||
| "provisional": true, | ||
| "eligibility": "com.example.loyalty_tier_1", | ||
| } | ||
| ], | ||
| "activated_tiers": ["tier_1"], | ||
| "rewards": [ | ||
| { | ||
| "currency": { | ||
| "name": "LoyaltyStars", | ||
| "code": "LST" | ||
| }, | ||
| "balance": { | ||
| "available": 4500 | ||
| }, | ||
| "redemption": { | ||
| "amount": 100, | ||
| "applies_on": "$.discounts.applied[0]" | ||
| } | ||
| } | ||
| ] | ||
| } | ||
| ], | ||
| "activated_memberships": ["membership_1"] | ||
| }, | ||
| "totals": [ | ||
| {"type": "subtotal", "display_text": "Subtotal", "amount": 5000}, | ||
| {"type": "items_discount", "display_text": "Point Redemption", "amount": -250}, | ||
| {"type": "total", "display_text": "Total", "amount": 4750} | ||
| ] | ||
| } | ||
| ``` | ||
|
|
||
| ### Member pricing | ||
|
|
||
| Buyer, as a loyalty member, receives special lower price on eligible item. | ||
|
|
||
| === "Request" | ||
|
|
||
| ```json | ||
| { | ||
| "context": { | ||
| "eligibility": ["com.example.loyalty_tier_1"] | ||
| }, | ||
| "line_items": [ | ||
| { | ||
| "item": { | ||
| "id": "prod_shirt", | ||
| "quantity": 2, | ||
| "price": 2000 | ||
| } | ||
| } | ||
| ] | ||
| } | ||
| ``` | ||
|
|
||
| === "Response" | ||
|
|
||
| ```json | ||
| { | ||
| "discounts": { | ||
| "applied": [ | ||
| { | ||
| "title": "Member discount", | ||
| "amount": 250, | ||
| "automatic": true, | ||
| "provisional": true, | ||
| "eligibility": "com.example.loyalty_tier_1", | ||
| "priority": 1, | ||
| "method": "each", | ||
| "allocations": [ | ||
| {"path": "$.line_items[0]", "amount": 250} | ||
| ] | ||
| } | ||
| ] | ||
| }, | ||
| "loyalty": { | ||
| "memberships": [ | ||
| { | ||
| "id": "membership_1", | ||
| "member_id": "member_id_1", | ||
| "name": "My Loyalty Program", | ||
| "tiers": [ | ||
| { | ||
| "id": "tier_1", | ||
| "name": "GOLD", | ||
| "level": 1, | ||
| "benefits": [ | ||
| { | ||
| "id": "BEN_001", | ||
| "title": "Member discount", | ||
| "description": "Members get $2.5 off", | ||
| "applies_on": "$.discounts.applied[0]" | ||
| } | ||
| ], | ||
| "provisional": true, | ||
| "eligibility": "com.example.loyalty_tier_1", | ||
| } | ||
| ], | ||
| "activated_tiers": ["tier_1"] | ||
| } | ||
| ], | ||
| "activated_memberships": ["membership_1"] | ||
| }, | ||
| "totals": [ | ||
| {"type": "subtotal", "display_text": "Subtotal", "amount": 5000}, | ||
| {"type": "items_discount", "display_text": "Point Redemption", "amount": -250}, | ||
| {"type": "total", "display_text": "Total", "amount": 4750} | ||
| ] | ||
| } | ||
| ``` | ||
|
|
||
| ## Behavior and Expectations | ||
|
|
||
| ### Request | ||
|
|
||
| * Non-empty `activated_memberships` and `activated_tiers` implies membership sign-up intention from the customer. No eligibility verification needed, but can rely on the `condition_type` of `membership_tier_condition` within each `membership_tier`to provide selections for customers (e.g. only offer `free` typed tier for sign up). | ||
| * Render applicable loyalty benefits based on the human-readable `title` and `description` provided, and highlight the source of immediate-value benefits alongside where they are applied based on the back referencing from `applies_on` to provide necessary disclosure or explanation. | ||
| * Only request rewards redemption when there is non-zero available balance but no need to run any extra check as redemption rules can be complicated even if there is sufficient available balance for example. | ||
|
|
||
| ### Response | ||
|
|
||
| * Check and determine user’s eligibility in context (e.g. checkout with member benefits applied, checkout with rewards redemption, sign-up new membership etc) for both existing and prospective customers. When eligibility condition is not met, response clear ERROR message to indicate that. | ||
| * Populate `applies_on` when immediate-value membership benefits are applicable to the transaction and reference them to the corresponding line items. | ||
| * Provide a comprehensive list of tiers that the membership offers, but at the minimum it MUST contain the ones that the customer is activated with (if applicable). | ||
| * For memberships that do not have a tiered system (i.e. loyalty level can not be upgraded or downgraded), it MUST still be treated as a single-tiered membership. | ||
| * Businesses MUST respond with at least one benefit and SHOULD aim to provide a comprehensive list. | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are some parts of this document that read a little more mechanical than other specification docs in UCP. I’d recommend putting this new spec and its siblings, like fulfillment, discount, and checkout, through your favorite LLM. I would at least expect to see a bit more guidance using the MUST/ SHOULD/ MAY structure of the other specifications.