diff --git a/.cspell/custom-words.txt b/.cspell/custom-words.txt index ce73c361..55507e83 100644 --- a/.cspell/custom-words.txt +++ b/.cspell/custom-words.txt @@ -19,6 +19,7 @@ celerybeat classpath CLASSPATH CMSPI +codegen cmwallet cncf Cobo @@ -35,6 +36,7 @@ davecgh dcql Dcql DCQL +datamodel deviceauth Dfile dmypy @@ -149,6 +151,7 @@ ropeproject RPCURL Rulebook screenreaders +sepolia setlocal sharedpref Shopcider @@ -169,6 +172,7 @@ Truelayer Trulioo udpa unmarshal +usdc viewmodel vulnz Wallex diff --git a/code/sdk/python/ap2/sdk/generated/types/payment_instrument.py b/code/sdk/python/ap2/sdk/generated/types/payment_instrument.py index 81ff9568..df146b7a 100644 --- a/code/sdk/python/ap2/sdk/generated/types/payment_instrument.py +++ b/code/sdk/python/ap2/sdk/generated/types/payment_instrument.py @@ -1,10 +1,10 @@ # 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): @@ -12,6 +12,9 @@ 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' diff --git a/code/sdk/python/ap2/tests/payment_instrument_tests.py b/code/sdk/python/ap2/tests/payment_instrument_tests.py new file mode 100644 index 00000000..b63a4b35 --- /dev/null +++ b/code/sdk/python/ap2/tests/payment_instrument_tests.py @@ -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'] diff --git a/code/sdk/schemas/ap2/types/payment_instrument.json b/code/sdk/schemas/ap2/types/payment_instrument.json index 99857b37..ac885556 100644 --- a/code/sdk/schemas/ap2/types/payment_instrument.json +++ b/code/sdk/schemas/ap2/types/payment_instrument.json @@ -4,6 +4,7 @@ "title": "Payment Instrument", "description": "Instrument used for payment.", "type": "object", + "additionalProperties": true, "properties": { "id": { "type": "string", @@ -22,4 +23,4 @@ "id", "type" ] -} \ No newline at end of file +}