docs: document business-owned response values#519
Conversation
|
Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA). View this failed invocation of the CLA check for more information. For the most up to date status, view the checks section at the bottom of the pull request. |
87da12d to
be1335c
Compare
…_instruments)
Adds a response-only extension that surfaces the authenticated user's
saved payment instruments on cart and checkout responses under
`payment.saved_instruments[]`. Identity-gated by a new
`dev.ucp.shopping.saved_instruments:read` scope listed in
`identity_linking.config.scopes`. Mirrors the loyalty extension pattern.
Key design choices:
- Response-only (`ucp_request: omit`) — flows merchant → platform only
- No credential on the wire: schema enforces `"credential": { "not": {} }`.
Platforms select via `payment.instruments[].id`; the business mints a
checkout-bound TokenCredential per its declared payment handler.
- Per-instrument `default: true` flag instead of inferring from list order
- Business-scoped IDs; cross-merchant portability explicitly out of scope
be1335c to
3ff52fe
Compare
| * **Expiration awareness:** Surface "your saved card expires next month" | ||
| inline with cart/checkout. | ||
|
|
||
| A standalone capability that allows enumeration and management of saved |
There was a problem hiding this comment.
will remove this
|
Hi @raginpirate, @amithanda , wanted your opinion on the following, since I saw that you both have been actively contributing in this area of identity linking :) cc: @jamesandersen |
|
@karangoel16 thanks for raising this PR proposing a way for users to tap into one of the key benefits of "logging into" the business - accessing saved payment methods to reduce checkout friction! This is directly referenced as one of the possible benefits ("saved addresses and credentials") of the identity linking capability and #358 also raises a very similar proposal. With the recent identity linking updates in #354 landed, it feels like a good time to dig into the details for saved payment methods ... and potentially other checkout data as the docs allude to. A few initial comments here:
|
|
👍 You've got that exactly right @jamesandersen, saved instruments are already possible to surface back through UCP and that is intentional: same pattern for fulfillment, buyer, really all base entities of the protocol. And you bring up a secondary point about specifically how we consider simplifying saved entities in the UCP spec when the platform really does not require knowing how to handle the spec of the instrument to use it (its already got its credential). I don't think some saved instrument payment handler is the right direction: I think theres actually a greater, and simpler, abstraction that UCP is missing to denote any node in the tree as "tokenized" (a lot like our tokenized credential) and drop-in an id + description of what it is when the platform doesn't need to know its full spec context. @rosew, our lead on payments platform at Shopify, was actually working on an internal proposal of this problem as it matters a lot for us thinking about PII shared for these kinds of stored entities. Lets see if we can accelerate that! In the meantime, I do think this PR highlights that in general UCP does not document the mechanic of surfacing business-owned values rather than simply what the platform supplies. It could be worth repurposing this first PR to tackle that gap! |
7854804 to
dea0d34
Compare
|
@jamesandersen @raginpirate one protocol improvement surfaced while looking at the saved payment instrument example: Today
It may be worth adding an explicit enum in a future protocol revision, for example: "credential_state": "reference"with values like: ["reference", "required", "present"]That would make the saved instrument case clear without relying on opaque Please do let me know thoughts on the same ? |
Yep, trying to infer saved state from an id alone would certainly be a bad idea. Something explicit like a
...however, if I'm tracking @raginpirate accurately, I think he's prompting something that would be a bit more generic and thus applicable not only to credentials but also to other checkout PII that the business could surface to the platform in an identity-linked session
I look forward to this! |
|
Thanks for incorporating some of the feedback @karangoel16, and +1 to the direction @raginpirate and @jamesandersen steered toward: surfacing business-owned user state through existing entities rather than a new extension. Saved instruments, buyer profile, membership id etc are all valid returns when the session is identity-linked, so getting this documented is worthwhile. One thing worth settling before we add a new page: is the mechanic actually undocumented, or is it under-anchored? the "business returns its own user state on a response" pattern is arguably already expressed in three places:
On the auth framing: Standalone page v/s clarifying documentation in existing pages The concern with a standalone concept page is drift: it restates the auth model in looser language than identity-linking's scopes, and it becomes a second source of truth that can diverge from the annotations that actually govern behavior. (Related to your open question 1: the nav currently slots it under Checkout among the extensions, next to Loyalty and Split Payments, which reads as "this is an extension" when it is a cross-cutting clarification.) Would it make sense to land the same intent as two smaller edits anchored to the existing sources of truth, and drop the standalone page? 1. In ### Business-Populated Response Values
When a request is user-authenticated, a business MAY populate response fields it
owns for that user from its own stored state, rather than only echoing values the
platform supplied. Two kinds of fields support this:
* Fields annotated `ucp_request: "omit"` are business-populated by definition:
the platform does not send them and the business returns them (for example
`loyalty`, `totals`, `messages`).
* Fields the platform MAY also supply (annotated `optional`, or unannotated) MAY
additionally be populated by the business from stored user state on the
response (for example `buyer` and `payment.instruments[]`).
Identity linking is what makes these values user-specific rather than generic.
Subject to the scopes that gate the operation, a business MAY return the user's
loyalty membership in `loyalty` (see [Loyalty](loyalty.md)), saved payment
instruments in `payment.instruments[]` (see [Checkout](checkout.md)), or buyer
profile data in `buyer`.
Businesses MUST only return values that belong to the authenticated user and are
appropriate for the current transaction. Platforms MUST treat returned
business-owned identifiers as opaque and business-scoped, and MUST NOT infer raw
credentials from a display value. Businesses SHOULD omit this state when the
request lacks the required user authorization.2. In Businesses may also populate `payment.instruments[]` on responses with
display-safe payment instruments from their own user state, such as payment
methods the authenticated buyer has saved with the business. Such an instrument
carries no raw credential: it is a display-safe, business-scoped reference (an
opaque `id` plus `display` fields) that the business resolves server-side when
the buyer selects it. The
[create checkout REST example](checkout-rest.md#create-checkout) already returns
this shape for a Shop Pay instrument. Returning user-specific saved state
requires a user-authenticated request; see
[Identity Linking](identity-linking.md) for the access levels and scopes that
gate it.
The `display` fields let the platform present a saved instrument and let the
buyer recognize it. Whether the instrument can be charged as-is or still
requires credential collection is not distinguished by the base schema today, so
platforms should not infer readiness from the absence of a `credential` or from
`id` naming.Together these two edits deliver the clarification you set out to add (a business may surface its own user state, including saved instruments and membership, on identity-linked responses) while keeping the auth framing and the payment example anchored to the specs that already own them. On the follow-ups, strong agree they are separate from this docs change and I like that you have them listed as out of scope. Worth flagging that the saved-instrument example motivates one of them directly: display metadata already covers presentation and buyer recognition, but nothing lets the platform deterministically tell a ready-to-resolve saved reference from an instrument that still needs credential collection (credential absence is ambiguous between the two, and Net: the use cases are clearly valid and the clarification is worth shipping. The main question is whether we anchor it to |
|
Thanks @amithanda ! I think extending/clarifying the existing documentation a bit here as discussed would be a quick win for this PR discussion. @karangoel16 I'd second Amit's proposal here. There may be a minor tweak or two in addition to the docs. For example: payment_instrument.json describes the The further enhancements that @raginpirate and @rosew alluded too can improve protocol level clarity of saved (tokenized) nodes. However, simply getting these clarifications in place unlocks/clarifies significant value for both businesses and platforms seeking deliver low friction checkout for identity-linked users. |
| metadata under `display`. | ||
|
|
||
| The response should not include raw payment credentials unless the active | ||
| payment handler explicitly requires credential material on that surface. For |
There was a problem hiding this comment.
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.
+1. @karangoel16 we should not be condoning transmission of unencrypted credentials at this layer.
|
@karangoel16 to make sure these clarifications land in the docs ahead of the next UCP release (short runway on that), I've put up #585 as a draft — a docs-only take that applies the direction we converged on in this thread: no new extension and no standalone page, just anchoring the "a business may populate response fields from its own stored user state, not only echo what the platform supplied" pattern where the auth model already lives. It folds in the specific edits discussed here:
This is very much building on your PR and the discussion here — happy to co-author or land it whatever way is easiest for you. The tokenized-node abstraction (@raginpirate / @rosew) and the ready-vs-needs-credential distinction (your |
Motivation
Review feedback clarified that saved instruments are already possible to surface through existing UCP response entities, especially
payment.instruments[]. The gap is that UCP does not clearly document the mechanic of businesses returning business-owned user state, rather than only echoing values supplied by the platform.This PR now repurposes the original saved-instruments proposal into a documentation clarification:
payment.instruments[]dev.ucp.shopping.saved_instrumentsextension is introducedProposal
Add a new docs page,
Business-Owned Response Values, explaining that UCP response objects may include values supplied by the business from its own user state when the request is user-authenticated through Identity Linking.The page includes:
payment.instruments[]Key design choices
payment.instruments[], not a newpayment.saved_instruments[]field.What this PR ships
docs/specification/business-owned-response-values.md— new docs page for business-owned response valuesdocs/specification/checkout.md— payment section links to the new guidancemkdocs.yml— nav/search entry for the new pagedocs/specification/saved-instruments.mdsource/schemas/shopping/saved_instruments.jsonSample shape
Business returns a saved payment method through existing
payment.instruments[]:{ "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" } } ] } }The
idis an opaque business-owned reference for the authenticated user. The platform renders/selects it; the business verifies ownership and resolves it server-side.Follow-ups intentionally not in this PR
Category (Required)
Checklist
git diff --check; I did not run the full docs build locally.Discussion welcome on