How UCP Manages Payment Handlers and Resolves Payment Instruments
UCP treats payment handlers as self-describing components registered in the ucp.payment_handlers registry, resolving concrete instrument schemas through an intersection algorithm that merges available_instruments constraints from both businesses and platforms.
The Universal Commerce Protocol (UCP) enables decentralized payment processing by defining handlers as discoverable, schema-backed entities rather than hard-coded integrations. Within the Universal-Commerce-Protocol/ucp repository, the resolution logic lives in declarative JSON schemas and Python tooling that materializes handler capabilities at runtime. This article examines the source code implementation—from the payment_handler.json schema definition to the ucp-schema CLI resolver—to explain how the protocol deterministically narrows which payment instruments a checkout session can actually use.
Payment Handler Declaration and Schema Structure
Every UCP payment handler must conform to the PaymentHandler schema defined in source/schemas/payment_handler.json. The schema establishes a base entity definition under $defs/base that inherits from the generic UCP entity (ucp.json#/$defs/entity) and adds the optional available_instruments field, which references shopping/types/available_payment_instrument.json for its item structure.
The schema provides three concrete variations:
- Base schema (
$defs/base): The foundational structure containingid,version,spec,schema, and the optionalavailable_instrumentsarray. - Platform schema (
$defs/platform_schema): UsesallOfcomposition to extend the base for platforms (shopping sites) that discover handlers. - Business schema (
$defs/business_schema): UsesallOfcomposition to extend the base for merchants that advertise handler capabilities.
In practice, a business publishes a handler declaration inside its discovery profile under the ucp.payment_handlers registry. The following minimal declaration shows a handler advertising card support with brand constraints:
{
"ucp": {
"payment_handlers": {
"com.example.tokenizer": [
{
"id": "tokenizer_01",
"version": "2026-01-11",
"spec": "https://example.com/ucp/tokenizer/spec.json",
"schema": "https://example.com/ucp/tokenizer/schema.json",
"available_instruments": [
{ "type": "card", "constraints": { "brands": ["visa", "mastercard"] } }
],
"config": {
"merchant_id": "mid_12345"
}
}
]
}
}
}
The Intersection Algorithm for Instrument Resolution
UCP resolves the final set of usable payment instruments by intersecting the available_instruments arrays published by two distinct participants: the Business (merchant) and the Platform (shopping site displaying the handler).
The rules governing this resolution are:
- When a handler omits
available_instruments, it implicitly claims support for any instrument type defined by its referenced handler schema. - When both participants publish arrays, the protocol performs a set intersection, retaining only instrument types and constraints that satisfy both declarations.
Consider a platform that imports the handler above but only supports Visa. It publishes its own constrained declaration:
{
"ucp": {
"payment_handlers": {
"com.example.tokenizer": [
{
"id": "tokenizer_01",
"available_instruments": [
{ "type": "card", "constraints": { "brands": ["visa"] } }
]
}
]
}
}
}
The resolved instrument set available to the checkout API becomes the intersection: Visa cards only. The platform must not present Mastercard as an option, even though the business supports it. This declarative narrowing occurs before any payment processing begins, ensuring deterministic validation schemas.
Runtime Schema Resolution Implementation
While static JSON schemas define the structure, the UCP repository provides dynamic resolution tooling in main.py that materializes handler-specific request and response bodies at runtime. This is critical for documentation generation, SDK validation, and server-side runtime checks.
The resolution system centers on the _resolve_schema() function (lines 88–106) in main.py:
def _resolve_schema():
# Builds: ucp-schema resolve <schema_path> --request|--response --op <operation>
# Parses JSON output into Python dicts for further processing
pass # Implementation handles CLI subprocess calls
Key implementation details include:
- Module-level caching (lines 40–41): A
_resolved_schema_cachedictionary prevents redundant external calls to theucp-schemaCLI. - Schema bundling (lines 118–124): When the
--bundleflag is passed, all$refpointers are inlined into a single self-contained schema, useful for generating static documentation tables. - Macro integration (lines 236–247): The
define_env()function registers custom Jinja2 macros (schema_fields,method_fields) for the MkDocs plugin, which calls_resolve_with_ucp_schema()(lines 169–176) to fetch fully resolved schemas for Markdown rendering.
The following Python snippet mirrors the internal resolver logic used by the documentation macros:
from pathlib import Path
import json
import subprocess
def resolve(schema_path: str, direction: str = "response", operation: str = "read"):
"""Thin wrapper around the same logic used by the MkDocs plugin."""
cmd = [
"ucp-schema",
"resolve",
schema_path,
"--response" if direction == "response" else "--request",
"--op",
operation,
]
result = subprocess.run(cmd, capture_output=True, text=True, check=False)
if result.returncode != 0:
raise RuntimeError(f"ucp-schema failed: {result.stderr}")
return json.loads(result.stdout)
# Example: resolve the checkout response schema for a particular handler
checkout_resp = resolve(
"source/schemas/shopping/checkout.json",
direction="response",
operation="read",
)
print(json.dumps(checkout_resp, indent=2))
This resolver executes during documentation builds via directives like {% schema_fields "checkout" direction="response" operation="read" %}, producing concrete JSON schemas that incorporate the intersected available_instruments constraints.
End-to-End Checkout Flow
The complete lifecycle of a payment handler resolution follows four distinct phases:
- Business Publication: The merchant publishes a discovery profile at
/.well-known/ucpcontaining the handler declaration underucp.payment_handlers, specifying itsavailable_instrumentsand handler schema URL. - Platform Discovery: The shopping site fetches the profile, reads the handler metadata from
source/schemas/payment_handler.json, and merges its ownavailable_instruments(derived fromsource/schemas/shopping/types/available_payment_instrument.jsonconstraints). - Intersection Resolution: Before rendering the checkout UI, the platform computes the intersection of both instrument arrays. The
source/services/shopping/rest.openapi.jsonOpenAPI specification governs how the checkout API consumes these resolved constraints. - Schema Materialization: When validating checkout requests or generating SDKs, the platform or business invokes the
ucp-schemaresolver (as implemented inmain.py) to produce the final request/response JSON schemas, ensuring that only the intersected instrument types (e.g., Visa only) validate successfully.
This architecture guarantees that instrument resolution remains deterministic, declarative, and centrally validated without hard-coding specific payment methods into the protocol core.
Summary
- Payment handlers in UCP are self-describing JSON entities declared in the
ucp.payment_handlersregistry of a discovery profile. - Schema inheritance flows from
ucp.json#/$defs/entitythrough$defs/baseto platform-specific and business-specific schemas insource/schemas/payment_handler.json. - Instrument resolution relies on an intersection algorithm applied to the
available_instrumentsarrays supplied by both the business and the platform. - Runtime validation uses the
ucp-schemaCLI (wrapped bymain.pylines 88–106) to dynamically resolve and bundle schemas for documentation and API validation. - Constraint narrowing occurs before checkout, ensuring that only mutually supported payment instruments (such as specific card brands) are presented to the user.
Frequently Asked Questions
What happens if a handler omits the available_instruments field?
When the available_instruments array is omitted from a handler declaration, the handler implicitly claims support for any instrument type defined by its referenced schema. This places the burden of constraint definition entirely on the platform side during the intersection phase.
How does UCP determine the final allowed payment instruments?
The protocol performs a set intersection of the available_instruments arrays published by the business and the platform. Only instrument types and constraints appearing in both arrays are included in the resolved set used for checkout validation.
What is the purpose of the ucp-schema CLI tool?
The ucp-schema CLI resolves JSON schema references on-the-fly, either producing dereferenced schemas or fully bundled (inlined) schemas via the --bundle flag. According to the implementation in main.py lines 40–41 and 88–106, it is used by documentation macros, SDK generators, and runtime validation logic to materialize concrete request and response shapes that incorporate the intersected instrument constraints.
Where is the checkout API contract defined?
The OpenAPI specification for the checkout and order APIs resides in source/services/shopping/rest.openapi.json. This file references the resolved payment handler schemas and defines how the intersected available_instruments constraints translate to actual API request validations.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →