Context
The UCP total.json schema already defines "fee" as a valid type enum value in the totals[] array, but the specification does not document:
- Whether multiple type: "fee" entries are permitted in the same totals[] array
- How platforms should render multiple fees (collapsed vs. itemized)
- How fees interact with the total calculation formula
- Whether fees can appear at different levels (checkout, line item, fulfillment option)
Businesses implementing UCP have no guidance on how to express itemized fees (e.g., service fees, handling fees, recycling fees, convenience fees).
Proposal
We propose documenting the following semantics for type: "fee" entries.
- Multiple fee entries are allowed
A business MAY include multiple type: "fee" entries in the same totals[] array. Each entry represents a distinct, itemized charge:
{
totals: [
{type: subtotal, display_text: Subtotal, amount: 5000},
{type: fee, display_text: Service Fee, amount: 200},
{type: fee, display_text: Handling Fee, amount: 150},
{type: fee, display_text: Gift Wrap Fee, amount: 350},
{type: fulfillment, display_text: Standard Shipping, amount: 500},
{type: tax, display_text: Tax, amount: 520},
{type: total, display_text: Total, amount: 6720}
]
}
-
Total calculation formula includes all fees
total = subtotal - discount + fulfillment + tax + fee₁ + fee₂ + ... + feeₙ
-
Fees at multiple levels
Fees MAY appear at any level where totals[] exists:
- Checkout-level:
$.totals[]
- Line-item-level:
$.line_items[].totals[]
- Fulfillment-option-level:
$.fulfillment_options[].totals[]
Key Design Question: Should fees be a formal Extension like Discounts?
Option A: Document within existing totals[]
Fees remain a type value in the existing Total schema. No new schema files, no capability registration. This is the minimal approach — it clarifies what's already structurally possible.
Pros:
- No new schemas
- Fees are conceptually simpler than discounts (no codes, no stacking, no allocations)
- Already works today — just needs documentation
Cons:
- No structured metadata (no way to express fee reason, legal / regulatory disclosures, tax applicability, whether fee is waivable, etc.)
- No capability discovery — platforms can't know in advance if a business will include fees
- No allocation breakdown — can't express "this fee is because of line item X"
- Limited to display_text + amount — not machine-readable
Option B: Create a Fee Extension
Create fee.json with structured fee types, a fees object on checkout, and capability registration via dev.ucp.shopping.fee.
This could enable:
- Fee discovery: Business advertises dev.ucp.shopping.fee so platforms know to expect and render fees
- Structured fee objects: applied_fee with title, type (service, handling, recycling, convenience, etc.), taxable boolean, waivable boolean
- Fee allocations: Link fees to specific line items or fulfillment options
- Fee negotiation: Platform could potentially request fee waivers or adjustments
Pros:
- Machine-readable fee metadata
- Platform can adapt UI based on fee capability discovery
- Future-proof for fee negotiation, fee itemization in receipts, etc.
Cons:
- May be over-engineering for a feature that's simpler than discounts
- Requires new schema file, spec page, capability registration
Open Questions
Q1: Uniqueness constraints
Are there any uniqueness constraints on type within a totals[] array? Should we formally state that subtotal, discount, fulfillment, tax, and total MUST appear at most once, while fee MAY appear multiple times?
Q2: Is display_text required for fees?
The schema marks display_text as optional. For fees, should it be effectively REQUIRED (SHOULD vs. MUST) since a fee without a label is not useful to the buyer?
Q3: Fee ordering
Should the spec prescribe a canonical ordering of type values in totals[]? (e.g., subtotal → discount → fee → fulfillment → tax → total)
Q4: Fee aggregation for line-item fees
When a fee appears in both line_items[].totals[] AND checkout-level totals[], should the checkout-level fee be the sum of line-item fees of the same kind? Or are they independent?
Q5: Impact on other schemas
total.json is shared across checkout.json, cart.json, order.json, line_item.json, order_line_item.json, and fulfillment_option.json. Do the same fee rules apply uniformly across all contexts?
Context
The UCP total.json schema already defines "fee" as a valid type enum value in the totals[] array, but the specification does not document:
Businesses implementing UCP have no guidance on how to express itemized fees (e.g., service fees, handling fees, recycling fees, convenience fees).
Proposal
We propose documenting the following semantics for type: "fee" entries.
A business MAY include multiple type: "fee" entries in the same totals[] array. Each entry represents a distinct, itemized charge:
Total calculation formula includes all fees
total = subtotal - discount + fulfillment + tax + fee₁ + fee₂ + ... + feeₙFees at multiple levels
Fees MAY appear at any level where totals[] exists:
$.totals[]$.line_items[].totals[]$.fulfillment_options[].totals[]Key Design Question: Should fees be a formal Extension like Discounts?
Option A: Document within existing totals[]
Fees remain a type value in the existing Total schema. No new schema files, no capability registration. This is the minimal approach — it clarifies what's already structurally possible.
Pros:
Cons:
Option B: Create a Fee Extension
Create fee.json with structured fee types, a fees object on checkout, and capability registration via dev.ucp.shopping.fee.
This could enable:
Pros:
Cons:
Open Questions
Q1: Uniqueness constraints
Are there any uniqueness constraints on type within a totals[] array? Should we formally state that subtotal, discount, fulfillment, tax, and total MUST appear at most once, while fee MAY appear multiple times?
Q2: Is display_text required for fees?
The schema marks display_text as optional. For fees, should it be effectively REQUIRED (SHOULD vs. MUST) since a fee without a label is not useful to the buyer?
Q3: Fee ordering
Should the spec prescribe a canonical ordering of type values in totals[]? (e.g., subtotal → discount → fee → fulfillment → tax → total)
Q4: Fee aggregation for line-item fees
When a fee appears in both line_items[].totals[] AND checkout-level totals[], should the checkout-level fee be the sum of line-item fees of the same kind? Or are they independent?
Q5: Impact on other schemas
total.json is shared across checkout.json, cart.json, order.json, line_item.json, order_line_item.json, and fulfillment_option.json. Do the same fee rules apply uniformly across all contexts?