Skip to content

Commit d03ce30

Browse files
maximenajimDayton
andcommitted
feat(shopping): address PR #245 review feedback from ACSchil
- Soften mismatch language; add continue_url as fallback option (#4) - Strengthen normative admonition with MUST/MUST NOT language (#5) - Scope fee IDs to session; clarify in schema description and docs (#7) - Mark description as plain text; reference Disclosures #222 (#8/#12) - Add messages[] guidance with fee_waived example for waived fees (#10) - Add readonly_field_not_allowed error code; reference in fee spec (#11) Co-authored-by: Dayton <31824+SVDEA001@users.noreply.git.target.com>
1 parent c491a72 commit d03ce30

3 files changed

Lines changed: 40 additions & 10 deletions

File tree

docs/specification/fee.md

Lines changed: 36 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -107,14 +107,18 @@ fees into the order total calculation.
107107
**Invariant:** `totals[type=fee].amount` equals `sum(fees.applied[].amount)`.
108108
Businesses MUST ensure this invariant holds. If a platform detects a mismatch
109109
between the aggregated fee total and the sum of itemized fees, the platform
110-
SHOULD treat the response as invalid and MUST NOT complete the checkout without
111-
surfacing the discrepancy to the user.
110+
SHOULD treat the response as potentially invalid and SHOULD surface the
111+
discrepancy to the user. Platforms MAY use `continue_url` to hand off to the
112+
business UI for resolution rather than attempting to complete the checkout with
113+
inconsistent fee data.
112114

113115
!!! note "When the Fee Extension is absent"
114-
If a business does not advertise `dev.ucp.shopping.fee`, a
115-
`totals[type=fee]` entry MAY still appear (e.g., as an opaque line item).
116-
Platforms SHOULD render it using `display_text` but cannot assume itemized
117-
fee data is available.
116+
When the Fee Extension is present, there MUST be at most one `totals[]`
117+
entry with `type: "fee"`, and its `amount` MUST equal
118+
`sum(fees.applied[].amount)`. When the Fee Extension is not advertised,
119+
the interpretation of any `totals[type=fee]` entry is business-defined —
120+
platforms SHOULD render it using `display_text` but MUST NOT assume
121+
itemized fee data is available.
118122

119123
### Fee Types
120124

@@ -144,6 +148,11 @@ individual fees across checkout updates (e.g., to track which fees changed
144148
between updates or to display consistent UI elements). Therefore, `id` is
145149
required on every fee.
146150

151+
Fee IDs are scoped to a single checkout or cart session. The same fee retains
152+
its `id` across requests within a session (create → update → complete), but the
153+
`id` is not guaranteed to be consistent across separate sessions. Businesses
154+
control ID generation.
155+
147156
## Multiple Fees
148157

149158
A checkout or cart may include multiple fees. The following invariants hold:
@@ -161,6 +170,21 @@ A checkout or cart may include multiple fees. The following invariants hold:
161170
fee marked `waivable: true` is actually waived, the business omits it rather
162171
than including a zero-amount entry.
163172

173+
4. **Communicating waived fees:** To inform the platform and user that a fee was
174+
waived, businesses MAY include a `messages[]` entry with `type: "info"`:
175+
176+
```json
177+
{
178+
"messages": [
179+
{
180+
"type": "info",
181+
"code": "fee_waived",
182+
"content": "Service Fee waived for loyalty members."
183+
}
184+
]
185+
}
186+
```
187+
164188
## Operations
165189

166190
Fees are fully read-only. The `fees` object uses `ucp_request: "omit"` for all
@@ -171,6 +195,8 @@ Business receivers MUST reject any `fees` fields provided by platforms in
171195
requests. Because fees directly affect money movement, silently ignoring
172196
client-supplied fee data is insufficient — an explicit error response prevents
173197
parameter-smuggling attacks where a platform attempts to influence fee amounts.
198+
Businesses SHOULD use the `readonly_field_not_allowed` error code when rejecting
199+
such requests.
174200

175201
**Checkout operations:**
176202

@@ -217,7 +243,10 @@ Order Summary
217243

218244
When a fee includes a `description`, platforms MAY display it as supplementary
219245
text (e.g., a tooltip or fine print) to help users understand why the fee is
220-
charged.
246+
charged. The `description` field is plain text — for richer content such as
247+
regulatory disclosures, images, or formatted copy, businesses should use the
248+
Disclosures capability (see [#222](https://github.com/Universal-Commerce-Protocol/ucp/issues/222))
249+
when available.
221250

222251
If a fee has `waivable: true`, platforms MAY indicate this to the user (e.g.,
223252
"This fee is waived for members").

source/schemas/shopping/types/error_code.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
"out_of_stock",
99
"item_unavailable",
1010
"address_undeliverable",
11-
"payment_failed"
11+
"payment_failed",
12+
"readonly_field_not_allowed"
1213
]
1314
}

source/schemas/shopping/types/fee.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,15 @@
1414
"properties": {
1515
"id": {
1616
"type": "string",
17-
"description": "Unique fee identifier. Unlike applied discounts, fees always require an id to enable stable referencing across updates."
17+
"description": "Unique fee identifier within the scope of a single checkout or cart session. Stable across requests within a session (create, update, complete) to enable platforms to track fee changes. Not guaranteed to be consistent across separate sessions."
1818
},
1919
"title": {
2020
"type": "string",
2121
"description": "Human-readable fee name (e.g., 'Service Fee', 'Recycling Fee')."
2222
},
2323
"description": {
2424
"type": "string",
25-
"description": "Optional explanation of why the fee is charged."
25+
"description": "Optional plain-text explanation of why the fee is charged. For richer content (e.g., regulatory disclosures), see the Disclosures capability."
2626
},
2727
"amount": {
2828
"$ref": "amount.json",

0 commit comments

Comments
 (0)