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
863 changes: 542 additions & 321 deletions docs/specification/payment-terms.md

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions source/schemas/common/types/warning_code.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
"final_sale",
"prop65",
"fulfillment_changed",
"payment_term_changed",
"age_restricted"
]
}
103 changes: 34 additions & 69 deletions source/schemas/shopping/payment_terms.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,8 @@
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://ucp.dev/schemas/shopping/payment_terms.json",
"name": "dev.ucp.shopping.payment_terms",
"version": "2026-01-23",
"title": "Payment Terms Extension",
"description": "Extends Checkout with flexible payment terms support, enabling deferred payments, installments, and per-line-item payment schedules.",
"description": "Extends Checkout with selectable payment terms, letting a Business offer alternative schedules for when payment for the checkout is due, and projects the accepted term onto the resulting Order.",
"requires": {
"protocol": { "min": "2026-04-08" },
"capabilities": {
Expand All @@ -13,83 +12,54 @@
}
},
"$defs": {
"available_payment_term": {
"$ref": "types/available_payment_term.json"
},
"payment_term": {
"$ref": "types/payment_term.json"
},
"payment_schedule": {
"$ref": "types/payment_schedule.json"
},
"schedule_ref": {
"$ref": "types/entity_ref.json"
},
"available_payment_instrument": {
"title": "Available Payment Instrument (Payment Terms)",
"description": "Extends available_payment_instrument with term reference constraints.",
"allOf": [
{ "$ref": "types/available_payment_instrument.json" },
{
"type": "object",
"properties": {
"constraints": {
"type": "object",
"properties": {
"term_refs": {
"type": "array",
"items": { "$ref": "#/$defs/schedule_ref" },
"description": "Payment terms and schedules this instrument supports. References can target a payment term by `id`, a payment schedule by `schedule_id`, a schedule class by `schedule_type`, or a combination. If omitted, the instrument supports all terms and schedules unless constrained by the payment handler."
}
}
}
}
}
]
},
"payment_instrument": {
"title": "Payment Instrument (Payment Terms)",
"description": "Payment instrument extended with payment term and payment schedule assignment.",
"allOf": [
{ "$ref": "types/payment_instrument.json" },
{
"type": "object",
"properties": {
"term_refs": {
"type": "array",
"items": { "$ref": "#/$defs/schedule_ref" },
"description": "Payment terms or schedules this instrument pays for. References can target a payment term by `id`, a payment schedule by `schedule_id`, a schedule class by `schedule_type`, or a combination. If omitted, the instrument can be applied by the business according to the selected terms and handler constraints."
}
}
}
]
},
"payment": {
"title": "Payment with Terms",
"description": "Payment object extended with payment terms.",
"description": "Payment object extended with selectable payment terms.",
"allOf": [
{ "$ref": "payment.json" },
{
"type": "object",
"required": ["terms"],
"properties": {
"available_terms": {
"terms": {
"type": "array",
"items": { "$ref": "#/$defs/available_payment_term" },
"description": "Available payment terms the buyer can choose from. Each term defines schedules for when payments are due.",
"items": { "$ref": "#/$defs/payment_term" },
"minItems": 1,
"description": "Payment terms the Buyer can choose from. An unselected term's amounts are indicative; the selected term's schedule totals sum to the checkout total.",
"ucp_request": "omit"
},
"instruments": {
"type": "array",
"items": { "$ref": "#/$defs/payment_instrument" },
"description": "Payment instruments, optionally assigned to selected payment terms or schedules."
"selected_term_id": {
"type": "string",
"description": "ID of the selected payment term. MUST match one `terms[].id` from the latest Checkout response. Always present in responses: the Checkout total is the selected term's total, so a response without a selection would show an amount that matches no stated terms. Where the Buyer has made no choice, the Business selects a default. Omitted on create requests because term IDs are checkout-scoped and no terms exist yet. Selecting a term is a Checkout mutation: the Business response is authoritative for all derived state.",
"ucp_request": {
"create": "omit",
"update": "optional",
"complete": "optional"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we simplify the lifecycle by requiring the selected term to be established in an authoritative Update-Checkout response before Complete? Changing the term would happen through Update, which returns the final totals, disclosures, and eligible handlers. Complete would then echo the same selected_term_id as an assertion and reject any mismatch, rather than selecting or repricing atomically. The default term from Create would already satisfy this invariant, so an extra Update is needed only when the buyer changes it, and any changing selected term will only be done via Update.

},
"ucp_response": "required"
}
}
}
]
},
"order_payment": {
"title": "Order Payment with Accepted Term",
"description": "Order payment details carrying the term the Buyer accepted at checkout.",
"type": "object",
"required": ["accepted_term"],
"properties": {
"accepted_term": {
"$ref": "#/$defs/payment_term",
"description": "The payment term the Buyer accepted at checkout. Businesses MUST carry it forward so the Order states the amounts owed and when, and MUST ensure its schedule totals sum to the Order total. The available terms are checkout state and are not projected."
}
}
},
"dev.ucp.shopping.checkout": {
"title": "Checkout with Payment Terms",
"description": "Checkout extended with payment terms and payment schedules.",
"description": "Checkout extended with selectable payment terms.",
"allOf": [
{ "$ref": "checkout.json" },
{
Expand All @@ -109,22 +79,17 @@
]
},
"dev.ucp.shopping.order": {
"title": "Order with Payment Terms",
"description": "Order extended with the payment terms accepted during checkout.",
"title": "Order with Accepted Payment Term",
"description": "Order extended with the payment term accepted at checkout.",
"allOf": [
{ "$ref": "order.json" },
{
"type": "object",
"properties": {
"payment": {
"type": "object",
"properties": {
"terms": {
"type": "array",
"items": { "$ref": "#/$defs/available_payment_term" },
"description": "Complete payment terms accepted for the originating checkout."
}
}
"$ref": "#/$defs/order_payment",
"description": "Payment details for the Order, including the accepted payment term.",
"ucp_response": "required"
}
}
}
Expand Down
30 changes: 0 additions & 30 deletions source/schemas/shopping/types/available_payment_term.json

This file was deleted.

29 changes: 0 additions & 29 deletions source/schemas/shopping/types/entity_ref.json

This file was deleted.

39 changes: 31 additions & 8 deletions source/schemas/shopping/types/payment_schedule.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,48 @@
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://ucp.dev/schemas/shopping/types/payment_schedule.json",
"title": "Payment Schedule",
"description": "A named payment obligation within a payment term. Timing is disclosed in buyer-facing text while totals provide a structured allocation amount.",
"description": "A single payment that settles part or all of the checkout under a payment term. Timing is stated in buyer-facing text; `type` and `due_at` are supplementary machine-readable signals derived from it.",
"type": "object",
"required": ["id", "type", "description", "totals"],
"required": [
"id",
"type",
"description",
"totals"
],
"additionalProperties": true,
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for this payment schedule."
"description": "Identifier for this payment schedule, unique within its payment term. Businesses SHOULD keep it stable across responses while the schedule remains the same payment."
},
"type": {
"$ref": "schedule_type.json",
"description": "Broad payment timing class. Well-known values: `immediate` and `deferred`."
"type": "string",
"description": "Timing class, drawn from an open vocabulary. `immediate` is the only value with defined meaning: the payment is captured when the checkout is completed. Any other value means the payment is not captured at completion, and `description` states when it is due. Businesses MAY use additional values (e.g. `deferred`, `on_shipment`); Platforms MUST treat unrecognized values as not captured at completion."

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this describe when payment is due rather than when it is captured? Capture is payment-handler behavior: an amount can be due at completion but only authorized then and captured later. Defining immediate as “due when checkout completes” would keep Payment Terms separate from execution semantics (capture).

},
"description": {
"$ref": "../../common/types/description.json",
"description": "Complete buyer-facing statement of when and how this payment is due. Businesses MUST make this field sufficient on its own: a Platform that recognizes no `type` value and reads no other field MUST be able to present this schedule correctly. Platforms MAY use `type` and `due_at` for enhanced presentation, but MUST NOT present derived timing that contradicts this field."
},
"due_at": {
"type": "string",
"description": "Complete buyer-facing explanation of when and how the payment is due."
"format": "date-time",
"description": "Absolute RFC 3339 date-time when this payment is due, when the Business can determine one at checkout. Supplementary to `description`, never a replacement for it. Omitted when the due date depends on a future event (e.g. 'due on delivery'); the timing is then stated in `description` alone."
},
"totals": {
"$ref": "totals.json",
"description": "Aggregate pricing breakdown for every payment represented by this schedule. The entry with `type: total` is the complete amount allocated to the schedule in the checkout or order currency."
"type": "array",
"items": {
"allOf": [
{ "$ref": "total.json" },
{
"if": { "properties": { "type": { "const": "total" } }, "required": ["type"] },
"then": { "properties": { "amount": { "minimum": 0 } } }
}
]
},
"contains": { "properties": { "type": { "const": "total" } }, "required": ["type"] },
"minContains": 1,
"maxContains": 1,
"description": "What this one payment costs. Businesses MUST include exactly one `total` entry with a non-negative amount: the final amount charged when this payment is taken, inclusive of tax and every other charge, in the checkout currency's minor units (ISO 4217). Unlike checkout totals, no `subtotal` is required, because the purchase is priced at the checkout rather than once per payment."
}
}
}
27 changes: 24 additions & 3 deletions source/schemas/shopping/types/payment_term.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,34 @@
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://ucp.dev/schemas/shopping/types/payment_term.json",
"title": "Payment Term",
"description": "A buyer's selection of a payment term for specific line items.",
"description": "A way of paying for the checkout: one or more payment schedules that together cover its total.",
"type": "object",
"required": ["id"],
"required": [
"id",
"title",
"schedules"
],
"additionalProperties": true,
"properties": {
"id": {
"type": "string",
"description": "ID of the selected payment term from available_terms."
"description": "Unique identifier for this payment term within the checkout. Referenced by `payment.selected_term_id`."
},
"title": {
"type": "string",
"description": "Short label that distinguishes this term from its siblings (e.g. 'Pay now', 'Pay in 4', 'Deposit + balance at check-in')."
},
"description": {
"$ref": "../../common/types/description.json",
"description": "Supplementary context for the title (e.g. 'Save 5% by paying today'). Directly renderable; MUST NOT repeat the title."
},
"schedules": {
"type": "array",
"items": {
"$ref": "payment_schedule.json"
},
"minItems": 1,
"description": "Payment schedules that settle this checkout under this term, in the order they come due."
}
}
}
8 changes: 0 additions & 8 deletions source/schemas/shopping/types/schedule_type.json

This file was deleted.

Loading