Skip to content

feat: add required_fields to available payment instruments#288

Open
jamesandersen wants to merge 1 commit intoUniversal-Commerce-Protocol:mainfrom
jamesandersen:feat/required-fields-available-instruments
Open

feat: add required_fields to available payment instruments#288
jamesandersen wants to merge 1 commit intoUniversal-Commerce-Protocol:mainfrom
jamesandersen:feat/required-fields-available-instruments

Conversation

@jamesandersen
Copy link
Copy Markdown

@jamesandersen jamesandersen commented Mar 22, 2026

Enhancement Proposal: Required Fields on Available Payment Instruments

Summary

PR #187 added available_instruments to payment handlers, with each instrument declaring a type and optional constraints. This proposal adds a required_fields optional property to available_payment_instrument.json — enabling handlers to declare which optional fields they require for payment processing.

Type of change

  • New feature (non-breaking change which adds functionality)

Motivation

UCP payment instruments are composed of two layers: the instrument itself (payment_instrument.json — defines billing_address, display, etc.) and a nested credential (card_credential.json — defines cvc, name, cryptogram, etc.). Many properties across both layers are optional at the protocol level but required by individual merchants and their PSPs. For example:

  • CVC (credential.cvc via card_credential.json) — optional in the schema, but most merchants require it for card-not-present transactions
  • Cardholder name (credential.name via card_credential.json) — optional in the schema, but required by some PSPs (e.g., Adyen)
  • Cryptogram and ECI (credential.cryptogram, credential.eci_value via card_credential.json) — optional in the schema, but required by PSPs when processing network tokens for customer-initiated transactions
  • Billing address (billing_address on payment_instrument.json) — optional in the schema, but required by merchants who use Address Verification System (AVS) checks. Whether a merchant uses AVS varies by market:
    • US/UK merchants typically require billing address for AVS fraud checks
    • EU/APAC merchants often skip AVS entirely, relying on 3D Secure instead

In the traditional web flow, the merchant's checkout form explicitly shows which fields to fill in. In the agentic flow, there is no form — the platform must guess which optional fields the merchant actually requires. This leads to:

  1. Trial-and-error tokenization — platform submits a credential, PSP rejects it for missing CVC, platform retries with CVC. Wastes time, creates latency, degrades UX.
  2. Over-collection — to avoid rejections, platforms collect every possible field even when the merchant doesn't need them. Unnecessary friction, especially in markets that don't use AVS.

Goals

  • Enable payment handlers to declare which optional fields they require, per instrument type
  • Build incrementally on available_payment_instrument.json (PR #187) as a typed optional property
  • Eliminate guesswork for agentic platforms — collect exactly the right fields before attempting tokenization

Non-Goals

  • Prescribing specific field requirements (these are PSP-specific and vary by merchant)
  • Transaction-level or scenario-level requirements (new card vs. saved card vs. recurring — PSP-specific concerns best handled at the implementation level)
  • Real-time requirement negotiation (this is a static declaration, not a query/response flow)

Related Work

  • PR #187 (merged): "feat: add available_instruments to payment handler configurations" — added available_instruments with type and constraints to payment handlers. This proposal adds a sibling property to constraints.
  • PR #214 (merged): "feat: add payment instrument qualifiers" — added qualifiers to payment instruments.
  • Complementary to the Card Network Token Credential proposal — network tokens eliminate CVV need entirely, but required_fields makes that explicit in the handler configuration.

Detailed Design

Modified Schema: available_payment_instrument.json

Add required_fields as an optional property alongside the existing type and constraints:

{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$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": {
      "type": "object",
      "additionalProperties": true,
      "description": "Constraints on this instrument type. Structure depends on instrument type and active capabilities.",
      "minProperties": 1
    },
    "required_fields": {
      "type": "array",
      "items": { "type": "string" },
      "description": "Optional fields from the instrument or credential schemas that this handler requires for payment processing. Uses dot notation for nested properties: credential fields use 'credential.' prefix (e.g., 'credential.cvc'), billing address subfields use 'billing_address.' prefix (e.g., 'billing_address.postal_code'). Top-level instrument fields use their property name directly (e.g., 'billing_address' for the full object).",
      "minItems": 1,
      "uniqueItems": true
    }
  }
}

Field names in required_fields use dot notation to reference nested properties. Credential fields are nested under the credential property on the instrument, so they use the credential. prefix (e.g., credential.cvc, credential.name). Billing address subfields use the billing_address. prefix to reference individual properties from postal_address.json (e.g., billing_address.postal_code, billing_address.country_code). Requiring billing_address without a subfield suffix means the full billing address object is required. This matches the actual structure of the payment instrument object that the platform constructs.

Resolution Flow

Per the Payment Handler Guide, the business resolves available_instruments by intersecting the platform's capabilities with its own configuration. The resolved available_instruments in the response_schema is authoritative — platforms MUST treat it as the definitive source.

required_fields participates in this resolution naturally: the business includes required_fields in the resolved available_instruments returned in the response. The platform reads these requirements from the response and knows exactly which fields to collect from the user before constructing the instrument.

Usage Examples

The simplest and most common case — a merchant that requires CVC for card payments:

{
  "payment_handlers": [
    {
      "id": "handler_merchant_psp",
      "available_instruments": [
        {
          "type": "card",
          "required_fields": ["credential.cvc"]
        }
      ]
    }
  ]
}

A US merchant requiring CVC and postal code for AVS (postal-only AVS), also accepting network tokens:

{
  "payment_handlers": [
    {
      "id": "handler_merchant_stripe",
      "available_instruments": [
        {
          "type": "card",
          "required_fields": ["credential.cvc", "billing_address.postal_code"]
        },
        {
          "type": "card_network_token",
          "required_fields": ["credential.cryptogram", "credential.eci_value"]
        }
      ]
    }
  ]
}

The platform only needs to collect a ZIP/postal code — not a full street address — reducing friction for the user while still satisfying the merchant's AVS check.

A US merchant requiring CVC and full billing address (full AVS):

{
  "payment_handlers": [
    {
      "id": "handler_merchant_full_avs",
      "available_instruments": [
        {
          "type": "card",
          "required_fields": ["credential.cvc", "billing_address"]
        }
      ]
    }
  ]
}

When billing_address is specified without a subfield suffix, the platform collects the full billing address object.

An EU merchant with no AVS (relies on 3D Secure), requiring cardholder name:

{
  "payment_handlers": [
    {
      "id": "handler_merchant_adyen",
      "available_instruments": [
        {
          "type": "card",
          "required_fields": ["credential.cvc", "credential.name"]
        },
        {
          "type": "card_network_token",
          "required_fields": ["credential.cryptogram", "credential.eci_value", "credential.name"]
        }
      ]
    }
  ]
}

Design Rationale

  1. Incremental addition to existing schema — adds a single optional property to available_payment_instrument.json (PR #187). Non-breaking change: handlers that don't declare required_fields behave exactly as before.
  2. Typed property, not untyped convention — modeling required_fields as a typed property on the schema (rather than a key inside the untyped constraints bag) provides schema validation and encourages consistent reuse across implementations. This prevents constraints from becoming a free-form structure that varies across protocol implementations.
  3. Dot notation for nested fields — payment instruments contain nested objects (credential, billing_address). Dot notation (e.g., credential.cvc, billing_address.postal_code) unambiguously identifies which layer and subfield is required. Specifying a top-level name without a suffix (e.g., billing_address) means the full object is required.
  4. Fits existing resolution flow — per the Payment Handler Guide, the business resolves available_instruments and the response is authoritative. required_fields is included in the resolved response, so platforms read it at the same time they discover which instruments are available — no additional round-trip needed.
  5. Handler-level declaration — requirements are declared by the payment handler (merchant's PSP), not the platform. The PSP knows what it needs.
  6. Eliminates guesswork — platforms can collect exactly the right fields before attempting tokenization. The agent knows what to ask the user for upfront.

Risks and Mitigations

Risk Mitigation
Requirements may vary at runtime (e.g., amount-based CVC rules) This proposal declares requirements per instrument type, not per transaction. Runtime variations are PSP-specific and should be handled at the implementation level.
Handlers may not adopt the property Property is optional. Platforms fall back to existing behavior (guess/retry) when not present. Adoption incentive is clear: fewer failed tokenization attempts.
Field name ambiguity between instrument and credential levels Dot notation disambiguates: credential.cvc is clearly a credential field, billing_address.postal_code is clearly a billing address subfield, and billing_address without a suffix means the full object. This mirrors the actual structure of the payment instrument object.

Test Plan

  • JSON Schema validation: required_fields accepted on available_payment_instrument.json
  • Missing property: available_instruments works normally without required_fields (non-breaking)
  • Cross-instrument-type: test required_fields for card, card_network_token, and linked_wallet instrument types
  • Field name validation: verify that entries correspond to actual properties in the composed schema hierarchy
  • uniqueItems: verify duplicate field names are rejected

Graduation Criteria

  • Schema merged into UCP source
  • Specification documentation updated
  • 2+ independent implementations
  • TC majority vote for Candidate status

Code Changes

Modified Files:

  • source/schemas/shopping/types/available_payment_instrument.json — add optional required_fields property
  • docs/specification/payment-handler-guide.md — Required Fields section, dot notation reference, usage examples, resolution flow integration

Discussion Points:

  • Billing address granularity: This proposal supports both full billing_address and subfield-level requirements (e.g., billing_address.postal_code) using the same dot notation pattern as credential fields. This enables handlers to express postal-only AVS without requiring agents to collect a full street address. The subfield names reference properties from postal_address.json. An open question is whether the protocol should define a recommended set of billing address subfields (e.g., postal_code, country_code, street_address, locality, region) or leave it fully open to whatever postal_address.json defines.

References

  • UCP Payment Handler Specification Guide — defines handler structure, available instruments, and resolution flow
  • UCP available_payment_instrument.json — instrument type schema with type and constraints (source)
  • UCP card_payment_instrument.json — extends base with available_card_payment_instrument and constraints.brands (source)
  • UCP payment_handler.json — payment handler schema with available_instruments
  • UCP payment_instrument.json — base instrument schema defining billing_address
  • UCP card_credential.json — card credential schema defining cvc, name, cryptogram, eci_value
  • UCP PR #187 — Added available_instruments with type-specific constraints to payment handlers (merged)
  • UCP PR #214 — Payment instrument qualifiers (merged)

Checklist

  • My code follows the style guidelines of this project
  • I have performed a self-review of my own code
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes

@google-cla
Copy link
Copy Markdown

google-cla bot commented Mar 22, 2026

Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

View this failed invocation of the CLA check for more information.

For the most up to date status, view the checks section at the bottom of the pull request.

@jamesandersen jamesandersen force-pushed the feat/required-fields-available-instruments branch from 69a3cd0 to de0b8e6 Compare March 22, 2026 06:01
Copy link
Copy Markdown
Contributor

@alexpark20 alexpark20 left a comment

Choose a reason for hiding this comment

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

I understand the motivation here, but I am concern that this solution is not scalable, especially for any new schema type to be introduced. I think the right solution is to leverage the .schema field in entity (ucp.json#/$defs/entity).

Before PR #49 , payment handlers had separate config_schema (URI) and instrument_schemas (array of URIs). PR #49 consolidated these into a single schema URI — but this was a simplification of the reference structure, not a reduction in capability. A single JSON Schema document can contain $defs covering all instrument types and their requirements. That said, I think the protocol's existing schema field is already capable of expressing everything required_fields is trying to express, and more.

Although, one can argue that the current schema in the entity (ucp.json#/$defs/entity) cannot really express its intention. I imagine people often assume that schema is only for the payment handler config. As an alternative to introducing required_fields, I think re introducing instrument_schema (just URI, not array though) could solve the problem. What do you think?

@igrigorik igrigorik added the TC review Ready for TC review label Mar 30, 2026
@jamesandersen
Copy link
Copy Markdown
Author

@alexpark20 thanks for taking time to do the review! The context on #49 is useful.

I agree that schema is likely where one would expect new defs specific for the payment handler to live and it might be non-obvious if that same schema URL also was trying to refine other payment instrument and credential types e.g. card_payment_instrument.json and card_credential.json e.g. . Using instrument_schema would help with the clarity there. As you noted, this does offer the full expression of JSON schema syntax to essentially say "for this handler, the card credential you can supply to the card payment instrument looks a bit different from the standard definition in that cvc is a required field"

While I think I could get behind this idea here's a couple additional thoughts for why the required_fields still feels a bit more appealing to me:

  • Requirements can vary per business, not per handler. A handler like dev.shopify.card serves thousands of merchants — some require CVC, some don't (it's a per-merchant fraud setting). required_fields lives on available_instruments, which already participates in the resolution flow, so it resolves per checkout naturally. A static instrument_schema URI can't vary per merchant without dynamically generating schema documents.
    • FWIW this is the exact problem we've encountered which motivated this PR ;-)
  • Low overhead for a narrow problem. The platform needs to answer "does this merchant require CVC?". required_fields keeps that answer inline in the discovery response with no additional fetch, no $ref resolution across a schema inheritance to refine types, no JSON Schema evaluation engine. We could indicate that the dot-notation syntax follows RFC 9535 (JSONPath).
  • Complementary, not competing. If the community wants instrument_schema for richer constraints down the road, required_fields wouldn't conflict.

To make it concrete — here's the Shopify card handler under each approach:

required_fields:

{
  "id": "shopify.card",
  "version": "2026-01-15",
  "schema": "https://shopify.dev/ucp/card-payment-handler/2026-01-15/config.json",
  "available_instruments": [{
    "type": "card",
    "constraints": { "brands": ["visa", "master", "american_express"] },
    // business can vary the required fields without serving a different handler schema
    "required_fields": ["credential.cvc"] 
  }]
}

instrument_schema — same declaration plus a new URI, and Shopify hosts a separate schema document:

// https://shopify.dev/ucp/card-payment-handler/2026-01-15/instrument-requirements.json
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "allOf": [
    // need to pull this schema...
    { "$ref": "https://ucp.dev/schemas/shopping/types/card_payment_instrument.json" },
    {
      "properties": {
        "credential": {
          "allOf": [
            // ... and this one to get the complete picture of the type refinement
            { "$ref": "https://ucp.dev/schemas/shopping/types/card_credential.json" },
            // probably requires a different schema when using this handler definition 
            // where CVC isn't required or zip code IS required
            { "required": ["cvc"] }
          ]
        }
      }
    }
  ]
}

Again - I think either way could work and I'm still getting calibrated on what best serves the broader community but hopefully this helps flesh out a bit more of the motivation for the required_fields approach. LMK what you think.

Enable payment handlers to declare which optional fields they require
for payment processing via a new `required_fields` property on
`available_payment_instrument.json`. Uses dot notation for nested
properties (e.g., `credential.cvc`, `billing_address.postal_code`).

This eliminates guesswork for agentic platforms — they can collect
exactly the right fields before attempting tokenization, avoiding
trial-and-error retries and unnecessary over-collection of buyer data.
@jamesandersen jamesandersen force-pushed the feat/required-fields-available-instruments branch from de0b8e6 to ba16c3e Compare March 31, 2026 19:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

TC review Ready for TC review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants