Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions docs/specification/payment/extensions/ap2-mandates.md
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,13 @@ The checkout mandate **MUST** contain the full checkout response including the
`ap2.merchant_authorization` field. This creates a nested cryptographic binding
where the platform's signature covers the business's signature.

When the Checkout aggregate total is provisional, that signed response includes
both `totals[].amount_finality` and `payment.maximum_amount`. The Checkout
mandate therefore records the amount shown to the Buyer and the maximum they
approved. The Business MUST NOT authorize or collect more than that maximum
under the mandate. A higher amount requires separate Buyer approval and cannot
rely on the original mandate.

**Specification Boundary:** This extension defines *where* mandates are placed
in UCP requests and responses. The mandate credential structure (claims,
selective disclosure, key binding) is defined by the
Expand Down
17 changes: 6 additions & 11 deletions docs/specification/payment/extensions/split-payments.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@ charged for each successfully-processed instrument in the same `amount`
field. Response `amount` is informational only; see "Response: Actual
Charges" below.

Split Payments `amount` records the instrument's contribution to the Checkout.
Order instruments use `authorized_amount` and `captured_amount` to report
authorization and capture separately.

All `amount` values are expressed in `checkout.currency` minor units
(ISO 4217). Handlers using foreign-currency-denominated instruments
(e.g., a CAD gift card in a USD checkout) or non-currency instruments
Expand Down Expand Up @@ -284,14 +288,12 @@ Neither instrument includes `amount` — the business determines both.
"id": "pi_gc_1",
"handler_id": "example_handler_1",
"type": "gift_card",
"credential": { "type": "gift_card", "token": "gc_abc123" },
"amount": 1000
},
{
"id": "pi_card_1",
"handler_id": "example_handler_1",
"type": "card",
"credential": { "type": "card", "token": "tok_visa_xxxx" },
"amount": 4000
}
]
Expand Down Expand Up @@ -346,14 +348,12 @@ the rest.
"id": "pi_lp_1",
"handler_id": "example_handler_1",
"type": "loyalty",
"credential": { "type": "loyalty", "token": "lp_abc123" },
"amount": 500
},
{
"id": "pi_card_1",
"handler_id": "example_handler_1",
"type": "card",
"credential": { "type": "card", "token": "tok_visa_xxxx" },
"amount": 4500
}
]
Expand Down Expand Up @@ -409,21 +409,18 @@ credit card covers the remaining $45.
"id": "pi_gc_1",
"handler_id": "handler_gc",
"type": "gift_card",
"credential": { "type": "gift_card", "token": "gc_abc123" },
"amount": 2500
},
{
"id": "pi_gc_2",
"handler_id": "handler_gc",
"type": "gift_card",
"credential": { "type": "gift_card", "token": "gc_def456" },
"amount": 0
},
{
"id": "pi_card_1",
"handler_id": "handler_card",
"type": "card",
"credential": { "type": "card", "token": "tok_visa_xxxx" },
"amount": 7500
}
]
Expand Down Expand Up @@ -454,14 +451,12 @@ per the atomic invariant):**
{
"id": "pi_gc_1",
"handler_id": "example_handler_1",
"type": "gift_card",
"credential": { "type": "gift_card", "token": "gc_abc123" }
"type": "gift_card"
},
{
"id": "pi_card_1",
"handler_id": "example_handler_1",
"type": "card",
"credential": { "type": "card", "token": "tok_visa_xxxx" }
"type": "card"
}
]
},
Expand Down
1 change: 1 addition & 0 deletions docs/specification/payment/extensions/terms.md
Original file line number Diff line number Diff line change
Expand Up @@ -535,6 +535,7 @@ disclosure moves with the term it governs:
{ "type": "total", "amount": 120000 }
],
"payment": {
"maximum_amount": 120000,
"accepted_term": {
"id": "pt_deposit_balance",
"title": "First night now, balance at check-in",
Expand Down
4 changes: 3 additions & 1 deletion docs/specification/payment/guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -574,13 +574,15 @@ Each variant has its own config schema tailored to its context:

| Schema | Description |
| :----------------------------------------------------------------------------------------- | :--------------------------------------------------------------- |
| [`payment_instrument.json`](site:schemas/common/types/payment_instrument.json) | Base: id, handler_id, type, billing_address, credential, display |
| [`payment_instrument.json`](site:schemas/common/types/payment_instrument.json) | Base: id, type, billing_address, credential, display |
| [`card_payment_instrument.json`](site:schemas/common/types/card_payment_instrument.json) | Extends base with display: brand, last_digits, expiry, card art |

UCP provides base schemas for universal payment instruments like `card`. Spec
authors **MAY** extend any of the base instruments to add handler-specific
display data or customize the credential reference. Handlers **MAY** define
multiple instrument types for different payment flows.
Checkout adds `handler_id` and `selected` through
`selected_payment_instrument`.

**Constraint Targets:**

Expand Down
20 changes: 20 additions & 0 deletions docs/specification/reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,22 @@ within the UCP.

{{ auto_generate_schema_reference('.', 'reference', include_extensions=False) }}

### Checkout Totals

An ordered array of [Checkout Total](#checkout-total) entries.

### Checkout Total

{{ extension_schema_fields('../shopping/checkout.json#/$defs/checkout_total', 'reference') }}

### Checkout Payment

{{ extension_schema_fields('../shopping/checkout.json#/$defs/checkout_payment', 'reference') }}

### Order Payment

{{ extension_schema_fields('../shopping/order.json#/$defs/order_payment', 'reference') }}

## Type Schemas

{{ auto_generate_schema_reference('types', 'reference', include_extensions=False, base_dir='source/schemas/common') }}
Expand All @@ -35,6 +51,10 @@ within the UCP.

{{ extension_schema_fields('types/payment_instrument.json#/$defs/selected_payment_instrument', 'reference') }}

### Order Payment Instrument {: #payment-instrument-order-payment-instrument }

{{ extension_schema_fields('types/payment_instrument.json#/$defs/order_payment_instrument', 'reference') }}

### Pagination Request {: #pagination-request }

{{ extension_schema_fields('types/pagination.json#/$defs/request', 'reference') }}
Expand Down
88 changes: 84 additions & 4 deletions docs/specification/shopping/checkout/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -1262,9 +1262,11 @@ this response root (e.g., `$.line_items[0]`). See

{{ schema_fields('types/message_warning', 'shopping/checkout') }}

### Payment
<span id="payment"></span>

{{ schema_fields('payment', 'shopping/checkout') }}
### Checkout Payment

{{ extension_schema_fields('checkout.json#/$defs/checkout_payment', 'shopping/checkout') }}

#### Selected Payment Instrument

Expand All @@ -1282,9 +1284,87 @@ this response root (e.g., `$.line_items[0]`). See

{{ extension_schema_fields('capability.json#/$defs/response_schema', 'shopping/checkout') }}

### Total {: #totals }
<span id="totals"></span>

### Checkout Totals

`totals` is an ordered array of Checkout Total entries.

#### Checkout Total

{{ extension_schema_fields('checkout.json#/$defs/checkout_total', 'shopping/checkout') }}

#### Final and Provisional Totals

The aggregate Checkout total is the single `totals[]` entry whose `type` is
`total`. It MAY include `amount_finality` as `final` or `provisional`; omission
means `final`. Other total entries MUST NOT include `amount_finality`.

Every Checkout sets the maximum amount the Buyer approves:

* For a final total, the aggregate total is the approved maximum and
`payment.maximum_amount` MUST be omitted.
* For a provisional total, `payment.maximum_amount` is required and MUST be
greater than or equal to the displayed aggregate total.

JSON Schema cannot compare the two values. Implementations MUST ensure that
`payment.maximum_amount` is at least the aggregate total.

Before completing a provisional Checkout, the Platform MUST disclose that the
amount may change up to `payment.maximum_amount` and obtain the Buyer's
approval. Across all instruments, the Business MUST NOT authorize or collect
more than that amount under the approval given at Checkout. Increasing the
payment for a post-purchase Order change requires separate Buyer approval.

`maximum_amount` applies to the Checkout as a whole rather than to each
instrument. It is also separate from the amount a payment provider will still
allow the Business to capture against an authorization. The sum of all
historical authorization and capture records can be larger than
`maximum_amount` because an authorization can be replaced or a reversed capture
can be retried.

Within UCP, the Business is responsible for enforcing the approved maximum. A
payment provider may also enforce a limit passed to it by the Business, but UCP
does not define that downstream exchange. The authorization and capture
summaries on the Order are not enough to verify the limit: one reports current
state and the other reports total activity. When AP2 is used, the Checkout
mandate provides a signed record of the amount the Buyer approved.

The Business MAY change the total and its finality while the Checkout remains
open. Once Complete Checkout is accepted, the approved maximum no longer
changes. The resulting Order MUST record that amount in
`order.payment.maximum_amount`.

{{ schema_fields('types/total_resp', 'shopping/checkout') }}
For example, this Checkout can increase from $54.00 to no more than $60.00
after completion:

<!-- ucp:example schema=shopping/checkout op=read -->
```json
{
"ucp": {
"version": "{{ ucp_version }}",
"status": "success",
"payment_handlers": {}
},
"id": "chk_provisional_total",
"status": "ready_for_complete",
"currency": "USD",
"line_items": [ ... ],
"totals": [
{ "type": "subtotal", "amount": 5000 },
{ "type": "tax", "amount": 400 },
{
"type": "total",
"amount": 5400,
"amount_finality": "provisional"
}
],
"links": [],
"payment": {
"maximum_amount": 6000
}
}
```

#### Rendering Contract

Expand Down
77 changes: 76 additions & 1 deletion docs/specification/shopping/order/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ it will be delivered, and what has happened since order placement.

### Key Concepts

Orders have three main components:
Orders have four main components:

**Line Items** — what was purchased at checkout:

Expand All @@ -39,6 +39,11 @@ Orders have three main components:
* **Expectations** — buyer-facing *promises* about when/how items will arrive
* **Events** (append-only log) — what actually happened (e.g. 👕 was shipped)

**Payment** — what the Buyer approved and what the Business reports:

* Records the maximum amount approved at Checkout
* May include the amount authorized and captured for each instrument

**Adjustments** — post-order events independent of fulfillment:

* Typically money movements (refunds, returns, credits, disputes, cancellations)
Expand Down Expand Up @@ -100,6 +105,41 @@ Expectations can be split, merged, or adjusted post-order. For example:
(common examples: `processing`, `shipped`, `in_transit`, `delivered`,
`failed_attempt`, `canceled`, `undeliverable`, `returned_to_sender`)

### Payment

**Payment** records the payment instruments used for the Order and the maximum
amount the Buyer approved at Checkout. The instruments may change after
purchase, for example when an Order modification adds or replaces a payment
method.

Each instrument uses the shared Payment Instrument in its Order context:

* `display` is required.
* Checkout-only `handler_id` and `selected` fields are absent.
* `credential` is request-only and is omitted from responses.
* `authorized_amount` is the amount currently authorized, in the Order
currency's minor units. Released and expired authorizations are excluded;
remaining capture capacity is a separate concept.
* `captured_amount` is the total amount successfully captured or collected, in
the Order currency's minor units, before refunds, credits, disputes, or
capture reversals. This measures capture rather than settlement or payout.

Either amount may be omitted when it does not apply or is not reported.
Platforms MUST accept `captured_amount` greater than `authorized_amount`; this
can happen when capture above the authorized amount is supported or when an
authorization is later released. Neither field sets a maximum for the
instrument.

Every Order includes `payment.maximum_amount`, which records the maximum amount
the Buyer approved when completing Checkout. For a provisional Checkout total,
it equals `checkout.payment.maximum_amount`; for a final total, it equals the
aggregate Checkout total. It remains unchanged if the Buyer later approves an
Order modification.

Platforms MUST NOT compare `maximum_amount` with the sum of historical
authorization or capture records. Replaced authorizations and retried captures
can cause those sums to exceed the amount approved by the Buyer.

### Attribution

Businesses MAY surface a snapshot of the originating checkout's
Expand Down Expand Up @@ -133,6 +173,14 @@ fulfillment:

{{ schema_fields('order', 'shopping/order') }}

### Order Payment

{{ extension_schema_fields('order.json#/$defs/order_payment', 'shopping/order') }}

### Order Payment Instrument

{{ extension_schema_fields('../common/types/payment_instrument.json#/$defs/order_payment_instrument', 'shopping/order') }}

### Order Line Item

Line items reflect what was purchased at checkout and their current state.
Expand Down Expand Up @@ -277,6 +325,30 @@ Examples: `refund`, `return`, `credit`, `price_adjustment`, `dispute`,
}
]
},
"payment": {
"maximum_amount": 15342,
"instruments": [
{
"id": "pi_gift",
"type": "gift_card",
"captured_amount": 5000,
"display": {
"description": "Gift card",
"last_digits": "9821"
}
},
{
"id": "pi_card",
"type": "card",
"captured_amount": 10342,
"display": {
"brand": "visa",
"last_digits": "4242",
"description": "Visa ending in 4242"
}
}
]
},
"adjustments": [
{
"id": "adj_1",
Expand Down Expand Up @@ -321,6 +393,7 @@ rounded to `119`, and the return credits `79 × 0.25 = 19.75`, rounded to
"checkout_id": "chk_bananas_1",
"permalink_url": "https://business.example.com/orders/bananas1",
"currency": "USD",
"payment": { "maximum_amount": 119 },
"line_items": [
{
"id": "li_bananas",
Expand Down Expand Up @@ -400,6 +473,7 @@ nominal steps `3 × 40 = 120`, settled `114`, delta `6` steps
"checkout_id": "chk_apples_1",
"permalink_url": "https://business.example.com/orders/apples1",
"currency": "USD",
"payment": { "maximum_amount": 240 },
"line_items": [
{
"id": "li_apples",
Expand Down Expand Up @@ -490,6 +564,7 @@ then holds exactly (`190 == 190`) and the line derives `fulfilled`:
"checkout_id": "chk_bananas_2",
"permalink_url": "https://business.example.com/orders/bananas2",
"currency": "USD",
"payment": { "maximum_amount": 158 },
"line_items": [
{
"id": "li_bananas",
Expand Down
1 change: 1 addition & 0 deletions docs/specification/shopping/order/mcp.md
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@ current-state snapshot of an order.
"checkout_id": "checkout_xyz789",
"permalink_url": "https://business.example.com/orders/abc123",
"currency": "USD",
"payment": { "maximum_amount": 4104 },
"line_items": [
{
"id": "li_shoes",
Expand Down
Loading
Loading