From 98b704cad78d1948aab499498e2b3f7ed2f0cd1d Mon Sep 17 00:00:00 2001 From: Daniel Wyckoff Date: Fri, 17 Jul 2026 12:25:55 -0400 Subject: [PATCH] replace detailed due_at with an open string description Assisted-By: devx/cda4fcd3-f600-4f4b-9e5e-884732556852 --- docs/specification/payment-terms.md | 65 ++++++++----------- .../shopping/types/payment_schedule.json | 14 ++-- source/schemas/shopping/types/schedule.json | 36 ---------- 3 files changed, 33 insertions(+), 82 deletions(-) delete mode 100644 source/schemas/shopping/types/schedule.json diff --git a/docs/specification/payment-terms.md b/docs/specification/payment-terms.md index 5d873417d..4a4dfdbb0 100644 --- a/docs/specification/payment-terms.md +++ b/docs/specification/payment-terms.md @@ -59,10 +59,6 @@ terms and term-aware payment instruments. {{ schema_fields('types/payment_schedule', 'payment_terms') }} -#### Schedule - -{{ schema_fields('types/schedule', 'payment_terms') }} - #### Schedule Type {{ schema_fields('types/schedule_type', 'payment_terms') }} @@ -86,25 +82,23 @@ Payment schedules use `type` as an open string vocabulary. Well-known values are | Type | Meaning | | :--- | :------ | | `immediate` | Payment is due when checkout is completed. | -| `deferred` | Payment is due after checkout completion, according to `due_at`. | +| `deferred` | Payment is due after checkout completion, as stated in `description`. | Businesses MAY use additional values. Platforms MUST tolerate unknown schedule types and either apply business-specific support or surface clear validation messages when a selected payment instrument cannot satisfy them. -An `immediate` schedule **MUST** omit `due_at`; a `deferred` schedule **MUST** -include a non-empty `due_at`. The shared Schedule fields are: - -* `anchor_date` — RFC 3339 reference point. If omitted, the anchor is the - instant successful checkout completion creates the order. -* `offset` — ISO 8601 duration from the anchor to the first occurrence. -* `interval` — ISO 8601 duration between recurring occurrences. -* `occurrences` — finite number of occurrences; **MUST** be present when - `interval` is present. +Every schedule has a buyer-facing `description` that **MUST** completely state +when and how its payment or payments are due. The `type` field provides only a +broad timing class; platforms do not infer dates or processor instructions from +it. -Each payment schedule has `totals[]`. For a recurring schedule, those totals -apply to each occurrence. Expanding every occurrence, the sum of schedule -`total` entries **MUST** equal the checkout `total` exactly once. +Each schedule's `totals[]` is the aggregate amount of every payment represented +by that schedule. A schedule may describe several occurrences in text, but its +`total` covers the complete series. If individual occurrences need different +instrument assignments, the business represents them as separate schedules. +The sum of schedule `total` entries **MUST** equal the checkout `total` exactly +once. Schedules settle only the current checkout; they do not create future purchases, orders, renewals, or fulfillment obligations. They disclose payment timing but @@ -156,7 +150,7 @@ When the [Split Payments](split-payments.md) extension is also active, instruments may include an `amount` field to specify a fixed contribution when multiple instruments share a schedule. A fixed contribution **MUST** resolve to exactly one schedule. Contributions to each schedule **MUST** equal that -schedule's expanded total, and every schedule **MUST** be covered by at least +schedule's aggregate total, and every schedule **MUST** be covered by at least one eligible instrument. ## Discovery @@ -226,6 +220,7 @@ When this extension is active, a completed Order **MUST** include exactly one { "id": "sched_first_night", "type": "immediate", + "description": "Due when checkout is completed.", "totals": [ { "type": "subtotal", "amount": 30000 }, { "type": "total", "amount": 30000 } @@ -234,9 +229,7 @@ When this extension is active, a completed Order **MUST** include exactly one { "id": "sched_check_in_balance", "type": "deferred", - "due_at": { - "anchor_date": "2026-09-01T15:00:00-07:00" - }, + "description": "Due at check-in on September 1, 2026 at 3:00 PM PDT.", "totals": [ { "type": "subtotal", "amount": 90000 }, { "type": "total", "amount": 90000 } @@ -334,6 +327,7 @@ amounts. { "id": "sched_installment_1", "type": "immediate", + "description": "One payment of $25 due when checkout is completed.", "totals": [ { "type": "subtotal", "amount": 2500 }, { "type": "total", "amount": 2500 } @@ -342,22 +336,18 @@ amounts. { "id": "sched_installments_2_to_4", "type": "deferred", - "due_at": { - "offset": "P2W", - "interval": "P2W", - "occurrences": 3 - }, + "description": "Three payments of $25 due two, four, and six weeks after checkout.", "totals": [ - { "type": "subtotal", "amount": 2500 }, - { "type": "total", "amount": 2500 } + { "type": "subtotal", "amount": 7500 }, + { "type": "total", "amount": 7500 } ] } ] } ``` -The deferred schedule has three occurrences. Its totals apply to each -occurrence, so the term expands to four payments of $25 each. +The deferred schedule describes three occurrences, while its totals represent +the aggregate $75 obligation assigned to that schedule. ### Order with accepted payment terms @@ -435,6 +425,7 @@ The Order carries the complete accepted term from the originating Checkout. { "id": "sched_installment_1", "type": "immediate", + "description": "One payment of $25 due when checkout is completed.", "totals": [ { "type": "subtotal", "amount": 2500 }, { "type": "total", "amount": 2500 } @@ -443,14 +434,10 @@ The Order carries the complete accepted term from the originating Checkout. { "id": "sched_installments_2_to_4", "type": "deferred", - "due_at": { - "offset": "P2W", - "interval": "P2W", - "occurrences": 3 - }, + "description": "Three payments of $25 due two, four, and six weeks after checkout.", "totals": [ - { "type": "subtotal", "amount": 2500 }, - { "type": "total", "amount": 2500 } + { "type": "subtotal", "amount": 7500 }, + { "type": "total", "amount": 7500 } ] } ] @@ -465,7 +452,7 @@ The Order carries the complete accepted term from the originating Checkout. Platforms SHOULD: * Present available payment terms in a clear, buyer-understandable format. -* Show amounts and due dates for each schedule, including future payments. +* Show every schedule's aggregate amount and complete timing description. * Validate term and schedule references plus instrument constraints before submitting payment. * Tolerate unknown schedule types and surface clear recovery guidance when a selected instrument cannot satisfy them. @@ -475,7 +462,7 @@ Platforms SHOULD: Businesses MUST: * Provide at least one checkout-wide payment term when the extension is active. -* Ensure expanded schedule totals cover the checkout total exactly once. +* Ensure aggregate schedule totals cover the checkout total exactly once. * Validate that all instrument assignments select the same term and completely fund its schedules. * Include that complete accepted term on the resulting Order. diff --git a/source/schemas/shopping/types/payment_schedule.json b/source/schemas/shopping/types/payment_schedule.json index 17cb8b6dc..46cdcaac8 100644 --- a/source/schemas/shopping/types/payment_schedule.json +++ b/source/schemas/shopping/types/payment_schedule.json @@ -2,9 +2,9 @@ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://ucp.dev/schemas/shopping/types/payment_schedule.json", "title": "Payment Schedule", - "description": "Defines when payment is due for a payment term. A schedule can represent a single payment or a recurring series of payments.", + "description": "A named payment obligation within a payment term. Timing is disclosed in buyer-facing text while totals provide a structured allocation amount.", "type": "object", - "required": ["id", "type", "totals"], + "required": ["id", "type", "description", "totals"], "properties": { "id": { "type": "string", @@ -12,15 +12,15 @@ }, "type": { "$ref": "schedule_type.json", - "description": "Payment timing type. 'immediate' = captured immediately at order creation. 'deferred' = captured later per due_at schedule." + "description": "Broad payment timing class. Well-known values: `immediate` and `deferred`." }, - "due_at": { - "$ref": "schedule.json", - "description": "When the payment is due. Protocol semantics require this for deferred schedules and omit it for immediate schedules." + "description": { + "type": "string", + "description": "Complete buyer-facing explanation of when and how the payment is due." }, "totals": { "$ref": "totals.json", - "description": "Complete pricing breakdown for this obligation. The entry with `type: total` is the amount due in the checkout or order currency." + "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." } } } diff --git a/source/schemas/shopping/types/schedule.json b/source/schemas/shopping/types/schedule.json deleted file mode 100644 index 85e16b4cc..000000000 --- a/source/schemas/shopping/types/schedule.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://ucp.dev/schemas/shopping/types/schedule.json", - "title": "Schedule", - "description": "Defines timing for a future or recurring event using RFC 3339 dates and ISO 8601 durations.", - "$defs": { - "iso8601_duration": { - "type": "string", - "pattern": "^P(?=\\d|T\\d)(?:\\d+Y)?(?:\\d+M)?(?:\\d+W)?(?:\\d+D)?(?:T(?:\\d+H)?(?:\\d+M)?(?:\\d+S)?)?$", - "description": "ISO 8601 duration. Examples include `P30D`, `P2W`, `P1M`, and `PT1H`." - } - }, - "type": "object", - "properties": { - "anchor_date": { - "type": "string", - "format": "date-time", - "description": "Reference point for the schedule as an RFC 3339 timestamp. Defaults to order time if omitted." - }, - "offset": { - "$ref": "#/$defs/iso8601_duration", - "description": "ISO 8601 duration from `anchor_date` to the first occurrence.", - "examples": ["P30D", "P2W"] - }, - "interval": { - "$ref": "#/$defs/iso8601_duration", - "description": "ISO 8601 duration between recurring occurrences.", - "examples": ["P2W", "P1M"] - }, - "occurrences": { - "type": "integer", - "minimum": 1, - "description": "Number of occurrences. Omit for an indefinite recurring schedule." - } - } -}