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
87 changes: 87 additions & 0 deletions docs/documentation/schema-authoring.md
Original file line number Diff line number Diff line change
Expand Up @@ -420,6 +420,93 @@ typos in core metadata like the `ucp` block).
}
```

### Constraint Objects

UCP uses `constraints` objects when a declaration or response needs to narrow
what is acceptable without changing the base payload schema. Constraints are sparse runtime requirements on an already-typed target.
UCP defines three composable primitive constraints:

| Primitive | Target | Built-in vocabulary |
| :-------- | :----- | :------------------ |
| [`ObjectConstraint`](site:{{ ucp_version }}/schemas/shopping/types/object_constraint.json) | Object | `required` |
| [`ValueConstraint`](site:{{ ucp_version }}/schemas/shopping/types/value_constraint.json) | Property value | `enum`, `const` |
| [`TypeConstraint`](site:{{ ucp_version }}/schemas/shopping/types/type_constraint.json) | Typed branch | `type`, optional `constraints` |

A concrete Object Constraint **MUST** extend `object_constraint.json` and define
every supported key. The key's schema determines its meaning:

- `ObjectConstraint` — recursively constrains a same-named object property.
- `ValueConstraint` — constrains a same-named property's value.
- `TypeConstraint` (or an array of them) — selects a typed branch.
- Any other schema — defines a domain-specific literal operator whose semantics
the owning specification must document.

Every name in `required` **MUST** be a property of the constrained object. The
Object Constraint stays open so handler and extension schemas can compose with
`allOf`; after negotiation, a key not defined by the concrete constraint schema
is an authoring or compatibility error.

This example defines all four key forms:

<!-- ucp:example skip reason="schema authoring example" -->
```json
{
"$defs": {
"constraint": {
"allOf": [
{ "$ref": "object_constraint.json" },
{
"properties": {
"billing_address": {
"allOf": [
{ "$ref": "object_constraint.json" },
{
"properties": {
"address_country": { "$ref": "value_constraint.json" }
}
}
]
},
"credentials": {
"type": "array",
"items": { "$ref": "type_constraint.json" }
},
"brands": {
"type": "array",
"items": { "type": "string" }
}
}
}
]
}
}
}
```

Its wire value remains local to the constrained fields:

<!-- ucp:example skip reason="schema authoring example" -->
```json
{
"required": ["billing_address"],
"billing_address": {
"required": ["address_country"],
"address_country": { "enum": ["US", "CA"] }
},
"credentials": [{ "type": "token" }],
"brands": ["visa", "mastercard"]
}
```

`ValueConstraint` is deliberately closed: unsupported assertions cannot be
ignored safely. `ObjectConstraint` and `TypeConstraint.type` are extension
points. To type-check a declaration, resolve its target and concrete constraint
schemas, validate each key and value, and check `required` names against the
target. Object and Value Constraints can compile into a JSON Schema overlay;
literal domain operators are only type-checked, and their owner enforces their
meaning. Declared constraints are an upfront minimum; dynamic requirements still
use recoverable errors and [`message_error.path`](site:{{ ucp_version }}/schemas/common/types/message_error.json).

### Property-Count Constraints (`minProperties` / `maxProperties`)

By default, UCP schemas do not set `minProperties` or `maxProperties` on
Expand Down
154 changes: 128 additions & 26 deletions docs/specification/payment-handler-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -186,11 +186,22 @@ and desired configuration.
}
```

**`available_instruments`** is optional. When absent, the handler places no
restrictions on instrument types or constraints — it supports the full set of
instrument types defined by its handler schema. When present, it narrows the
advertised types and/or applies additional constraints (e.g., limiting card
brands to `["visa", "mastercard"]`).
**`available_instruments`** is an array of
[`TypeConstraint`](site:schemas/shopping/types/type_constraint.json) entries over
the handler's payment-instrument family. Each entry selects an instrument branch
with `type` and applies an Object Constraint to acceptable instances:

```text
available_instruments[] Type Constraint
├── type selects an instrument schema
└── constraints Object Constraint on that instrument
├── required requires instrument properties
├── billing_address constrains a nested object
└── credentials[] selects and constrains credential types
```

When omitted, the declaration does not narrow the handler's instruments. When
present, only the listed types are available and each entry's constraints apply.

---

Expand Down Expand Up @@ -296,6 +307,9 @@ authoritative value returned in the `response_schema`.
- Its own `business_schema` declaration (what the merchant is actually set up to accept)
- Cart/checkout context (e.g., certain item types may restrict eligible methods)

The business matches Type Constraints by `type` and resolves their Object
Constraints according to the negotiated handler schema.

3. **Response is authoritative** — the `available_instruments` in the
`response_schema` reflects the business's resolved selection for this specific
checkout. Platforms **MUST** treat it as authoritative and **MUST NOT** attempt
Expand All @@ -312,6 +326,47 @@ authoritative value returned in the `response_schema`.
In this example, the business's PSP is not configured for Discover, so Discover
is excluded from the response even though the platform supports it.

#### Constraint Semantics

Within each available-instrument Type Constraint, `constraints` is an
[`ObjectConstraint`](site:schemas/shopping/types/object_constraint.json) on the
selected instrument. The base availability schema defines:

| Key | Constraint type | Meaning |
| :-- | :-------------- | :------ |
| `required` | Object | Instrument properties required in this context. |
| `billing_address` | Object | Nested requirements on the billing address. |
| `credentials` | Type | Accepted credential branches and their requirements. |

Concrete schemas add their own keys. The card availability schema adds `brands`,
a literal list of accepted networks. Use field constraints instead of
handler-specific booleans for modeled data.

<!-- ucp:example schema=payment_handler def=business_schema -->
```json
{
"id": "processor_tokenizer_1234",
"version": "{{ ucp_version }}",
"available_instruments": [
{
"type": "card",
"constraints": {
"required": ["billing_address"],
"billing_address": {
"required": ["postal_code", "address_country"]
},
"credentials": [{ "type": "token" }],
"brands": ["visa", "mastercard"]
}
}
]
}
```

See [Constraint Objects](../documentation/schema-authoring.md#constraint-objects)
for composition rules. Declared constraints are the upfront minimum; dynamic
requirements still use recoverable errors.

---

#### Defining the Schema
Expand All @@ -322,6 +377,7 @@ Authors typically define each shape in its own file and reference them:
- **Config** — Configuration for platform/business declarations and runtime responses
- **Instrument** — The payment instrument structure returned to platforms
- **Credential** — The credential structure within instruments
- **Available instrument** — Optional availability declaration item, used only when the handler has typed availability constraints to validate

**Example Handler Schema:**

Expand Down Expand Up @@ -351,6 +407,14 @@ Authors typically define each shape in its own file and reference them:
{ "$ref": "#/$defs/tokenizer_alt_instrument" }
]
},
"available_payment_instrument": {
"title": "Available Tokenizer Instrument",
"description": "Optional: validates available_instruments[] because this handler defines typed availability constraints.",
"oneOf": [
{ "$ref": "types/tokenizer_instrument.json#/$defs/available_tokenizer_card" },
{ "$ref": "types/tokenizer_alt_instrument.json#/$defs/available_tokenizer_alt" }
]
},
"platform_schema": {
"title": "Tokenizer (Platform)",
"description": "Platform-level handler configuration for discovery.",
Expand Down Expand Up @@ -508,19 +572,20 @@ multiple instrument types for different payment flows.

**Available Instrument Schemas:**

Each instrument schema defines its own `available_*` variant in `$defs` that
specifies what constraints are valid for that instrument type. For example,
[`card_payment_instrument.json`](site:schemas/shopping/types/card_payment_instrument.json)
defines `available_card_payment_instrument` with a `brands` constraint.
[`available_payment_instrument.json`](site:schemas/shopping/types/available_payment_instrument.json)
is the reusable Type Constraint over payment instruments. Instrument schemas can
publish an `available_*` definition that specializes a branch and its Object
Constraint; the card definition adds `brands` and credential refinements.

| Schema | Constraints |
| :----------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------- |
| [`available_payment_instrument.json`](site:schemas/shopping/types/available_payment_instrument.json) | Base: type, constraints (open object) |
| `card_payment_instrument.json#/$defs/available_card_payment_instrument` | Extends base with `constraints.brands` for card networks |
| Schema | Constraint shape |
| :--------------------------------------------------------------------------------------------------- | :--------------------------------------------------- |
| [`available_payment_instrument.json`](site:schemas/shopping/types/available_payment_instrument.json) | Open type, `required`, address, and credential keys |
| `card_payment_instrument.json#/$defs/available_card_payment_instrument` | `type: "card"`, `brands`, and credential refinements |

Handlers reference these instrument-defined schemas when declaring
`available_instruments`. The **instrument schema authors** define what
constraints are meaningful (e.g., `brands` for cards), and **platforms/businesses** use this to advertise what they support (e.g., `["visa", "mastercard"]`).
The base Payment Handler intentionally remains open: an instrument `type` does
not globally select a schema. After negotiation, consumers use
`$defs.{handler_name}.available_payment_instrument` from the handler schema.
Handlers that add no availability-specific keys can omit this definition.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

A few concrete examples would help here — this is how the three use cases map onto the primitives. Suggested insertion:

Suggested change
Handlers that add no availability-specific keys can omit this definition.
Handlers that add no availability-specific keys can omit this definition.
**Common constraint examples**
The same three primitives express the requirements handlers most often advertise
upfront. Each is an entry a handler publishes under `available_instruments[]`.
Require a card verification value (CVV) for card payments:
<!-- ucp:example skip reason="instrument availability instance" -->
```json
{
"type": "card",
"constraints": {
"credentials": [
{ "type": "card", "constraints": { "required": ["cvc"] } }
]
}
}
```
Require Address Verification System (AVS) fields from the billing address:
<!-- ucp:example skip reason="instrument availability instance" -->
```json
{
"type": "card",
"constraints": {
"billing_address": { "required": ["street_address", "postal_code"] }
}
}
```
Require a network-token cryptogram (and ECI value) on card credentials:
<!-- ucp:example skip reason="instrument availability instance" -->
```json
{
"type": "card",
"constraints": {
"credentials": [
{
"type": "card",
"constraints": {
"card_number_type": { "const": "network_token" },
"required": ["cryptogram", "eci_value"]
}
}
]
}
}
```


**Example `types/tokenizer_instrument.json`**:

Expand All @@ -541,16 +606,21 @@ constraints are meaningful (e.g., `brands` for cards), and **platforms/businesse
{
"type": "object",
"properties": {
"type": { "const": "tokenizer_card" },
"type": { "const": "card" },
"constraints": {
"type": "object",
"properties": {
"tokenization_types": {
"type": "array",
"items": { "type": "string" },
"description": "Supported tokenization types (e.g., ['network_token', 'merchant_token'])."
"allOf": [
{ "$ref": "https://ucp.dev/{{ ucp_version }}/schemas/shopping/types/object_constraint.json" },
{
"type": "object",
"properties": {
"tokenization_types": {
"type": "array",
"items": { "type": "string" },
"description": "Supported tokenization types (e.g., ['network_token', 'merchant_token'])."
}
}
}
}
]
}
}
}
Expand All @@ -564,7 +634,7 @@ constraints are meaningful (e.g., `brands` for cards), and **platforms/businesse
"type": "object",
"required": ["type"],
"properties": {
"type": { "const": "tokenizer_card" },
"type": { "const": "card" },
"credential": {
"oneOf": [
{ "$ref": "tokenizer_token.json" },
Expand All @@ -588,6 +658,19 @@ constraints are meaningful (e.g., `brands` for cards), and **platforms/businesse
"$id": "https://example.com/ucp/handlers/tokenizer/types/tokenizer_alt_instrument.json",
"title": "Tokenizer Alt Instrument",
"description": "Alternative payment instrument for com.example.tokenizer.",
"$defs": {
"available_tokenizer_alt": {
"allOf": [
{ "$ref": "https://ucp.dev/{{ ucp_version }}/schemas/shopping/types/available_payment_instrument.json" },
{
"type": "object",
"properties": {
"type": { "const": "tokenizer_alt" }
}
}
]
}
},
"allOf": [
{ "$ref": "https://ucp.dev/{{ ucp_version }}/schemas/shopping/types/payment_instrument.json" }
],
Expand Down Expand Up @@ -625,7 +708,11 @@ extend these schemas to include handler-specific credential context. Handlers
**MAY** define multiple credential types for different instrument flows.

The specification **MUST** define which credential types are accepted by the
handler.
handler. Credential schemas that can appear in
`available_instruments[].constraints.credentials[]` **SHOULD** also define a
`$defs.constraint` typed constraint entry. That entry lets handler declarations
say both "this credential family is accepted" and, when needed, which optional
credential fields are required in the current context.

**Important:** If using token credentials, the schema **MUST** include an
expiration field (`expiry`, `ttl`, or similar) to ensure platforms know when to
Expand Down Expand Up @@ -655,6 +742,21 @@ refresh credentials.
"format": "date-time",
"description": "Token expiration. Platforms must refresh before this time."
}
},
"$defs": {
"constraint": {
"title": "Tokenizer Card Token Constraint",
"description": "Typed constraint entry for tokenizer card token credentials.",
"allOf": [
{ "$ref": "https://ucp.dev/{{ ucp_version }}/schemas/shopping/types/type_constraint.json" },
{
"type": "object",
"properties": {
"type": { "const": "tokenizer_card_token" }
}
}
]
}
}
}
```
Expand Down
34 changes: 22 additions & 12 deletions source/schemas/shopping/types/available_payment_instrument.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,28 @@
"$id": "https://ucp.dev/schemas/shopping/types/available_payment_instrument.json",
"title": "Available Payment Instrument",
"description": "An instrument type available from a payment handler with optional constraints.",
"type": "object",
"required": ["type"],
"properties": {
"type": {
"type": "string",
"description": "The instrument type identifier (e.g., 'card', 'gift_card'). References an instrument schema's type constant."
},
"constraints": {
"allOf": [
{ "$ref": "type_constraint.json" },
{
"type": "object",
"additionalProperties": true,
"description": "Constraints on this instrument type. Structure depends on instrument type and active capabilities.",
"minProperties": 1
"properties": {
"constraints": {
"type": "object",
"properties": {
"billing_address": {
"$ref": "object_constraint.json",
"description": "Local constraint on the instrument's `billing_address` field."
},
"credentials": {
"type": "array",
"items": { "$ref": "type_constraint.json" },
"uniqueItems": true,
"description": "Credential specific constraints accepted for the payment instrument branch selected by `type`. Concrete instrument schemas can narrow known credential entries while preserving handler-extended credential entries."
}
},
"description": "Constraints on this instrument type. Base payment instrument constraints can require and describe `billing_address` and `credentials`; concrete instrument schemas SHOULD add instrument-specific constraint keys while remaining open to extensions."
}
}
}
}
]
}
Loading
Loading