agentscore-commerce 1.3.2__tar.gz → 1.3.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (128) hide show
  1. {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.3}/PKG-INFO +1 -1
  2. {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.3}/agentscore_commerce/payment/x402_settle.py +49 -4
  3. {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.3}/pyproject.toml +1 -1
  4. {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.3}/tests/test_lifted_helpers.py +214 -0
  5. {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.3}/uv.lock +1 -1
  6. {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.3}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  7. {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.3}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  8. {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.3}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  9. {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.3}/.github/dependabot.yml +0 -0
  10. {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.3}/.github/workflows/ci.yml +0 -0
  11. {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.3}/.github/workflows/publish.yml +0 -0
  12. {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.3}/.github/workflows/security.yml +0 -0
  13. {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.3}/.gitignore +0 -0
  14. {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.3}/CLAUDE.md +0 -0
  15. {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.3}/CODE_OF_CONDUCT.md +0 -0
  16. {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.3}/CONTRIBUTING.md +0 -0
  17. {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.3}/LICENSE +0 -0
  18. {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.3}/README.md +0 -0
  19. {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.3}/SECURITY.md +0 -0
  20. {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.3}/agentscore_commerce/__init__.py +0 -0
  21. {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.3}/agentscore_commerce/api/__init__.py +0 -0
  22. {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.3}/agentscore_commerce/challenge/__init__.py +0 -0
  23. {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.3}/agentscore_commerce/challenge/accepted_methods.py +0 -0
  24. {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.3}/agentscore_commerce/challenge/agent_instructions.py +0 -0
  25. {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.3}/agentscore_commerce/challenge/agent_memory.py +0 -0
  26. {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.3}/agentscore_commerce/challenge/body.py +0 -0
  27. {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.3}/agentscore_commerce/challenge/how_to_pay.py +0 -0
  28. {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.3}/agentscore_commerce/challenge/identity.py +0 -0
  29. {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.3}/agentscore_commerce/challenge/order_receipt.py +0 -0
  30. {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.3}/agentscore_commerce/challenge/pricing.py +0 -0
  31. {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.3}/agentscore_commerce/challenge/respond_402.py +0 -0
  32. {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.3}/agentscore_commerce/challenge/validation_error.py +0 -0
  33. {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.3}/agentscore_commerce/discovery/__init__.py +0 -0
  34. {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.3}/agentscore_commerce/discovery/bazaar.py +0 -0
  35. {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.3}/agentscore_commerce/discovery/llms_txt.py +0 -0
  36. {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.3}/agentscore_commerce/discovery/openapi.py +0 -0
  37. {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.3}/agentscore_commerce/discovery/probe.py +0 -0
  38. {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.3}/agentscore_commerce/discovery/robots_tag.py +0 -0
  39. {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.3}/agentscore_commerce/discovery/skill_md.py +0 -0
  40. {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.3}/agentscore_commerce/discovery/well_known_mpp.py +0 -0
  41. {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.3}/agentscore_commerce/discovery/well_known_x402.py +0 -0
  42. {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.3}/agentscore_commerce/identity/__init__.py +0 -0
  43. {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.3}/agentscore_commerce/identity/_denial.py +0 -0
  44. {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.3}/agentscore_commerce/identity/_response.py +0 -0
  45. {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.3}/agentscore_commerce/identity/a2a.py +0 -0
  46. {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.3}/agentscore_commerce/identity/address.py +0 -0
  47. {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.3}/agentscore_commerce/identity/aiohttp.py +0 -0
  48. {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.3}/agentscore_commerce/identity/cache.py +0 -0
  49. {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.3}/agentscore_commerce/identity/client.py +0 -0
  50. {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.3}/agentscore_commerce/identity/django.py +0 -0
  51. {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.3}/agentscore_commerce/identity/fastapi.py +0 -0
  52. {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.3}/agentscore_commerce/identity/flask.py +0 -0
  53. {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.3}/agentscore_commerce/identity/middleware.py +0 -0
  54. {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.3}/agentscore_commerce/identity/policy.py +0 -0
  55. {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.3}/agentscore_commerce/identity/py.typed +0 -0
  56. {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.3}/agentscore_commerce/identity/sanic.py +0 -0
  57. {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.3}/agentscore_commerce/identity/sessions.py +0 -0
  58. {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.3}/agentscore_commerce/identity/signer.py +0 -0
  59. {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.3}/agentscore_commerce/identity/types.py +0 -0
  60. {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.3}/agentscore_commerce/identity/ucp.py +0 -0
  61. {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.3}/agentscore_commerce/payment/__init__.py +0 -0
  62. {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.3}/agentscore_commerce/payment/directive.py +0 -0
  63. {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.3}/agentscore_commerce/payment/dispatch.py +0 -0
  64. {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.3}/agentscore_commerce/payment/headers.py +0 -0
  65. {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.3}/agentscore_commerce/payment/idempotency.py +0 -0
  66. {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.3}/agentscore_commerce/payment/mppx_server.py +0 -0
  67. {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.3}/agentscore_commerce/payment/networks.py +0 -0
  68. {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.3}/agentscore_commerce/payment/rails.py +0 -0
  69. {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.3}/agentscore_commerce/payment/settlement_override.py +0 -0
  70. {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.3}/agentscore_commerce/payment/signer.py +0 -0
  71. {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.3}/agentscore_commerce/payment/usdc.py +0 -0
  72. {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.3}/agentscore_commerce/payment/wwwauthenticate.py +0 -0
  73. {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.3}/agentscore_commerce/payment/x402.py +0 -0
  74. {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.3}/agentscore_commerce/payment/x402_server.py +0 -0
  75. {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.3}/agentscore_commerce/payment/x402_validation.py +0 -0
  76. {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.3}/agentscore_commerce/stripe_multichain/__init__.py +0 -0
  77. {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.3}/agentscore_commerce/stripe_multichain/mppx_stripe.py +0 -0
  78. {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.3}/agentscore_commerce/stripe_multichain/payment_intent.py +0 -0
  79. {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.3}/agentscore_commerce/stripe_multichain/pi_cache.py +0 -0
  80. {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.3}/agentscore_commerce/stripe_multichain/simulate_deposit.py +0 -0
  81. {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.3}/examples/README.md +0 -0
  82. {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.3}/examples/api_provider.py +0 -0
  83. {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.3}/examples/compliance_merchant.py +0 -0
  84. {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.3}/examples/identity_only.py +0 -0
  85. {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.3}/examples/multi_rail_merchant.py +0 -0
  86. {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.3}/examples/per_product_policy_merchant.py +0 -0
  87. {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.3}/examples/stripe_multichain_merchant.py +0 -0
  88. {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.3}/examples/variable_cost_merchant.py +0 -0
  89. {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.3}/lefthook.yml +0 -0
  90. {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.3}/ruff.toml +0 -0
  91. {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.3}/tests/__init__.py +0 -0
  92. {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.3}/tests/conftest.py +0 -0
  93. {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.3}/tests/test_a2a.py +0 -0
  94. {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.3}/tests/test_address.py +0 -0
  95. {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.3}/tests/test_agent_memory_emitter.py +0 -0
  96. {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.3}/tests/test_aiohttp.py +0 -0
  97. {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.3}/tests/test_api_reexport.py +0 -0
  98. {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.3}/tests/test_cache.py +0 -0
  99. {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.3}/tests/test_challenge.py +0 -0
  100. {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.3}/tests/test_client.py +0 -0
  101. {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.3}/tests/test_coverage_fillers.py +0 -0
  102. {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.3}/tests/test_denial.py +0 -0
  103. {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.3}/tests/test_discovery.py +0 -0
  104. {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.3}/tests/test_django.py +0 -0
  105. {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.3}/tests/test_fastapi.py +0 -0
  106. {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.3}/tests/test_flask.py +0 -0
  107. {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.3}/tests/test_gate_quota_info.py +0 -0
  108. {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.3}/tests/test_idempotency_helper.py +0 -0
  109. {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.3}/tests/test_integration.py +0 -0
  110. {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.3}/tests/test_middleware.py +0 -0
  111. {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.3}/tests/test_payment_directive.py +0 -0
  112. {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.3}/tests/test_payment_dispatch.py +0 -0
  113. {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.3}/tests/test_payment_headers.py +0 -0
  114. {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.3}/tests/test_payment_misc.py +0 -0
  115. {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.3}/tests/test_payment_servers.py +0 -0
  116. {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.3}/tests/test_payment_signer.py +0 -0
  117. {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.3}/tests/test_policy.py +0 -0
  118. {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.3}/tests/test_pricing.py +0 -0
  119. {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.3}/tests/test_response.py +0 -0
  120. {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.3}/tests/test_robots_tag.py +0 -0
  121. {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.3}/tests/test_sanic.py +0 -0
  122. {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.3}/tests/test_sessions.py +0 -0
  123. {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.3}/tests/test_signer_match.py +0 -0
  124. {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.3}/tests/test_skill_md.py +0 -0
  125. {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.3}/tests/test_stripe_multichain.py +0 -0
  126. {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.3}/tests/test_ucp.py +0 -0
  127. {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.3}/tests/test_validation_error.py +0 -0
  128. {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.3}/vulture_whitelist.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agentscore-commerce
3
- Version: 1.3.2
3
+ Version: 1.3.3
4
4
  Summary: Agent commerce SDK for Python — identity middleware (FastAPI, Flask, Django, AIOHTTP, Sanic, ASGI) + payment helpers + 402 builders + discovery + Stripe multichain. The full merchant-side toolkit for AgentScore-powered agent commerce.
5
5
  Project-URL: Homepage, https://agentscore.sh
6
6
  Project-URL: Repository, https://github.com/agentscore/python-commerce
@@ -220,10 +220,40 @@ def _coerce_resource_config(config: Any) -> Any:
220
220
  return config
221
221
 
222
222
 
223
+ def _coerce_payment_payload(payload: Any) -> Any:
224
+ """Best-effort dict → x402 ``PaymentPayload`` (v1 or v2) coercion.
225
+
226
+ ``verify_x402_request`` returns ``payload`` as a plain dict (the result of
227
+ ``json.loads(base64.b64decode(X-Payment))``), but x402 2.9's
228
+ ``server.verify_payment`` / ``server.settle_payment`` call ``payload.get_scheme()``
229
+ and other typed-model methods on it. Without coercion, the dict raises
230
+ ``AttributeError("'dict' object has no attribute 'get_scheme'")`` on the verify leg.
231
+
232
+ Routes by the ``x402Version`` field: ``1`` → ``PaymentPayloadV1`` (flat shape with
233
+ top-level ``scheme`` / ``network``); anything else → ``PaymentPayload`` (v2 shape
234
+ nested under ``accepted``). Falls back to the original dict on any failure so callers
235
+ that already pass typed instances or unusual shapes still flow through unchanged.
236
+ """
237
+ if not isinstance(payload, dict):
238
+ return payload
239
+ try:
240
+ from x402.schemas import PaymentPayload
241
+ from x402.schemas.v1 import PaymentPayloadV1
242
+ except ImportError:
243
+ return payload
244
+ version = payload.get("x402Version")
245
+ model = PaymentPayloadV1 if version == 1 else PaymentPayload
246
+ try:
247
+ return model.model_validate(payload)
248
+ except Exception:
249
+ return payload
250
+
251
+
223
252
  async def process_x402_settle(input: ProcessX402SettleInput) -> ProcessX402SettleResult:
224
253
  """Run the x402 verify→settle flow and return a tagged outcome."""
225
254
  server = input.x402_server
226
255
  resource_config = _coerce_resource_config(input.resource_config)
256
+ payload = _coerce_payment_payload(input.payload)
227
257
 
228
258
  try:
229
259
  built_requirements = server.build_payment_requirements(resource_config)
@@ -267,7 +297,7 @@ async def process_x402_settle(input: ProcessX402SettleInput) -> ProcessX402Settl
267
297
  # — not ``process_payment_request`` (a fictional method that earlier versions of this
268
298
  # helper called and only ever worked against test stubs).
269
299
  try:
270
- verify_result = await server.verify_payment(input.payload, matched_requirement)
300
+ verify_result = await server.verify_payment(payload, matched_requirement)
271
301
  except Exception as err:
272
302
  return ProcessX402SettleFailure(phase="facilitator_error", step="verify_payment", error=err)
273
303
 
@@ -288,11 +318,10 @@ async def process_x402_settle(input: ProcessX402SettleInput) -> ProcessX402Settl
288
318
  return ProcessX402SettleFailure(phase="verify_failed", verify_result=verify_result)
289
319
 
290
320
  try:
291
- settle_result = await server.settle_payment(input.payload, matched_requirement)
321
+ settle_result = await server.settle_payment(payload, matched_requirement)
292
322
  payment_response_header: str | None = None
293
323
  if settle_result is not None:
294
- payload_bytes = json.dumps(settle_result, separators=(",", ":")).encode()
295
- payment_response_header = base64.b64encode(payload_bytes).decode()
324
+ payment_response_header = base64.b64encode(_settle_result_to_json_bytes(settle_result)).decode()
296
325
  return ProcessX402SettleSuccess(
297
326
  matched_requirement=matched_requirement,
298
327
  settle_result=settle_result,
@@ -301,3 +330,19 @@ async def process_x402_settle(input: ProcessX402SettleInput) -> ProcessX402Settl
301
330
  )
302
331
  except Exception as err:
303
332
  return ProcessX402SettleFailure(phase="settle_failed", error=err, matched_requirement=matched_requirement)
333
+
334
+
335
+ def _settle_result_to_json_bytes(settle_result: Any) -> bytes:
336
+ """Serialize the settle result to a base64-friendly JSON byte string.
337
+
338
+ x402 2.9's ``settle_payment`` returns a Pydantic ``SettleResponse`` model that
339
+ ``json.dumps`` rejects with ``TypeError: Object of type SettleResponse is not
340
+ JSON serializable``. Use ``model_dump_json(by_alias=True)`` for Pydantic models
341
+ (so emitted keys match the wire shape — ``errorReason`` / ``errorMessage`` rather
342
+ than the snake_case attrs) and fall through to ``json.dumps`` for plain dicts
343
+ (used by older x402 / test stubs).
344
+ """
345
+ model_dump_json = getattr(settle_result, "model_dump_json", None)
346
+ if callable(model_dump_json):
347
+ return model_dump_json(by_alias=True).encode()
348
+ return json.dumps(settle_result, separators=(",", ":")).encode()
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "agentscore-commerce"
7
- version = "1.3.2"
7
+ version = "1.3.3"
8
8
  description = "Agent commerce SDK for Python — identity middleware (FastAPI, Flask, Django, AIOHTTP, Sanic, ASGI) + payment helpers + 402 builders + discovery + Stripe multichain. The full merchant-side toolkit for AgentScore-powered agent commerce."
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -534,6 +534,220 @@ async def test_process_x402_settle_passes_typed_resource_config_unchanged():
534
534
  assert captured["cfg"] is typed
535
535
 
536
536
 
537
+ @pytest.mark.asyncio
538
+ async def test_process_x402_settle_coerces_dict_payload_v2_to_typed_payment_payload():
539
+ """Same fix as resource_config: ``verify_x402_request`` returns ``payload`` as a
540
+ plain dict (the result of ``json.loads(base64.b64decode(X-Payment))``); x402 2.9's
541
+ ``server.verify_payment`` calls ``payload.get_scheme()`` and other typed-model methods
542
+ on it. Coerce dicts → ``PaymentPayload`` (or ``PaymentPayloadV1`` when ``x402Version=1``)
543
+ so the helper doesn't ``AttributeError`` at the verify leg.
544
+ """
545
+ captured: dict = {}
546
+
547
+ class _CapturingServer:
548
+ def build_payment_requirements(self, _cfg: object, _ext: object = None) -> list:
549
+ return [{"id": "req1"}]
550
+
551
+ async def verify_payment(self, payload: object, _req: object) -> dict:
552
+ captured["verify_payload"] = payload
553
+ return {"is_valid": True}
554
+
555
+ async def settle_payment(self, payload: object, _req: object) -> dict:
556
+ captured["settle_payload"] = payload
557
+ return {"tx_hash": "0xabc"}
558
+
559
+ payload_dict = {
560
+ "x402Version": 2,
561
+ "accepted": {
562
+ "scheme": "exact",
563
+ "network": "eip155:8453",
564
+ "amount": "100000",
565
+ "asset": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
566
+ "payTo": "0x000000000000000000000000000000000000dEaD",
567
+ "maxTimeoutSeconds": 300,
568
+ },
569
+ "payload": {
570
+ "authorization": {
571
+ "from": "0xeb2Ca790F72787c7e61bC6c861353a1e4ACDFCa5",
572
+ "to": "0x000000000000000000000000000000000000dEaD",
573
+ "value": "100000",
574
+ "validAfter": 0,
575
+ "validBefore": 9999999999,
576
+ "nonce": "0x" + "aa" * 32,
577
+ },
578
+ "signature": "0x" + "cc" * 65,
579
+ },
580
+ }
581
+ res = await process_x402_settle(
582
+ ProcessX402SettleInput(
583
+ x402_server=_CapturingServer(),
584
+ payload=payload_dict,
585
+ resource_config={
586
+ "scheme": "exact",
587
+ "network": "eip155:8453",
588
+ "price": "$0.10",
589
+ "payTo": "0x" + "00" * 19 + "dE" + "aD",
590
+ "maxTimeoutSeconds": 300,
591
+ },
592
+ resource_meta=_RESOURCE_META,
593
+ )
594
+ )
595
+ assert isinstance(res, ProcessX402SettleSuccess)
596
+ # Both verify and settle legs received the typed Pydantic model, not the raw dict.
597
+ verify_payload = captured["verify_payload"]
598
+ settle_payload = captured["settle_payload"]
599
+ assert type(verify_payload).__name__ == "PaymentPayload"
600
+ assert type(settle_payload).__name__ == "PaymentPayload"
601
+ # The typed model exposes the get_scheme() method that x402's facilitator calls.
602
+ assert hasattr(verify_payload, "get_scheme")
603
+ assert verify_payload.get_scheme() == "exact"
604
+
605
+
606
+ @pytest.mark.asyncio
607
+ async def test_process_x402_settle_serializes_pydantic_settle_result_to_payment_response_header():
608
+ """x402 2.9's ``settle_payment`` returns a Pydantic ``SettleResponse`` model;
609
+ plain ``json.dumps`` rejects it with ``TypeError``. The helper must call
610
+ ``model_dump_json(by_alias=True)`` so the X-Payment-Response header stays
611
+ base64'd JSON with the right wire keys (``errorReason`` not ``error_reason``).
612
+ """
613
+ from x402.schemas import SettleResponse
614
+
615
+ pydantic_settle = SettleResponse(
616
+ success=True,
617
+ transaction="0xabc",
618
+ network="eip155:8453",
619
+ payer="0x000000000000000000000000000000000000dEaD",
620
+ amount=None,
621
+ )
622
+
623
+ class _PydanticSettleServer:
624
+ def build_payment_requirements(self, _cfg: object, _ext: object = None) -> list:
625
+ return [{"id": "req1"}]
626
+
627
+ async def verify_payment(self, _payload: object, _req: object) -> dict:
628
+ return {"is_valid": True}
629
+
630
+ async def settle_payment(self, _payload: object, _req: object) -> SettleResponse:
631
+ return pydantic_settle
632
+
633
+ res = await process_x402_settle(
634
+ ProcessX402SettleInput(
635
+ x402_server=_PydanticSettleServer(),
636
+ payload={},
637
+ resource_config={
638
+ "scheme": "exact",
639
+ "network": "eip155:8453",
640
+ "price": "$0.10",
641
+ "payTo": "0x" + "00" * 19 + "dE" + "aD",
642
+ "maxTimeoutSeconds": 300,
643
+ },
644
+ resource_meta=_RESOURCE_META,
645
+ )
646
+ )
647
+ assert isinstance(res, ProcessX402SettleSuccess)
648
+ assert res.payment_response_header is not None
649
+ decoded = json.loads(base64.b64decode(res.payment_response_header).decode())
650
+ assert decoded["success"] is True
651
+ assert decoded["transaction"] == "0xabc"
652
+ assert decoded["network"] == "eip155:8453"
653
+ # by_alias=True: wire shape uses errorReason / errorMessage (camelCase) — not snake_case.
654
+ assert "errorReason" in decoded
655
+
656
+
657
+ @pytest.mark.asyncio
658
+ async def test_process_x402_settle_serializes_dict_settle_result_for_legacy_stubs():
659
+ """Plain-dict settle results (from older x402 / test stubs) still serialize."""
660
+
661
+ class _DictSettleServer:
662
+ def build_payment_requirements(self, _cfg: object, _ext: object = None) -> list:
663
+ return [{"id": "req1"}]
664
+
665
+ async def verify_payment(self, _payload: object, _req: object) -> dict:
666
+ return {"is_valid": True}
667
+
668
+ async def settle_payment(self, _payload: object, _req: object) -> dict:
669
+ return {"success": True, "transaction": "0xdef"}
670
+
671
+ res = await process_x402_settle(
672
+ ProcessX402SettleInput(
673
+ x402_server=_DictSettleServer(),
674
+ payload={},
675
+ resource_config={
676
+ "scheme": "exact",
677
+ "network": "eip155:8453",
678
+ "price": "$0.10",
679
+ "payTo": "0x" + "00" * 19 + "dE" + "aD",
680
+ "maxTimeoutSeconds": 300,
681
+ },
682
+ resource_meta=_RESOURCE_META,
683
+ )
684
+ )
685
+ assert isinstance(res, ProcessX402SettleSuccess)
686
+ assert res.payment_response_header is not None
687
+ decoded = json.loads(base64.b64decode(res.payment_response_header).decode())
688
+ assert decoded == {"success": True, "transaction": "0xdef"}
689
+
690
+
691
+ @pytest.mark.asyncio
692
+ async def test_process_x402_settle_passes_typed_payment_payload_unchanged():
693
+ """If the caller already provides a typed PaymentPayload, pass it through unchanged."""
694
+ from x402.schemas import PaymentPayload
695
+
696
+ typed = PaymentPayload.model_validate(
697
+ {
698
+ "x402Version": 2,
699
+ "accepted": {
700
+ "scheme": "exact",
701
+ "network": "eip155:8453",
702
+ "amount": "100000",
703
+ "asset": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
704
+ "payTo": "0x000000000000000000000000000000000000dEaD",
705
+ "maxTimeoutSeconds": 300,
706
+ },
707
+ "payload": {
708
+ "authorization": {
709
+ "from": "0xeb2Ca790F72787c7e61bC6c861353a1e4ACDFCa5",
710
+ "to": "0x000000000000000000000000000000000000dEaD",
711
+ "value": "100000",
712
+ "validAfter": 0,
713
+ "validBefore": 9999999999,
714
+ "nonce": "0x" + "aa" * 32,
715
+ },
716
+ "signature": "0x" + "cc" * 65,
717
+ },
718
+ }
719
+ )
720
+ captured: dict = {}
721
+
722
+ class _CapturingServer:
723
+ def build_payment_requirements(self, _cfg: object, _ext: object = None) -> list:
724
+ return [{"id": "req1"}]
725
+
726
+ async def verify_payment(self, payload: object, _req: object) -> dict:
727
+ captured["payload"] = payload
728
+ return {"is_valid": True}
729
+
730
+ async def settle_payment(self, _payload: object, _req: object) -> dict:
731
+ return {"tx_hash": "0xabc"}
732
+
733
+ res = await process_x402_settle(
734
+ ProcessX402SettleInput(
735
+ x402_server=_CapturingServer(),
736
+ payload=typed,
737
+ resource_config={
738
+ "scheme": "exact",
739
+ "network": "eip155:8453",
740
+ "price": "$0.10",
741
+ "payTo": "0x" + "00" * 19 + "dE" + "aD",
742
+ "maxTimeoutSeconds": 300,
743
+ },
744
+ resource_meta=_RESOURCE_META,
745
+ )
746
+ )
747
+ assert isinstance(res, ProcessX402SettleSuccess)
748
+ assert captured["payload"] is typed
749
+
750
+
537
751
  # ─────────────────────────────────────────────────────────────────────────────
538
752
  # process_x402_settle: facilitator_error wrap
539
753
  # ─────────────────────────────────────────────────────────────────────────────
@@ -10,7 +10,7 @@ resolution-markers = [
10
10
 
11
11
  [[package]]
12
12
  name = "agentscore-commerce"
13
- version = "1.3.2"
13
+ version = "1.3.3"
14
14
  source = { editable = "." }
15
15
  dependencies = [
16
16
  { name = "agentscore-py" },