diff --git a/docs/specification/payment/extensions/ap2-mandates.md b/docs/specification/payment/extensions/ap2-mandates.md index 1ba806d41..600f3395c 100644 --- a/docs/specification/payment/extensions/ap2-mandates.md +++ b/docs/specification/payment/extensions/ap2-mandates.md @@ -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 diff --git a/docs/specification/payment/extensions/split-payments.md b/docs/specification/payment/extensions/split-payments.md index 542bf32cb..b70fae73c 100644 --- a/docs/specification/payment/extensions/split-payments.md +++ b/docs/specification/payment/extensions/split-payments.md @@ -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 @@ -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 } ] @@ -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 } ] @@ -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 } ] @@ -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" } ] }, diff --git a/docs/specification/payment/extensions/terms.md b/docs/specification/payment/extensions/terms.md index 6449b825a..9c84f4caa 100644 --- a/docs/specification/payment/extensions/terms.md +++ b/docs/specification/payment/extensions/terms.md @@ -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", diff --git a/docs/specification/payment/guide.md b/docs/specification/payment/guide.md index 111ebcc10..305e69ff6 100644 --- a/docs/specification/payment/guide.md +++ b/docs/specification/payment/guide.md @@ -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:** diff --git a/docs/specification/reference.md b/docs/specification/reference.md index 6b1fb7147..9c0e4d58d 100644 --- a/docs/specification/reference.md +++ b/docs/specification/reference.md @@ -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') }} @@ -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') }} diff --git a/docs/specification/shopping/checkout/index.md b/docs/specification/shopping/checkout/index.md index 0aba1d4dd..a056ea4fd 100644 --- a/docs/specification/shopping/checkout/index.md +++ b/docs/specification/shopping/checkout/index.md @@ -1262,9 +1262,11 @@ this response root (e.g., `$.line_items[0]`). See {{ schema_fields('types/message_warning', 'shopping/checkout') }} -### Payment + -{{ schema_fields('payment', 'shopping/checkout') }} +### Checkout Payment + +{{ extension_schema_fields('checkout.json#/$defs/checkout_payment', 'shopping/checkout') }} #### Selected Payment Instrument @@ -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 } + + +### 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: + + +```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 diff --git a/docs/specification/shopping/order/index.md b/docs/specification/shopping/order/index.md index 76edff575..8e0b53897 100644 --- a/docs/specification/shopping/order/index.md +++ b/docs/specification/shopping/order/index.md @@ -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: @@ -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) @@ -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 @@ -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. @@ -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", @@ -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", @@ -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", @@ -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", diff --git a/docs/specification/shopping/order/mcp.md b/docs/specification/shopping/order/mcp.md index ffd27f898..0ea706a6c 100644 --- a/docs/specification/shopping/order/mcp.md +++ b/docs/specification/shopping/order/mcp.md @@ -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", diff --git a/docs/specification/shopping/order/rest.md b/docs/specification/shopping/order/rest.md index fbd7e6120..1a87e79d8 100644 --- a/docs/specification/shopping/order/rest.md +++ b/docs/specification/shopping/order/rest.md @@ -123,6 +123,7 @@ Returns the 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", diff --git a/scripts/scaffolds/shopping_order_response.json b/scripts/scaffolds/shopping_order_response.json index 4f631403d..3f90227f8 100644 --- a/scripts/scaffolds/shopping_order_response.json +++ b/scripts/scaffolds/shopping_order_response.json @@ -23,5 +23,8 @@ "totals": [ { "type": "subtotal", "amount": 1000 }, { "type": "total", "amount": 1000 } - ] + ], + "payment": { + "maximum_amount": 1000 + } } diff --git a/source/schemas/common/payment_split_payments.json b/source/schemas/common/payment_split_payments.json index adcb77cef..5bc7044ff 100644 --- a/source/schemas/common/payment_split_payments.json +++ b/source/schemas/common/payment_split_payments.json @@ -12,7 +12,7 @@ "title": "Payment Instrument (Split Payments)", "description": "Payment instrument extended with an optional per-instrument amount for split payments.", "allOf": [ - { "$ref": "types/payment_instrument.json" }, + { "$ref": "types/payment_instrument.json#/$defs/selected_payment_instrument" }, { "type": "object", "properties": { diff --git a/source/schemas/common/payment_terms.json b/source/schemas/common/payment_terms.json index f5e246d78..0c0b09861 100644 --- a/source/schemas/common/payment_terms.json +++ b/source/schemas/common/payment_terms.json @@ -74,8 +74,7 @@ "properties": { "payment": { "$ref": "#/$defs/order_payment", - "description": "Payment details for the Order, including the accepted payment term.", - "ucp_response": "optional" + "description": "Payment details for the Order, including the accepted payment term." } } } diff --git a/source/schemas/common/types/card_payment_instrument.json b/source/schemas/common/types/card_payment_instrument.json index cf35f228b..4cc519106 100644 --- a/source/schemas/common/types/card_payment_instrument.json +++ b/source/schemas/common/types/card_payment_instrument.json @@ -23,7 +23,7 @@ }, "allOf": [ { - "$ref": "payment_instrument.json" + "$ref": "payment_instrument.json#/$defs/base_payment_instrument" }, { "type": "object", diff --git a/source/schemas/common/types/payment_instrument.json b/source/schemas/common/types/payment_instrument.json index 830188577..cf0998bac 100644 --- a/source/schemas/common/types/payment_instrument.json +++ b/source/schemas/common/types/payment_instrument.json @@ -2,48 +2,53 @@ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://ucp.dev/schemas/common/types/payment_instrument.json", "title": "Payment Instrument", - "description": "The base definition for any payment instrument. It links the instrument to a specific payment handler.", - "type": "object", - "required": [ - "id", - "handler_id", - "type" - ], - "properties": { - "id": { - "type": "string", - "description": "A unique identifier for this instrument instance. Typically assigned by the platform for instruments it collects. For a business-owned saved instrument returned on an identity-linked response, this identifier is assigned by the business; the platform MUST treat it as an opaque, business-scoped reference, and the business resolves it server-side when the buyer selects it." - }, - "handler_id": { - "type": "string", - "description": "The unique identifier for the handler instance that produced this instrument. This corresponds to the 'id' field in the Payment Handler definition." - }, - "type": { - "type": "string", - "description": "The broad category of the instrument (e.g., 'card', 'tokenized_card'). Specific schemas will constrain this to a constant value." - }, - "billing_address": { - "$ref": "postal_address.json", - "description": "The billing address associated with this payment method." - }, - "credential": { - "$ref": "payment_credential.json" - }, - "display": { - "type": "object", - "description": "Display information for this payment instrument. Each payment instrument schema defines its specific display properties, as outlined by the payment handler." - } - }, - "additionalProperties": true, + "description": "Fields shared by payment instruments in Checkout and Order. Payment handlers add instrument-specific credential and display fields, while each capability adds the fields it needs for routing, selection, or reporting.", + "$ref": "payment_instrument.json#/$defs/base_payment_instrument", "$defs": { + "base_payment_instrument": { + "type": "object", + "required": [ + "id", + "type" + ], + "properties": { + "id": { + "type": "string", + "description": "Unique identifier for this instrument, assigned by the participant that introduces it. Identifiers assigned by another participant MUST be treated as opaque." + }, + "type": { + "type": "string", + "description": "The broad category of the instrument (e.g., 'card', 'tokenized_card'). Specific schemas will constrain this to a constant value." + }, + "billing_address": { + "$ref": "postal_address.json", + "description": "The billing address associated with this payment method." + }, + "credential": { + "$ref": "payment_credential.json", + "description": "Credential used to execute payment. This field is request-only and MUST NOT be returned in responses.", + "ucp_response": "omit" + }, + "display": { + "type": "object", + "description": "Display information for this payment instrument. Each payment instrument schema defines its specific display properties, as outlined by the payment handler." + } + }, + "additionalProperties": true + }, "selected_payment_instrument": { "title": "Selected Payment Instrument", - "description": "A payment instrument with selection state.", + "description": "A Checkout payment instrument with handler routing and selection state.", "allOf": [ - { "$ref": "#" }, + { "$ref": "payment_instrument.json#/$defs/base_payment_instrument" }, { "type": "object", + "required": ["handler_id"], "properties": { + "handler_id": { + "type": "string", + "description": "The unique identifier for the handler instance that produced this instrument. This corresponds to the `id` field in the Payment Handler definition." + }, "selected": { "type": "boolean", "description": "Whether this instrument is selected by the user." @@ -51,6 +56,34 @@ } } ] + }, + "order_payment_instrument": { + "title": "Order Payment Instrument", + "description": "Payment instrument as returned on an Order, which may include current authorization and total captured amounts.", + "allOf": [ + { "$ref": "payment_instrument.json#/$defs/base_payment_instrument" }, + { + "type": "object", + "required": ["display"], + "properties": { + "authorized_amount": { + "$ref": "amount.json", + "description": "Amount currently authorized for this instrument, in the Order currency's minor units. Released and expired authorizations are excluded. Remaining capture capacity is a separate concept." + }, + "captured_amount": { + "$ref": "amount.json", + "description": "Total amount successfully captured or collected for this instrument, in the Order currency's minor units, before refunds, credits, disputes, or capture reversals. This measures capture rather than settlement or payout." + } + }, + "not": { + "anyOf": [ + { "required": ["credential"] }, + { "required": ["handler_id"] }, + { "required": ["selected"] } + ] + } + } + ] } } } diff --git a/source/schemas/shopping/checkout.json b/source/schemas/shopping/checkout.json index 983e42972..479109c36 100644 --- a/source/schemas/shopping/checkout.json +++ b/source/schemas/shopping/checkout.json @@ -4,6 +4,65 @@ "name": "dev.ucp.shopping.checkout", "title": "Checkout", "description": "Base checkout schema. Extensions compose onto this using allOf.", + "$defs": { + "checkout_total": { + "title": "Checkout Total", + "description": "Checkout pricing entry. Only the aggregate total may state whether its amount is final.", + "allOf": [ + { "$ref": "../common/types/total.json" }, + { + "type": "object", + "properties": { + "amount_finality": { + "type": "string", + "enum": ["final", "provisional"], + "description": "Whether the aggregate total may change after the Buyer completes the Checkout. Omission means `final`. When `provisional`, `payment.maximum_amount` is the most the Business may authorize or collect under the Buyer's approval of this Checkout.", + "ucp_request": "omit" + } + }, + "if": { + "properties": { + "type": { "const": "total" } + }, + "required": ["type"] + }, + "else": { + "not": { + "required": ["amount_finality"] + } + } + } + ] + }, + "checkout_totals": { + "title": "Checkout Totals", + "description": "Checkout pricing breakdown in which only the aggregate total may state whether its amount is final.", + "allOf": [ + { "$ref": "../common/types/totals.json" }, + { + "type": "array", + "items": { "$ref": "#/$defs/checkout_total" } + } + ] + }, + "checkout_payment": { + "title": "Checkout Payment", + "description": "Checkout payment details, including the maximum amount approved by the Buyer when the total is provisional.", + "allOf": [ + { "$ref": "../common/types/payment.json" }, + { + "type": "object", + "properties": { + "maximum_amount": { + "$ref": "../common/types/amount.json", + "description": "Maximum amount, across all payment instruments and in the Checkout currency's minor units, that the Business may authorize or collect under the Buyer's approval of this Checkout. Required for a provisional total and omitted for a final total, where the aggregate total is the approved maximum.", + "ucp_request": "omit" + } + } + } + ] + } + }, "type": "object", "required": [ "ucp", @@ -81,7 +140,7 @@ "ucp_request": "omit" }, "totals": { - "$ref": "../common/types/totals.json", + "$ref": "#/$defs/checkout_totals", "description": "Different cart totals.", "ucp_request": "omit" }, @@ -127,7 +186,7 @@ "ucp_request": "omit" }, "payment": { - "$ref": "../common/types/payment.json", + "$ref": "#/$defs/checkout_payment", "ucp_request": { "create": "optional", "update": "optional", @@ -139,5 +198,38 @@ "description": "Details about an order created for this checkout session.", "ucp_request": "omit" } - } + }, + "allOf": [ + { + "if": { + "properties": { + "totals": { + "contains": { + "properties": { + "type": { "const": "total" }, + "amount_finality": { "const": "provisional" } + }, + "required": ["type", "amount_finality"] + } + } + }, + "required": ["totals"] + }, + "then": { + "properties": { + "payment": { + "required": ["maximum_amount"] + } + }, + "required": ["payment"] + }, + "else": { + "properties": { + "payment": { + "not": { "required": ["maximum_amount"] } + } + } + } + } + ] } diff --git a/source/schemas/shopping/order.json b/source/schemas/shopping/order.json index f5ade18a9..7e79da6ee 100644 --- a/source/schemas/shopping/order.json +++ b/source/schemas/shopping/order.json @@ -19,6 +19,25 @@ "description": "URL where merchant sends order lifecycle events (webhooks)." } } + }, + "order_payment": { + "title": "Order Payment", + "description": "Payment details returned on the Order, including the maximum amount the Buyer approved at Checkout.", + "type": "object", + "required": ["maximum_amount"], + "properties": { + "instruments": { + "type": "array", + "items": { + "$ref": "../common/types/payment_instrument.json#/$defs/order_payment_instrument" + }, + "description": "Payment instruments used on the Order." + }, + "maximum_amount": { + "$ref": "../common/types/amount.json", + "description": "Maximum amount the Buyer approved when completing the Checkout, in the Order currency's minor units. For a provisional Checkout total, this 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." + } + } } }, "type": "object", @@ -29,6 +48,7 @@ "permalink_url", "line_items", "fulfillment", + "payment", "currency", "totals" ], @@ -80,6 +100,11 @@ }, "description": "Fulfillment data: buyer expectations and what actually happened." }, + "payment": { + "$ref": "#/$defs/order_payment", + "description": "Payment details returned on the Order. The instruments may change after purchase, but `maximum_amount` continues to record what the Buyer approved at Checkout.", + "ucp_request": "omit" + }, "adjustments": { "type": "array", "items": {