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
4 changes: 4 additions & 0 deletions .cspell/custom-words.txt
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ celerybeat
classpath
CLASSPATH
CMSPI
codegen
cmwallet
cncf
Cobo
Expand All @@ -35,6 +36,7 @@ davecgh
dcql
Dcql
DCQL
datamodel
deviceauth
Dfile
dmypy
Expand Down Expand Up @@ -149,6 +151,7 @@ ropeproject
RPCURL
Rulebook
screenreaders
sepolia
setlocal
sharedpref
Shopcider
Expand All @@ -169,6 +172,7 @@ Truelayer
Trulioo
udpa
unmarshal
usdc
viewmodel
vulnz
Wallex
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,20 @@
# generated by datamodel-codegen:
# filename: types/payment_instrument.json
# timestamp: 2026-04-28T00:39:38+00:00
# timestamp: 2026-08-13T10:40:58+00:00

from __future__ import annotations

from pydantic import BaseModel, Field
from pydantic import BaseModel, ConfigDict, Field


class PaymentInstrument(BaseModel):
"""
Instrument used for payment.
"""

model_config = ConfigDict(
extra='allow',
)
id: str = Field(..., description='unique identifier for this instrument')
type: str = Field(
..., description='unique string identifying this category of instrument'
Expand Down
74 changes: 74 additions & 0 deletions code/sdk/python/ap2/tests/payment_instrument_tests.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
"""Tests for extensible payment instrument fields."""

from ap2.sdk.generated.open_payment_mandate import (
AllowedPaymentInstruments,
OpenPaymentMandate,
)
from ap2.sdk.generated.types.payment_instrument import PaymentInstrument
from ap2.sdk.payment_mandate_chain import PaymentMandateChain
from ap2.tests.conftest import make_cnf, sample_payment_mandate


def test_type_specific_fields_survive_signed_payment_mandate_roundtrip(
user_key,
user_public_key,
agent_key,
holder,
):
"""Type-specific instrument fields remain signed and typed after verify."""
instrument = PaymentInstrument(
id='x402-base-sepolia-usdc',
type='x402',
payee_address='0x1111111111111111111111111111111111111111',
facilitator='https://facilitator.example',
)
expected_extension = {
'payee_address': '0x1111111111111111111111111111111111111111',
'facilitator': 'https://facilitator.example',
}

instrument_dump = instrument.model_dump(exclude_none=True)
assert {
key: instrument_dump[key] for key in expected_extension
} == expected_extension

closed_mandate = sample_payment_mandate(payment_instrument=instrument)
nested_dump = closed_mandate.model_dump(exclude_none=True)
assert {
key: nested_dump['payment_instrument'][key]
for key in expected_extension
} == expected_extension

open_token = holder.create(
payloads=[
OpenPaymentMandate(
constraints=[
AllowedPaymentInstruments(allowed=[instrument]),
],
cnf=make_cnf(agent_key),
)
],
issuer_key=user_key,
)
signed_chain = holder.present(
holder_key=agent_key,
mandate_token=open_token,
payloads=[closed_mandate],
aud='merchant',
nonce='merchant-nonce',
)

verified_payloads = holder.verify(
token=signed_chain,
key_or_provider=lambda _token: user_public_key,
)
parsed_chain = PaymentMandateChain.parse(verified_payloads)

assert parsed_chain.verify() == []
parsed_instrument = parsed_chain.closed_mandate.payment_instrument
assert {
key: parsed_instrument.model_dump(exclude_none=True)[key]
for key in expected_extension
} == expected_extension
assert parsed_instrument.payee_address == expected_extension['payee_address']
assert parsed_instrument.facilitator == expected_extension['facilitator']
3 changes: 2 additions & 1 deletion code/sdk/schemas/ap2/types/payment_instrument.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"title": "Payment Instrument",
"description": "Instrument used for payment.",
"type": "object",
"additionalProperties": true,
"properties": {
"id": {
"type": "string",
Expand All @@ -22,4 +23,4 @@
"id",
"type"
]
}
}
Loading