-
Notifications
You must be signed in to change notification settings - Fork 432
docs: document business-owned response values #519
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
karangoel16
wants to merge
4
commits into
Universal-Commerce-Protocol:main
Choose a base branch
from
karangoel16:feat/saved-payment-instruments-extension
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.
Open
Changes from all commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
3ff52fe
feat: add Saved Payment Instruments extension (dev.ucp.shopping.saved…
karangoel16 8df60a5
Merge branch 'main' into feat/saved-payment-instruments-extension
karangoel16 dea0d34
docs: repurpose saved instruments guidance
karangoel16 eb75938
Merge branch 'main' into feat/saved-payment-instruments-extension
damaz91 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,174 @@ | ||
| <!-- | ||
| 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. | ||
| --> | ||
|
|
||
| # Business-Owned Response Values | ||
|
|
||
| ## Overview | ||
|
|
||
| UCP response objects may include values supplied by the business from its own | ||
| user state, not only values previously supplied by the platform. When a request | ||
| is associated with an authenticated user through | ||
| [Identity Linking](identity-linking.md), a business can populate existing | ||
| checkout, cart, buyer, payment, and fulfillment fields with display-safe values | ||
| that it already knows for that user. | ||
|
|
||
| This pattern avoids creating a separate extension for every kind of saved user | ||
| state. For example, a business can return a user's saved payment methods in the | ||
| existing `payment.instruments[]` array on cart or checkout responses. The | ||
| platform can render those instruments for selection using the same payment | ||
| instrument shape it already understands. | ||
|
|
||
| ## Common Cases | ||
|
|
||
| Business-owned response values are useful for: | ||
|
|
||
| * **Saved payment instruments:** A business returns display-safe saved cards or | ||
| wallet accounts in `payment.instruments[]`. | ||
| * **Buyer profile data:** A business pre-populates `buyer` fields from the | ||
| authenticated user's account. | ||
| * **Saved fulfillment information:** A business returns transaction-eligible | ||
| fulfillment options or addresses where the capability allows it. | ||
|
|
||
| The business remains responsible for authorization, ownership checks, data | ||
| minimization, and only returning values appropriate for the current user and | ||
| transaction. | ||
|
|
||
| ## Saved Payment Instruments | ||
|
|
||
| Saved payment instruments should use the existing `payment.instruments[]` | ||
| surface. A saved instrument is represented as a normal payment instrument with | ||
| an opaque business-scoped `id`, a `handler_id`, a `type`, and display-safe | ||
| metadata under `display`. | ||
|
|
||
| The response should not include raw payment credentials unless the active | ||
| payment handler explicitly requires credential material on that surface. For | ||
| business-vaulted saved instruments, the `id` is the reference the business can | ||
| resolve server-side when the buyer selects that instrument for checkout. | ||
|
|
||
| The base UCP schema does not currently standardize a generic "saved" or | ||
| "default" flag. A business can indicate the recommended choice by setting | ||
| `selected: true` when the instrument is selected for the current transaction, | ||
| and can use handler-specific display fields for richer UI labels. A broader | ||
| generic reference/tokenized-node abstraction may standardize more metadata in | ||
| the future. | ||
|
|
||
| ## Sample Checkout Response | ||
|
|
||
| In this example, the platform created a checkout with a user-identity bearer | ||
| token. The business recognizes the user and returns a saved Shop Pay instrument | ||
| in the existing `payment.instruments[]` array. The instrument carries no raw | ||
| credential; it is a display-safe business-owned reference. | ||
|
|
||
| ```json | ||
| { | ||
| "ucp": { | ||
| "version": "{{ ucp_version }}", | ||
| "capabilities": { | ||
| "dev.ucp.shopping.checkout": [ | ||
| { | ||
| "version": "{{ ucp_version }}" | ||
| } | ||
| ], | ||
| "dev.ucp.common.identity_linking": [ | ||
| { | ||
| "version": "{{ ucp_version }}" | ||
| } | ||
| ] | ||
| }, | ||
| "payment_handlers": { | ||
| "com.shopify.shop_pay": [ | ||
| { | ||
| "id": "shop_pay_1234", | ||
| "version": "{{ ucp_version }}", | ||
| "available_instruments": [ | ||
| { | ||
| "type": "shop_pay" | ||
| } | ||
| ] | ||
| } | ||
| ] | ||
| } | ||
| }, | ||
| "id": "checkout_123", | ||
| "status": "ready_for_complete", | ||
| "currency": "USD", | ||
| "line_items": [ | ||
| { | ||
| "id": "line_item_1", | ||
| "item": { | ||
| "id": "prod_1", | ||
| "title": "T-Shirt", | ||
| "price": 2000 | ||
| }, | ||
| "quantity": 1, | ||
| "totals": [ | ||
| { | ||
| "type": "subtotal", | ||
| "amount": 2000 | ||
| } | ||
| ] | ||
| } | ||
| ], | ||
| "totals": [ | ||
| { | ||
| "type": "subtotal", | ||
| "amount": 2000 | ||
| }, | ||
| { | ||
| "type": "tax", | ||
| "amount": 160 | ||
| }, | ||
| { | ||
| "type": "total", | ||
| "amount": 2160 | ||
| } | ||
| ], | ||
| "payment": { | ||
| "instruments": [ | ||
| { | ||
| "id": "saved_shop_pay_4242", | ||
| "handler_id": "shop_pay_1234", | ||
| "type": "shop_pay", | ||
| "selected": true, | ||
| "display": { | ||
| "description": "Saved Shop Pay account", | ||
| "email": "buyer@example.com", | ||
| "last_digits": "4242" | ||
| } | ||
| } | ||
| ] | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| If the buyer chooses this instrument, the platform can submit the selected | ||
| instrument by `id` in the next checkout update or completion request. The | ||
| business verifies ownership against the authenticated user and resolves the | ||
| reference through its own vault or payment handler integration. | ||
|
|
||
| ## Guidance | ||
|
|
||
| * Businesses MUST only return business-owned values that belong to the | ||
| authenticated user and are appropriate for the current transaction. | ||
| * Platforms MUST treat returned business-owned identifiers as opaque and scoped | ||
| to that business. | ||
| * Platforms SHOULD render only display-safe fields and MUST NOT infer raw | ||
| credentials from a display value. | ||
| * Businesses SHOULD omit business-owned user state when the request lacks the | ||
| required user authorization. | ||
| * Empty arrays are meaningful: they indicate the business checked the relevant | ||
| state and found no transaction-eligible values. Omitted fields indicate the | ||
| business did not expose that state on the response. | ||
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
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
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.
Are we aware of any such examples? This would run counter to UCP best practices for credentials. As we make any docs updates I'd suggest leaving this out.
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.
+1. @karangoel16 we should not be condoning transmission of unencrypted credentials at this layer.