agentscore-commerce 2.5.2__tar.gz → 2.5.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.
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/CLAUDE.md +1 -1
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/PKG-INFO +1 -1
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/agentscore_commerce/__init__.py +9 -1
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/agentscore_commerce/checkout.py +48 -1
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/agentscore_commerce/payment/__init__.py +4 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/agentscore_commerce/payment/payment_header.py +65 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/agentscore_commerce/payment/zero_settle.py +13 -4
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/agentscore_commerce/quote_cache.py +84 -34
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/pyproject.toml +1 -1
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/tests/test_aip_checkout.py +4 -4
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/tests/test_checkout.py +70 -10
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/tests/test_checkout_coverage_gaps.py +3 -1
- agentscore_commerce-2.5.3/tests/test_credential_precheck.py +135 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/tests/test_dispatch.py +8 -2
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/tests/test_quote_cache.py +44 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/tests/test_seamless_helpers.py +50 -14
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/uv.lock +1 -1
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/.github/dependabot.yml +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/.github/workflows/ci.yml +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/.github/workflows/publish.yml +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/.github/workflows/security.yml +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/.gitignore +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/CODE_OF_CONDUCT.md +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/CONTRIBUTING.md +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/LICENSE +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/README.md +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/SECURITY.md +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/agentscore_commerce/_headers.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/agentscore_commerce/_mppx_receipt.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/agentscore_commerce/_redis.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/agentscore_commerce/_warnings.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/agentscore_commerce/aip/__init__.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/agentscore_commerce/aip/gate.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/agentscore_commerce/aip/http_signature.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/agentscore_commerce/aip/jwks.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/agentscore_commerce/aip/request.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/agentscore_commerce/aip/types.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/agentscore_commerce/aip/verify.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/agentscore_commerce/api/__init__.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/agentscore_commerce/challenge/__init__.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/agentscore_commerce/challenge/accepted_methods.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/agentscore_commerce/challenge/agent_instructions.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/agentscore_commerce/challenge/agent_memory.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/agentscore_commerce/challenge/body.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/agentscore_commerce/challenge/how_to_pay.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/agentscore_commerce/challenge/identity.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/agentscore_commerce/challenge/pricing.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/agentscore_commerce/challenge/receipt.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/agentscore_commerce/challenge/respond_402.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/agentscore_commerce/challenge/validation_error.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/agentscore_commerce/checkout_compute_first.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/agentscore_commerce/checkout_hooks.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/agentscore_commerce/discovery/__init__.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/agentscore_commerce/discovery/agentscore_content.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/agentscore_commerce/discovery/bazaar.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/agentscore_commerce/discovery/llms_txt.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/agentscore_commerce/discovery/openapi.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/agentscore_commerce/discovery/probe.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/agentscore_commerce/discovery/redemption_md.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/agentscore_commerce/discovery/robots_tag.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/agentscore_commerce/discovery/skill_md.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/agentscore_commerce/discovery/well_known.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/agentscore_commerce/discovery/well_known_mpp.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/agentscore_commerce/discovery/well_known_x402.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/agentscore_commerce/errors.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/agentscore_commerce/forwarded_proto.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/agentscore_commerce/identity/__init__.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/agentscore_commerce/identity/_denial.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/agentscore_commerce/identity/_response.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/agentscore_commerce/identity/a2a.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/agentscore_commerce/identity/address.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/agentscore_commerce/identity/aiohttp.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/agentscore_commerce/identity/cache.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/agentscore_commerce/identity/core.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/agentscore_commerce/identity/default_denied.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/agentscore_commerce/identity/django.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/agentscore_commerce/identity/fastapi.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/agentscore_commerce/identity/flask.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/agentscore_commerce/identity/middleware.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/agentscore_commerce/identity/policy.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/agentscore_commerce/identity/py.typed +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/agentscore_commerce/identity/sanic.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/agentscore_commerce/identity/sessions.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/agentscore_commerce/identity/signer.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/agentscore_commerce/identity/tokens.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/agentscore_commerce/identity/types.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/agentscore_commerce/identity/ucp.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/agentscore_commerce/identity/ucp_jwks.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/agentscore_commerce/middleware/__init__.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/agentscore_commerce/middleware/_core.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/agentscore_commerce/middleware/aiohttp.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/agentscore_commerce/middleware/asgi.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/agentscore_commerce/middleware/django.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/agentscore_commerce/middleware/fastapi.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/agentscore_commerce/middleware/flask.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/agentscore_commerce/middleware/sanic.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/agentscore_commerce/payment/amounts.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/agentscore_commerce/payment/compose_rails.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/agentscore_commerce/payment/constants.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/agentscore_commerce/payment/default_rails.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/agentscore_commerce/payment/directive.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/agentscore_commerce/payment/dispatch.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/agentscore_commerce/payment/headers.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/agentscore_commerce/payment/idempotency.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/agentscore_commerce/payment/lazy.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/agentscore_commerce/payment/mppx_failures.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/agentscore_commerce/payment/mppx_server.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/agentscore_commerce/payment/network_kind.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/agentscore_commerce/payment/networks.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/agentscore_commerce/payment/rail_spec.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/agentscore_commerce/payment/rails.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/agentscore_commerce/payment/settlement_override.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/agentscore_commerce/payment/signer.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/agentscore_commerce/payment/solana.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/agentscore_commerce/payment/usdc.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/agentscore_commerce/payment/wwwauthenticate.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/agentscore_commerce/payment/x402.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/agentscore_commerce/payment/x402_server.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/agentscore_commerce/payment/x402_settle.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/agentscore_commerce/payment/x402_validation.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/agentscore_commerce/py.typed +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/agentscore_commerce/stripe_multichain/__init__.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/agentscore_commerce/stripe_multichain/mppx_stripe.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/agentscore_commerce/stripe_multichain/pay_to_address.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/agentscore_commerce/stripe_multichain/payment_intent.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/agentscore_commerce/stripe_multichain/pi_cache.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/agentscore_commerce/stripe_multichain/simulate_deposit.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/agentscore_commerce/stripe_multichain/simulate_dispatch.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/examples/README.md +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/examples/api_provider.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/examples/compliance_merchant.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/examples/compute_first_merchant.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/examples/identity_only.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/examples/multi_rail_merchant.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/examples/per_product_policy_merchant.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/examples/signed_ucp_merchant.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/examples/stripe_multichain_merchant.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/lefthook.yml +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/osv-scanner.toml +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/ruff.toml +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/scripts/regenerate_cross_lang_fixtures.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/tests/__init__.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/tests/conftest.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/tests/fixtures/cross-lang/node-agentscore-gate-blocked.json +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/tests/fixtures/cross-lang/node-agentscore-gate-full.json +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/tests/fixtures/cross-lang/node-capability.json +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/tests/fixtures/cross-lang/node-emoji-keys.json +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/tests/fixtures/cross-lang/node-es256-rails.json +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/tests/fixtures/cross-lang/node-extras-int.json +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/tests/fixtures/cross-lang/node-int-boundary.json +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/tests/fixtures/cross-lang/node-minimal.json +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/tests/fixtures/cross-lang/node-multikey.json +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/tests/fixtures/cross-lang/node-unicode.json +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/tests/fixtures/cross-lang/py-agentscore-gate-blocked.json +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/tests/fixtures/cross-lang/py-agentscore-gate-full.json +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/tests/fixtures/cross-lang/py-capability.json +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/tests/fixtures/cross-lang/py-emoji-keys.json +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/tests/fixtures/cross-lang/py-es256-rails.json +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/tests/fixtures/cross-lang/py-extras-int.json +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/tests/fixtures/cross-lang/py-int-boundary.json +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/tests/fixtures/cross-lang/py-minimal.json +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/tests/fixtures/cross-lang/py-multikey.json +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/tests/fixtures/cross-lang/py-unicode.json +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/tests/test_a2a.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/tests/test_a2a_jws_roundtrip.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/tests/test_address.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/tests/test_agent_memory_emitter.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/tests/test_aiohttp.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/tests/test_aip_adapters.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/tests/test_aip_agent_memory.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/tests/test_aip_gate.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/tests/test_aip_http_signature.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/tests/test_aip_jwks.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/tests/test_aip_presentation.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/tests/test_aip_request.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/tests/test_aip_types.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/tests/test_aip_verify.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/tests/test_amounts.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/tests/test_api_reexport.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/tests/test_cache.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/tests/test_challenge.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/tests/test_checkout_compute_first.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/tests/test_checkout_compute_first_adapters.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/tests/test_checkout_compute_first_ofac.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/tests/test_checkout_compute_first_settle.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/tests/test_checkout_signer_match.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/tests/test_checkout_wallet_ofac_default.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/tests/test_classify_orchestration_error.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/tests/test_compose_rails.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/tests/test_core.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/tests/test_coverage_fillers.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/tests/test_default_denied.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/tests/test_default_rails.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/tests/test_default_read_only_on_denied.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/tests/test_denial.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/tests/test_discovery.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/tests/test_django.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/tests/test_extract_owner_scope.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/tests/test_fastapi.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/tests/test_flask.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/tests/test_forwarded_proto.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/tests/test_gate_quota_info.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/tests/test_get_signer_verdict.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/tests/test_idempotency_helper.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/tests/test_integration.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/tests/test_internal_helpers.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/tests/test_lifted_helpers.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/tests/test_load_ucp_signing_key_from_env.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/tests/test_middleware.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/tests/test_mppx_failures.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/tests/test_mppx_receipt.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/tests/test_network_kind.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/tests/test_pay_to_address.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/tests/test_payment_directive.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/tests/test_payment_dispatch.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/tests/test_payment_handlers.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/tests/test_payment_header.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/tests/test_payment_headers.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/tests/test_payment_misc.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/tests/test_payment_servers.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/tests/test_payment_signer.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/tests/test_policy.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/tests/test_pricing.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/tests/test_public_surface.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/tests/test_quote_cache_redis.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/tests/test_rail_spec.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/tests/test_rate_limit.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/tests/test_redis_internal.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/tests/test_response.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/tests/test_robots_tag.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/tests/test_sanic.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/tests/test_sessions.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/tests/test_signer_match.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/tests/test_signer_verdict_request_scoped.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/tests/test_simulate_dispatch.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/tests/test_skill_md.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/tests/test_solana.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/tests/test_solana_mpp_rail_spec_post_init.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/tests/test_stripe_multichain.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/tests/test_tokens.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/tests/test_ucp.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/tests/test_ucp_cross_lang.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/tests/test_ucp_jwks.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/tests/test_validation_error.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/tests/test_x402_settle_extra.py +0 -0
- {agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/tests/test_zero_settle.py +0 -0
|
@@ -8,7 +8,7 @@ Every helper is extracted from a real consumer, not speculated.
|
|
|
8
8
|
|
|
9
9
|
| Submodule | What it is |
|
|
10
10
|
|---|---|
|
|
11
|
-
| `agentscore_commerce` (top-level) | `Checkout` orchestrator (the 2.0 high-level surface): one config object + hooks (pre_validate, compute_pricing, mint_recipients, compose_mppx, on_settled, gate), auto-derived x402+pympp servers, per-framework adapters `handle_fastapi`/`handle_flask`/`handle_django`/`handle_aiohttp`/`handle_sanic`, signed UCP routes via `mount_ucp_routes_{fastapi,flask,django,aiohttp,sanic}`, optional `discovery_probe` config for x402-crawler auto-routing. Plus `compute_first_checkout` — variable-cost pay-per-result helper (compute-first + exact-x402). Scope is exact-mode rails only (x402-exact Base, tempo/charge, solana/charge, Stripe SPT); does NOT use x402-upto (Permit2) or Settlement-Overrides — variable cost is captured by running the work pre-settle and emitting a 402 at the exact computed price. `create_quote_cache` — content-hash quote cache used by the compute-first helper (in-memory by default; pass `redis_url` for distributed deployments). `create_default_on_denied` — canonical `on_denied(reason)` factory matching `Checkout`'s gate hook (handles `wallet_signer_mismatch`, `wallet_not_trusted` unfixable fallback, `payment_required`, `token_expired`/`invalid_credential`/`api_error`); merchants pass `merchant_name` + `support_email` and override `wallet_not_trusted_message` / `payment_required_message` / `support_context` for vendor-specific copy. `has_payment_header` — discriminator that splits discovery legs (no payment credential → 402) from settle legs (`payment-signature` / `x-payment` / `Authorization: Payment <jwt>`); `has_x402_header` / `has_mppx_header` — granular dispatch helpers (x402 vs MPP credential present) for routes that branch on rail. `default_read_only_on_denied(reason)` — canonical `on_denied` for read-only resource gates (`GET /orders/:id`): collapses every denial to 401 `unauthorized` + `Cache-Control: no-store` while still spreading `denial_reason_to_body` so `agent_instructions` / `verify_url` ride through. Returns a `DefaultOnDeniedResult(body, status, headers)` dataclass; FastAPI / Flask / aiohttp / Sanic `on_denied` callbacks accept an optional 3-tuple `(body, status, headers)` — convert with `lambda req, reason: (r := default_read_only_on_denied(reason), (r.body, r.status, r.headers or {}))[1]` or a named wrapper. Django + ASGI middleware adapters return Response objects directly; construct `JsonResponse(r.body, status=r.status, headers=r.headers)` / `JSONResponse(content=r.body, status_code=r.status, headers=r.headers)`. `extract_owner_scope(headers) -> OwnerScope` — pull canonical owner identity from `X-Wallet-Address` / `X-Operator-Token` with safe token hashing; pair with a wallet-or-token-scoped resource query so plaintext tokens never leave the request. Plus factories: `pricing_result` (cents → typed `PricingResult`), `validation_response_{fastapi,flask,django,aiohttp,sanic}` (4xx envelope per framework) |
|
|
11
|
+
| `agentscore_commerce` (top-level) | `Checkout` orchestrator (the 2.0 high-level surface): one config object + hooks (pre_validate, compute_pricing, mint_recipients, compose_mppx, on_settled, gate), auto-derived x402+pympp servers, per-framework adapters `handle_fastapi`/`handle_flask`/`handle_django`/`handle_aiohttp`/`handle_sanic`, signed UCP routes via `mount_ucp_routes_{fastapi,flask,django,aiohttp,sanic}`, optional `discovery_probe` config for x402-crawler auto-routing. Plus `compute_first_checkout` — variable-cost pay-per-result helper (compute-first + exact-x402). Scope is exact-mode rails only (x402-exact Base, tempo/charge, solana/charge, Stripe SPT); does NOT use x402-upto (Permit2) or Settlement-Overrides — variable cost is captured by running the work pre-settle and emitting a 402 at the exact computed price. `create_quote_cache` — content-hash quote cache used by the compute-first helper (in-memory by default; pass `redis_url` for distributed deployments). `create_result_cache` — the neutral primitive under it: keyed JSON-value cache with the same body-hash key builder; use it to cache any probe-leg result a Checkout-class merchant replays on the settle leg (e.g. a paid upstream call made in `pre_validate`). `create_default_on_denied` — canonical `on_denied(reason)` factory matching `Checkout`'s gate hook (handles `wallet_signer_mismatch`, `wallet_not_trusted` unfixable fallback, `payment_required`, `token_expired`/`invalid_credential`/`api_error`); merchants pass `merchant_name` + `support_email` and override `wallet_not_trusted_message` / `payment_required_message` / `support_context` for vendor-specific copy. `has_payment_header` — discriminator that splits discovery legs (no payment credential → 402) from settle legs (`payment-signature` / `x-payment` / `Authorization: Payment <jwt>`); `has_x402_header` / `has_mppx_header` — granular dispatch helpers (x402 vs MPP credential present) for routes that branch on rail. `malformed_payment_credential` — wire-shape gate for payment credentials (not base64 JSON, not token-shaped → reject); `Checkout` runs it before any merchant hook by default (`credential_pre_check=False` opts out). `default_read_only_on_denied(reason)` — canonical `on_denied` for read-only resource gates (`GET /orders/:id`): collapses every denial to 401 `unauthorized` + `Cache-Control: no-store` while still spreading `denial_reason_to_body` so `agent_instructions` / `verify_url` ride through. Returns a `DefaultOnDeniedResult(body, status, headers)` dataclass; FastAPI / Flask / aiohttp / Sanic `on_denied` callbacks accept an optional 3-tuple `(body, status, headers)` — convert with `lambda req, reason: (r := default_read_only_on_denied(reason), (r.body, r.status, r.headers or {}))[1]` or a named wrapper. Django + ASGI middleware adapters return Response objects directly; construct `JsonResponse(r.body, status=r.status, headers=r.headers)` / `JSONResponse(content=r.body, status_code=r.status, headers=r.headers)`. `extract_owner_scope(headers) -> OwnerScope` — pull canonical owner identity from `X-Wallet-Address` / `X-Operator-Token` with safe token hashing; pair with a wallet-or-token-scoped resource query so plaintext tokens never leave the request. Plus factories: `pricing_result` (cents → typed `PricingResult`), `validation_response_{fastapi,flask,django,aiohttp,sanic}` (4xx envelope per framework) |
|
|
12
12
|
| `agentscore_commerce.identity.{fastapi,flask,django,aiohttp,sanic,middleware}` | Trust gate middleware (KYC, age, sanctions on both account name and signer wallet, jurisdiction). Each adapter exports a conditional variant that wraps the gate so it fires only on settle legs (anonymous discovery flows through and gets a 402 with all rails): FastAPI / ASGI expose `ConditionalAgentScoreGate`, Django exposes `ConditionalAgentScoreMiddleware`, Flask + Sanic expose `conditional_agentscore_gate(app, ...)`, aiohttp exposes `conditional_agentscore_gate_middleware(...)`. Adapters export ONLY framework-specific surface (gate classes / fns, accessors, `capture_wallet`); shared helpers like `has_payment_header` / `denial_reason_to_body` import from their canonical home (`agentscore_commerce.payment` and `agentscore_commerce.identity` respectively). The Flask / Sanic `agentscore_gate(app, ...)` function accepts an optional `condition=` callable for inline gating (`AgentScoreGate.__init__` does not). |
|
|
13
13
|
| `agentscore_commerce.identity.policy` | Per-product compliance helpers: `PolicyBlock`, `build_gate_from_policy`, `run_gate_with_enforcement`, `shipping_country_allowed`, `shipping_state_allowed`, `validate_shipping_against_policy` (one-call country+state validator that raises `CheckoutValidationError` with the canonical envelope on miss) |
|
|
14
14
|
| `agentscore_commerce.payment` | Networks/USDC/rails registries, paymentauth.org directive builders, `create_x402_server` (wraps `x402[evm]>=2.9` + `cdp-sdk` for `facilitator="coinbase"`; install via the `coinbase` extra), `build_x402_accepts_for_402` (build the 402's `accepts[]` from the registered scheme; derives the right `extra.name` per network), `build_default_checkout_rails(tempo=, x402_base=, solana_mpp=, stripe=)` (canonical 4-rail `rails` dict factory: merchants pass per-rail overrides instead of redeclaring the recipient sentinel + network/chain_id/token boilerplate. When a caller flips `network` without pinning `token` / `chain_id`, the underlying dataclass derives them from the network: Base Sepolia → Sepolia USDC + chain_id 84532, Solana devnet → devnet USDC mint. Explicit overrides always win. Solana's `network` field accepts both CAIP-2 (`solana:5eykt4UsFv8…` / `solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1`) AND the raw `@solana/mpp` form (`mainnet-beta` / `devnet` / `localnet`)), `build_mppx_compose_rails(amount_usd=, tempo_recipient=, solana_recipient=, ...)` (per-call intent factory replacing the hand-rolled `[("tempo/charge", {...}), ("solana/charge", {...}), ("stripe/charge", {...})]` list; auto-handles USD→atomic conversion for Solana; auto-drops the `stripe/charge` rail with a one-time `logging.warning` when `amount_usd < 0.50` since Stripe's fixed ~$0.30 fee makes sub-50-cent charges unprofitable — many Stripe accounts also reject PI creation below the floor with `amount_too_small`; sub-50-cent APIs pass `include_stripe=False` explicitly to silence the warning), `process_x402_settle` (verify+settle in one call), `create_mppx_server` (wraps `pympp[server,tempo,stripe]>=0.6`), `is_evm_network`/`is_solana_network` (CAIP-2 discriminators that hide the `startswith("eip155:")` / `startswith("solana:")` prefix matching), `has_payment_header` (settle-leg vs discovery-leg discriminator), `parse_did_pkh_address` (parses ``did:pkh:<family>:<chain>:<addr>`` into a `PaymentSigner`), dispatch-by-network, signer extraction, WWW-Authenticate header, Settlement-Overrides header |
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: agentscore-commerce
|
|
3
|
-
Version: 2.5.
|
|
3
|
+
Version: 2.5.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://www.agentscore.com
|
|
6
6
|
Project-URL: Repository, https://github.com/agentscore/python-commerce
|
|
@@ -145,7 +145,13 @@ from agentscore_commerce.payment import (
|
|
|
145
145
|
load_solana_fee_payer,
|
|
146
146
|
read_x402_payment_header,
|
|
147
147
|
)
|
|
148
|
-
from agentscore_commerce.quote_cache import
|
|
148
|
+
from agentscore_commerce.quote_cache import (
|
|
149
|
+
CachedQuote,
|
|
150
|
+
QuoteCache,
|
|
151
|
+
ResultCache,
|
|
152
|
+
create_quote_cache,
|
|
153
|
+
create_result_cache,
|
|
154
|
+
)
|
|
149
155
|
|
|
150
156
|
try:
|
|
151
157
|
__version__ = _pkg_version("agentscore-commerce")
|
|
@@ -208,6 +214,7 @@ __all__ = [
|
|
|
208
214
|
"PolicyResult",
|
|
209
215
|
"PricingResult",
|
|
210
216
|
"QuoteCache",
|
|
217
|
+
"ResultCache",
|
|
211
218
|
"SettleOutcome",
|
|
212
219
|
"SignerNetwork",
|
|
213
220
|
"SignerVerdict",
|
|
@@ -242,6 +249,7 @@ __all__ = [
|
|
|
242
249
|
"compute_first_checkout",
|
|
243
250
|
"create_default_on_denied",
|
|
244
251
|
"create_quote_cache",
|
|
252
|
+
"create_result_cache",
|
|
245
253
|
"default_read_only_on_denied",
|
|
246
254
|
"denial_reason_status",
|
|
247
255
|
"denial_reason_to_body",
|
|
@@ -90,7 +90,11 @@ from agentscore_commerce.errors import CheckoutValidationError
|
|
|
90
90
|
from agentscore_commerce.forwarded_proto import apply_forwarded_proto, read_forwarded_proto
|
|
91
91
|
from agentscore_commerce.payment.constants import STRIPE_MIN_CHARGE_USD
|
|
92
92
|
from agentscore_commerce.payment.mppx_failures import classify_mppx_failure
|
|
93
|
-
from agentscore_commerce.payment.payment_header import
|
|
93
|
+
from agentscore_commerce.payment.payment_header import (
|
|
94
|
+
has_mppx_header,
|
|
95
|
+
has_x402_header,
|
|
96
|
+
malformed_payment_credential,
|
|
97
|
+
)
|
|
94
98
|
from agentscore_commerce.payment.rail_spec import (
|
|
95
99
|
RecipientLike,
|
|
96
100
|
SolanaMppRailSpec,
|
|
@@ -752,6 +756,13 @@ class Checkout:
|
|
|
752
756
|
CDP facilitator and pympp's tempo intents both reject $0 settles outright;
|
|
753
757
|
this carve-out makes free-redemption flows work uniformly across rails.
|
|
754
758
|
Default ``False`` (every payment header attempts a real settle).
|
|
759
|
+
* ``credential_pre_check``; reject payment credentials that fail the cheap
|
|
760
|
+
wire-shape check (not base64 JSON, not a token-shaped value) BEFORE any
|
|
761
|
+
merchant hook runs, so junk headers never trigger ``pre_validate`` /
|
|
762
|
+
pricing / recipient minting / the gate's assess call. Shape only —
|
|
763
|
+
signature and payTo verification stay on the settle path. Default
|
|
764
|
+
``True``; set ``False`` for custom ``compose_mppx`` implementations that
|
|
765
|
+
accept non-standard credential encodings.
|
|
755
766
|
"""
|
|
756
767
|
|
|
757
768
|
def __init__(
|
|
@@ -776,6 +787,7 @@ class Checkout:
|
|
|
776
787
|
on_settled: OnSettledFn | None = None,
|
|
777
788
|
is_cached_address: IsCachedAddressFn | None = None,
|
|
778
789
|
zero_settle_carve_out: bool = False,
|
|
790
|
+
credential_pre_check: bool = True,
|
|
779
791
|
gate: CheckoutGateConfig | None = None,
|
|
780
792
|
discovery_extensions: dict[str, Any] | None = None,
|
|
781
793
|
resource_info: dict[str, Any] | None = None,
|
|
@@ -841,6 +853,7 @@ class Checkout:
|
|
|
841
853
|
self.on_settled = on_settled
|
|
842
854
|
self.is_cached_address = is_cached_address
|
|
843
855
|
self.zero_settle_carve_out = zero_settle_carve_out
|
|
856
|
+
self.credential_pre_check = credential_pre_check
|
|
844
857
|
self.gate = gate
|
|
845
858
|
# Lazily-built JWKS cache for AIP verification, shared across requests so issuer keys are
|
|
846
859
|
# fetched once and cached (per the verifier's hard 24h cap). Built on first AIT.
|
|
@@ -1048,6 +1061,40 @@ class Checkout:
|
|
|
1048
1061
|
settle_phase="discovery_probe",
|
|
1049
1062
|
)
|
|
1050
1063
|
|
|
1064
|
+
# Credential shape gate: runs BEFORE pre_validate / the identity gate /
|
|
1065
|
+
# pricing / recipient minting so a junk payment header cannot trigger
|
|
1066
|
+
# merchant hooks (which may do paid upstream work) or burn an assess
|
|
1067
|
+
# call. Shape only — real verification stays on the settle path, which
|
|
1068
|
+
# needs per-request state the hooks produce. Scoped to the credential
|
|
1069
|
+
# channels this Checkout actually dispatches on, so e.g. an x402 header
|
|
1070
|
+
# at a Tempo-only merchant keeps its current discovery-leg behavior.
|
|
1071
|
+
if self.credential_pre_check:
|
|
1072
|
+
malformed = malformed_payment_credential(request.headers)
|
|
1073
|
+
enforced = malformed is not None and (
|
|
1074
|
+
(self._x402_server_available() and self._x402_base_network is not None)
|
|
1075
|
+
if malformed.channel == "x402"
|
|
1076
|
+
else self.compose_mppx is not None
|
|
1077
|
+
)
|
|
1078
|
+
if enforced and malformed is not None:
|
|
1079
|
+
return CheckoutResult(
|
|
1080
|
+
status=400,
|
|
1081
|
+
body=build_validation_error(
|
|
1082
|
+
code="payment_proof_invalid",
|
|
1083
|
+
message=malformed.message,
|
|
1084
|
+
next_steps={
|
|
1085
|
+
"action": "regenerate_payment_credential",
|
|
1086
|
+
"user_message": (
|
|
1087
|
+
"The payment credential could not be decoded. "
|
|
1088
|
+
"Rebuild it from a fresh 402 challenge and retry."
|
|
1089
|
+
),
|
|
1090
|
+
},
|
|
1091
|
+
),
|
|
1092
|
+
headers={},
|
|
1093
|
+
reference_id=ctx.reference_id,
|
|
1094
|
+
settled=False,
|
|
1095
|
+
settle_phase="credential_malformed",
|
|
1096
|
+
)
|
|
1097
|
+
|
|
1051
1098
|
# Pre-validate (optional): resolve merchant-specific per-request state
|
|
1052
1099
|
# (product lookup, code resolution, shipping checks, ...). May raise
|
|
1053
1100
|
# CheckoutValidationError to short-circuit with a 4xx; otherwise return
|
{agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/agentscore_commerce/payment/__init__.py
RENAMED
|
@@ -21,9 +21,11 @@ from agentscore_commerce.payment.mppx_server import MppxRailSpec, create_mppx_se
|
|
|
21
21
|
from agentscore_commerce.payment.network_kind import is_evm_network, is_solana_network
|
|
22
22
|
from agentscore_commerce.payment.networks import NetworkFamily, network_family, networks
|
|
23
23
|
from agentscore_commerce.payment.payment_header import (
|
|
24
|
+
MalformedPaymentCredential,
|
|
24
25
|
has_mppx_header,
|
|
25
26
|
has_payment_header,
|
|
26
27
|
has_x402_header,
|
|
28
|
+
malformed_payment_credential,
|
|
27
29
|
)
|
|
28
30
|
from agentscore_commerce.payment.rail_spec import (
|
|
29
31
|
RecipientLike,
|
|
@@ -95,6 +97,7 @@ __all__ = [
|
|
|
95
97
|
"X402_SUPPORTED_BASE_NETWORKS",
|
|
96
98
|
"ClassifiedX402Error",
|
|
97
99
|
"CustomScheme",
|
|
100
|
+
"MalformedPaymentCredential",
|
|
98
101
|
"MppxRailSpec",
|
|
99
102
|
"NetworkFamily",
|
|
100
103
|
"PaymentHeadersRail",
|
|
@@ -148,6 +151,7 @@ __all__ = [
|
|
|
148
151
|
"lazy_x402_server",
|
|
149
152
|
"load_solana_fee_payer",
|
|
150
153
|
"lookup_rail",
|
|
154
|
+
"malformed_payment_credential",
|
|
151
155
|
"network_family",
|
|
152
156
|
"networks",
|
|
153
157
|
"parse_did_pkh_address",
|
|
@@ -15,7 +15,12 @@ Three credential channels are checked:
|
|
|
15
15
|
|
|
16
16
|
from __future__ import annotations
|
|
17
17
|
|
|
18
|
+
import base64
|
|
19
|
+
import binascii
|
|
20
|
+
import json
|
|
21
|
+
import re
|
|
18
22
|
from collections.abc import Mapping
|
|
23
|
+
from dataclasses import dataclass
|
|
19
24
|
from typing import Any
|
|
20
25
|
|
|
21
26
|
|
|
@@ -94,3 +99,63 @@ def has_mppx_header(request_or_headers: Any) -> bool:
|
|
|
94
99
|
headers = _unwrap_headers(request_or_headers)
|
|
95
100
|
auth = _read_header(headers, "authorization")
|
|
96
101
|
return bool(isinstance(auth, str) and auth.startswith("Payment "))
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
_JWT_SHAPE_RE = re.compile(r"^[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$")
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def _decodes_to_json_object(token: str) -> bool:
|
|
108
|
+
try:
|
|
109
|
+
padded = token + "=" * (-len(token) % 4)
|
|
110
|
+
try:
|
|
111
|
+
raw = base64.b64decode(padded, validate=False)
|
|
112
|
+
except (ValueError, binascii.Error):
|
|
113
|
+
raw = base64.urlsafe_b64decode(padded)
|
|
114
|
+
return isinstance(json.loads(raw.decode("utf-8")), dict)
|
|
115
|
+
except (ValueError, UnicodeDecodeError, binascii.Error):
|
|
116
|
+
return False
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
@dataclass(frozen=True)
|
|
120
|
+
class MalformedPaymentCredential:
|
|
121
|
+
"""Which credential channel carried the malformed value, and why."""
|
|
122
|
+
|
|
123
|
+
channel: str # "x402" | "mpp"
|
|
124
|
+
message: str
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def malformed_payment_credential(request_or_headers: Any) -> MalformedPaymentCredential | None:
|
|
128
|
+
"""Wire-shape gate for payment credentials, cheap enough to run before any merchant hook.
|
|
129
|
+
|
|
130
|
+
A request whose payment header cannot possibly be a credential (not
|
|
131
|
+
base64/base64url JSON, not a JWT-shaped token) is rejected up front, so junk
|
|
132
|
+
headers never trigger per-request hooks (``pre_validate``, pricing,
|
|
133
|
+
recipient minting) or the identity-gate API call.
|
|
134
|
+
|
|
135
|
+
This is deliberately a SHAPE check only. Signature verification, payTo
|
|
136
|
+
binding, and challenge validation stay where they are (the x402 validator
|
|
137
|
+
and the MPP settle path) — those need per-request state the hooks produce.
|
|
138
|
+
A well-formed-but-invalid credential still reaches the real validators and
|
|
139
|
+
fails there.
|
|
140
|
+
|
|
141
|
+
Returns ``None`` when every present credential channel is plausibly shaped
|
|
142
|
+
(or no payment header is present).
|
|
143
|
+
"""
|
|
144
|
+
headers = _unwrap_headers(request_or_headers)
|
|
145
|
+
x402_token = _read_header(headers, "payment-signature") or _read_header(headers, "x-payment")
|
|
146
|
+
if x402_token:
|
|
147
|
+
if not _decodes_to_json_object(x402_token):
|
|
148
|
+
return MalformedPaymentCredential(
|
|
149
|
+
channel="x402",
|
|
150
|
+
message="X-Payment header is not decodable base64 JSON.",
|
|
151
|
+
)
|
|
152
|
+
return None
|
|
153
|
+
auth = _read_header(headers, "authorization")
|
|
154
|
+
if isinstance(auth, str) and auth.startswith("Payment "):
|
|
155
|
+
token = auth[len("Payment ") :].strip()
|
|
156
|
+
if not token or (not _decodes_to_json_object(token) and not _JWT_SHAPE_RE.match(token)):
|
|
157
|
+
return MalformedPaymentCredential(
|
|
158
|
+
channel="mpp",
|
|
159
|
+
message=("Authorization: Payment credential is neither base64-encoded JSON nor a token-shaped value."),
|
|
160
|
+
)
|
|
161
|
+
return None
|
{agentscore_commerce-2.5.2 → agentscore_commerce-2.5.3}/agentscore_commerce/payment/zero_settle.py
RENAMED
|
@@ -2,16 +2,25 @@
|
|
|
2
2
|
|
|
3
3
|
CDP rejects EIP-3009 ``transferWithAuthorization`` with ``value=0`` as
|
|
4
4
|
``invalid_payload``; pympp's tempo intents accept only ``hash`` and
|
|
5
|
-
``transaction`` payload types
|
|
6
|
-
emits for $0 settles
|
|
7
|
-
|
|
5
|
+
``transaction`` payload types — the wallet-bound ``proof`` payload that the
|
|
6
|
+
mppx client emits for $0 settles (and that the mppx server verifies) has no
|
|
7
|
+
pympp counterpart yet. Both upstream verify+settle paths therefore fail when
|
|
8
|
+
the authorized amount is zero, so merchants that drop the settle to $0 in a
|
|
8
9
|
redemption-code flow need a way to skip verify+settle entirely while still
|
|
9
10
|
recovering the signer for wallet-capture attribution.
|
|
10
11
|
|
|
11
12
|
``zero_amount_carve_out`` is that path: parse the credential, lift the signer,
|
|
12
13
|
return ``ZeroSettleResult(signer_address, signer_network, tx_hash=None)``.
|
|
13
14
|
Identity is still authenticated by the merchant's gate above; the redemption
|
|
14
|
-
code is single-use; nothing on-chain to verify.
|
|
15
|
+
code is single-use; nothing on-chain to verify. The recovered signer block is
|
|
16
|
+
UNAUTHENTICATED (parse-only) on the MPP path — treat it as an attribution
|
|
17
|
+
hint, not a verified identity.
|
|
18
|
+
|
|
19
|
+
Known divergence from the Node SDK: node-commerce delegates $0 Tempo settles
|
|
20
|
+
to mppx's native zero-amount proof path (verified) and carves out only x402 +
|
|
21
|
+
Solana. This module keeps the Tempo carve-out because pympp has no proof
|
|
22
|
+
payload support; revisit when pympp ships it so both SDKs converge on
|
|
23
|
+
upstream verification.
|
|
15
24
|
"""
|
|
16
25
|
|
|
17
26
|
from __future__ import annotations
|
|
@@ -1,7 +1,17 @@
|
|
|
1
|
-
"""Short-TTL body-hash
|
|
1
|
+
"""Short-TTL body-hash caches.
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
:func:`create_result_cache` is the neutral primitive: a keyed JSON-value cache
|
|
4
|
+
with a stable content-hash key builder. Use it to cache any per-request result
|
|
5
|
+
a merchant computes on the probe leg and replays on the settle leg — e.g. the
|
|
6
|
+
output of a paid upstream call made in a ``Checkout`` ``pre_validate`` hook, so
|
|
7
|
+
a payment retry (or a junk payment header) never pays upstream twice.
|
|
8
|
+
|
|
9
|
+
:func:`create_quote_cache` is the compute-first-flavored wrapper used by
|
|
10
|
+
``compute_first_checkout``: the cached value is a :class:`CachedQuote`
|
|
11
|
+
(``body`` / ``price_cents`` / ``recipients``). Standard x402-fetch retry
|
|
12
|
+
semantics resign the buyer's ORIGINAL request body — there's no ``result_id``
|
|
13
|
+
echo channel through the protocol — so both caches key by a stable
|
|
14
|
+
content-hash of the request body. Same body → same hash → same cache slot.
|
|
5
15
|
|
|
6
16
|
Default in-memory ``dict``; optional ``redis_url`` lazy-imports
|
|
7
17
|
``redis.asyncio`` for multi-instance deployments. ``redis`` is an optional
|
|
@@ -46,6 +56,16 @@ def _canonicalize(value: Any) -> Any:
|
|
|
46
56
|
return value
|
|
47
57
|
|
|
48
58
|
|
|
59
|
+
@dataclass
|
|
60
|
+
class ResultCache:
|
|
61
|
+
"""Async neutral result cache returned by :func:`create_result_cache`."""
|
|
62
|
+
|
|
63
|
+
body_hash_key: Any # (prefix: str, body: dict) -> str
|
|
64
|
+
read: Any # (key: str) -> Any | None
|
|
65
|
+
write: Any # (key: str, value: Any) -> None
|
|
66
|
+
clear: Any # () -> None
|
|
67
|
+
|
|
68
|
+
|
|
49
69
|
@dataclass
|
|
50
70
|
class QuoteCache:
|
|
51
71
|
"""Async quote cache returned by :func:`create_quote_cache`."""
|
|
@@ -56,20 +76,21 @@ class QuoteCache:
|
|
|
56
76
|
clear: Any # () -> None
|
|
57
77
|
|
|
58
78
|
|
|
59
|
-
def
|
|
79
|
+
def create_result_cache(
|
|
60
80
|
*,
|
|
61
81
|
ttl_ms: int = DEFAULT_TTL_MS,
|
|
62
82
|
redis_url: str | None = None,
|
|
63
|
-
key_prefix: str = "
|
|
64
|
-
) ->
|
|
65
|
-
"""Build a fresh
|
|
66
|
-
|
|
67
|
-
Each call owns its own state (memory dict + Redis client).
|
|
68
|
-
``redis_url`` for a Redis-backed
|
|
69
|
-
in-process only and replicas diverge under
|
|
83
|
+
key_prefix: str = "result:",
|
|
84
|
+
) -> ResultCache:
|
|
85
|
+
"""Build a fresh neutral result cache.
|
|
86
|
+
|
|
87
|
+
Each call owns its own state (memory dict + Redis client). Values must
|
|
88
|
+
survive ``json.dumps`` round-trips. Set ``redis_url`` for a Redis-backed
|
|
89
|
+
cache; otherwise the cache is in-process only and replicas diverge under
|
|
90
|
+
load.
|
|
70
91
|
"""
|
|
71
|
-
mem_state: dict[str, tuple[
|
|
72
|
-
_get_redis = memoized_redis(url=redis_url, label="
|
|
92
|
+
mem_state: dict[str, tuple[Any, float]] = {}
|
|
93
|
+
_get_redis = memoized_redis(url=redis_url, label="result-cache")
|
|
73
94
|
|
|
74
95
|
def _body_hash_key(prefix: str, body: dict[str, Any]) -> str:
|
|
75
96
|
canonical = json.dumps(_canonicalize(body), separators=(",", ":"))
|
|
@@ -82,40 +103,25 @@ def create_quote_cache(
|
|
|
82
103
|
if expires_at <= now:
|
|
83
104
|
del mem_state[k]
|
|
84
105
|
|
|
85
|
-
async def _read(key: str) ->
|
|
106
|
+
async def _read(key: str) -> Any | None:
|
|
86
107
|
r = await _get_redis()
|
|
87
108
|
if r is not None:
|
|
88
109
|
with contextlib.suppress(Exception):
|
|
89
110
|
raw = await r.get(f"{key_prefix}{key}")
|
|
90
111
|
if raw is None:
|
|
91
112
|
return None
|
|
92
|
-
|
|
93
|
-
return CachedQuote(
|
|
94
|
-
body=data["body"],
|
|
95
|
-
price_cents=data["price_cents"],
|
|
96
|
-
recipients=data.get("recipients", {}),
|
|
97
|
-
)
|
|
113
|
+
return json.loads(raw)
|
|
98
114
|
_evict_expired()
|
|
99
115
|
entry = mem_state.get(key)
|
|
100
116
|
return entry[0] if entry else None
|
|
101
117
|
|
|
102
|
-
async def _write(
|
|
103
|
-
key: str,
|
|
104
|
-
body: dict[str, Any],
|
|
105
|
-
price_cents: float,
|
|
106
|
-
*,
|
|
107
|
-
recipients: dict[str, str] | None = None,
|
|
108
|
-
) -> None:
|
|
109
|
-
cached = CachedQuote(body=body, price_cents=price_cents, recipients=recipients or {})
|
|
118
|
+
async def _write(key: str, value: Any) -> None:
|
|
110
119
|
r = await _get_redis()
|
|
111
120
|
if r is not None:
|
|
112
121
|
with contextlib.suppress(Exception):
|
|
113
|
-
|
|
114
|
-
{"body": cached.body, "price_cents": cached.price_cents, "recipients": cached.recipients}
|
|
115
|
-
)
|
|
116
|
-
await r.set(f"{key_prefix}{key}", payload, px=ttl_ms)
|
|
122
|
+
await r.set(f"{key_prefix}{key}", json.dumps(value), px=ttl_ms)
|
|
117
123
|
return
|
|
118
|
-
mem_state[key] = (
|
|
124
|
+
mem_state[key] = (value, time.monotonic() * 1000 + ttl_ms)
|
|
119
125
|
|
|
120
126
|
async def _clear() -> None:
|
|
121
127
|
mem_state.clear()
|
|
@@ -124,9 +130,53 @@ def create_quote_cache(
|
|
|
124
130
|
with contextlib.suppress(Exception):
|
|
125
131
|
await r.flushdb()
|
|
126
132
|
|
|
127
|
-
return
|
|
133
|
+
return ResultCache(
|
|
128
134
|
body_hash_key=_body_hash_key,
|
|
129
135
|
read=_read,
|
|
130
136
|
write=_write,
|
|
131
137
|
clear=_clear,
|
|
132
138
|
)
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def create_quote_cache(
|
|
142
|
+
*,
|
|
143
|
+
ttl_ms: int = DEFAULT_TTL_MS,
|
|
144
|
+
redis_url: str | None = None,
|
|
145
|
+
key_prefix: str = "quote:",
|
|
146
|
+
) -> QuoteCache:
|
|
147
|
+
"""Build a fresh quote cache (a result cache with the quote-shaped ``write``).
|
|
148
|
+
|
|
149
|
+
Each call owns its own state (memory dict + Redis client). Set
|
|
150
|
+
``redis_url`` for a Redis-backed cache; otherwise the cache is
|
|
151
|
+
in-process only and replicas diverge under load.
|
|
152
|
+
"""
|
|
153
|
+
cache = create_result_cache(ttl_ms=ttl_ms, redis_url=redis_url, key_prefix=key_prefix)
|
|
154
|
+
|
|
155
|
+
async def _read(key: str) -> CachedQuote | None:
|
|
156
|
+
data = await cache.read(key)
|
|
157
|
+
if data is None:
|
|
158
|
+
return None
|
|
159
|
+
return CachedQuote(
|
|
160
|
+
body=data["body"],
|
|
161
|
+
price_cents=data["price_cents"],
|
|
162
|
+
recipients=data.get("recipients", {}),
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
async def _write(
|
|
166
|
+
key: str,
|
|
167
|
+
body: dict[str, Any],
|
|
168
|
+
price_cents: float,
|
|
169
|
+
*,
|
|
170
|
+
recipients: dict[str, str] | None = None,
|
|
171
|
+
) -> None:
|
|
172
|
+
await cache.write(
|
|
173
|
+
key,
|
|
174
|
+
{"body": body, "price_cents": price_cents, "recipients": recipients or {}},
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
return QuoteCache(
|
|
178
|
+
body_hash_key=cache.body_hash_key,
|
|
179
|
+
read=_read,
|
|
180
|
+
write=_write,
|
|
181
|
+
clear=cache.clear,
|
|
182
|
+
)
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "agentscore-commerce"
|
|
7
|
-
version = "2.5.
|
|
7
|
+
version = "2.5.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"
|
|
@@ -137,7 +137,7 @@ def _req(headers: dict[str, str]) -> CheckoutRequest:
|
|
|
137
137
|
return CheckoutRequest(
|
|
138
138
|
method="POST",
|
|
139
139
|
url=URL,
|
|
140
|
-
headers={"x-payment": "
|
|
140
|
+
headers={"x-payment": "eyJzdHViIjogdHJ1ZX0=", **headers},
|
|
141
141
|
body={"product_id": "p1", "quantity": 1},
|
|
142
142
|
)
|
|
143
143
|
|
|
@@ -608,7 +608,7 @@ class TestCheckoutAipRendererHonorsProblemJson:
|
|
|
608
608
|
async def test_edge_deny_renders_problem_json_through_fastapi_adapter(self) -> None:
|
|
609
609
|
checkout = _make_checkout(CheckoutGateConfig(api_key="as_test_key", require_kyc=True, aip=AIP))
|
|
610
610
|
request = _starlette_request(
|
|
611
|
-
{"x-payment": "
|
|
611
|
+
{"x-payment": "eyJzdHViIjogdHJ1ZX0=", "agent-identity": "eyJhbGciOiJFZERTQSJ9.e30.sig"}
|
|
612
612
|
)
|
|
613
613
|
resp = await checkout.handle_fastapi(request)
|
|
614
614
|
assert resp.status_code == 401
|
|
@@ -620,7 +620,7 @@ class TestCheckoutAipRendererHonorsProblemJson:
|
|
|
620
620
|
# The override is AIP-only: a missing-identity denial (no Agent-Identity header) through the
|
|
621
621
|
# same renderer must stay on the application/json default, untouched.
|
|
622
622
|
checkout = _make_checkout(CheckoutGateConfig(api_key="as_test_key", require_kyc=True, aip=AIP))
|
|
623
|
-
request = _starlette_request({"x-payment": "
|
|
623
|
+
request = _starlette_request({"x-payment": "eyJzdHViIjogdHJ1ZX0="})
|
|
624
624
|
resp = await checkout.handle_fastapi(request)
|
|
625
625
|
assert resp.media_type == "application/json"
|
|
626
626
|
body = json.loads(resp.body)
|
|
@@ -797,7 +797,7 @@ class TestCheckoutStaticGateEnforcement:
|
|
|
797
797
|
req = CheckoutRequest(
|
|
798
798
|
method="POST",
|
|
799
799
|
url=URL,
|
|
800
|
-
headers={"x-payment": "
|
|
800
|
+
headers={"x-payment": "eyJzdHViIjogdHJ1ZX0="},
|
|
801
801
|
body={"product_id": "p1", "quantity": 1},
|
|
802
802
|
raw=object(),
|
|
803
803
|
)
|
|
@@ -550,7 +550,13 @@ async def test_compose_mppx_returns_200_runs_on_settled() -> None:
|
|
|
550
550
|
compose_mppx=compose_mppx,
|
|
551
551
|
on_settled=on_settled,
|
|
552
552
|
)
|
|
553
|
-
result = await checkout.handle(
|
|
553
|
+
result = await checkout.handle(
|
|
554
|
+
_req(
|
|
555
|
+
headers={
|
|
556
|
+
"authorization": "Payment eyJjaGFsbGVuZ2UiOiB7ImlkIjogImNoXzEiLCAicmVhbG0iOiAiYXBpLmV4YW1wbGUifSwgInBheWxvYWQiOiB7InR5cGUiOiAiaGFzaCIsICJoYXNoIjogIjB4YWJjIn19"
|
|
557
|
+
}
|
|
558
|
+
)
|
|
559
|
+
)
|
|
554
560
|
assert result.status == 200
|
|
555
561
|
assert result.headers["payment-response"] == "ok"
|
|
556
562
|
on_settled.assert_awaited_once()
|
|
@@ -584,7 +590,13 @@ async def test_mppx_settle_leg_resolves_recipients_for_compose_and_on_settled()
|
|
|
584
590
|
compose_mppx=compose,
|
|
585
591
|
on_settled=on_settled,
|
|
586
592
|
)
|
|
587
|
-
result = await checkout.handle(
|
|
593
|
+
result = await checkout.handle(
|
|
594
|
+
_req(
|
|
595
|
+
headers={
|
|
596
|
+
"authorization": "Payment eyJjaGFsbGVuZ2UiOiB7ImlkIjogImNoXzEiLCAicmVhbG0iOiAiYXBpLmV4YW1wbGUifSwgInBheWxvYWQiOiB7InR5cGUiOiAiaGFzaCIsICJoYXNoIjogIjB4YWJjIn19"
|
|
597
|
+
}
|
|
598
|
+
)
|
|
599
|
+
)
|
|
588
600
|
assert result.status == 200
|
|
589
601
|
# Both hooks saw the per-request minted recipient (not an empty dict).
|
|
590
602
|
assert seen["compose"] == {"tempo": minted}
|
|
@@ -606,7 +618,13 @@ async def test_compose_mppx_payment_receipt_header_surfaces_on_response() -> Non
|
|
|
606
618
|
compute_pricing=lambda _ctx: PricingResult(amount_usd=10.0),
|
|
607
619
|
compose_mppx=compose_mppx,
|
|
608
620
|
)
|
|
609
|
-
result = await checkout.handle(
|
|
621
|
+
result = await checkout.handle(
|
|
622
|
+
_req(
|
|
623
|
+
headers={
|
|
624
|
+
"authorization": "Payment eyJjaGFsbGVuZ2UiOiB7ImlkIjogImNoXzEiLCAicmVhbG0iOiAiYXBpLmV4YW1wbGUifSwgInBheWxvYWQiOiB7InR5cGUiOiAiaGFzaCIsICJoYXNoIjogIjB4YWJjIn19"
|
|
625
|
+
}
|
|
626
|
+
)
|
|
627
|
+
)
|
|
610
628
|
assert result.status == 200
|
|
611
629
|
assert result.headers["payment-receipt"] == receipt_header
|
|
612
630
|
|
|
@@ -622,7 +640,13 @@ async def test_compose_mppx_omitted_payment_receipt_header_emits_no_header() ->
|
|
|
622
640
|
compute_pricing=lambda _ctx: PricingResult(amount_usd=10.0),
|
|
623
641
|
compose_mppx=compose_mppx,
|
|
624
642
|
)
|
|
625
|
-
result = await checkout.handle(
|
|
643
|
+
result = await checkout.handle(
|
|
644
|
+
_req(
|
|
645
|
+
headers={
|
|
646
|
+
"authorization": "Payment eyJjaGFsbGVuZ2UiOiB7ImlkIjogImNoXzEiLCAicmVhbG0iOiAiYXBpLmV4YW1wbGUifSwgInBheWxvYWQiOiB7InR5cGUiOiAiaGFzaCIsICJoYXNoIjogIjB4YWJjIn19"
|
|
647
|
+
}
|
|
648
|
+
)
|
|
649
|
+
)
|
|
626
650
|
assert result.status == 200
|
|
627
651
|
assert "payment-receipt" not in result.headers
|
|
628
652
|
|
|
@@ -647,7 +671,13 @@ async def test_compose_mppx_auto_extracts_receipt_header_from_raw_dict() -> None
|
|
|
647
671
|
compute_pricing=lambda _ctx: PricingResult(amount_usd=10.0),
|
|
648
672
|
compose_mppx=compose_mppx,
|
|
649
673
|
)
|
|
650
|
-
result = await checkout.handle(
|
|
674
|
+
result = await checkout.handle(
|
|
675
|
+
_req(
|
|
676
|
+
headers={
|
|
677
|
+
"authorization": "Payment eyJjaGFsbGVuZ2UiOiB7ImlkIjogImNoXzEiLCAicmVhbG0iOiAiYXBpLmV4YW1wbGUifSwgInBheWxvYWQiOiB7InR5cGUiOiAiaGFzaCIsICJoYXNoIjogIjB4YWJjIn19"
|
|
678
|
+
}
|
|
679
|
+
)
|
|
680
|
+
)
|
|
651
681
|
assert result.status == 200
|
|
652
682
|
assert result.headers["payment-receipt"] == "auto-from-raw-dict"
|
|
653
683
|
|
|
@@ -671,7 +701,13 @@ async def test_compose_mppx_auto_extracts_receipt_header_from_raw_tuple() -> Non
|
|
|
671
701
|
compute_pricing=lambda _ctx: PricingResult(amount_usd=10.0),
|
|
672
702
|
compose_mppx=compose_mppx,
|
|
673
703
|
)
|
|
674
|
-
result = await checkout.handle(
|
|
704
|
+
result = await checkout.handle(
|
|
705
|
+
_req(
|
|
706
|
+
headers={
|
|
707
|
+
"authorization": "Payment eyJjaGFsbGVuZ2UiOiB7ImlkIjogImNoXzEiLCAicmVhbG0iOiAiYXBpLmV4YW1wbGUifSwgInBheWxvYWQiOiB7InR5cGUiOiAiaGFzaCIsICJoYXNoIjogIjB4YWJjIn19"
|
|
708
|
+
}
|
|
709
|
+
)
|
|
710
|
+
)
|
|
675
711
|
assert result.status == 200
|
|
676
712
|
assert result.headers["payment-receipt"] == "auto-from-raw-tuple"
|
|
677
713
|
|
|
@@ -699,7 +735,13 @@ async def test_compose_mppx_explicit_payment_receipt_header_wins_over_raw() -> N
|
|
|
699
735
|
compute_pricing=lambda _ctx: PricingResult(amount_usd=10.0),
|
|
700
736
|
compose_mppx=compose_mppx,
|
|
701
737
|
)
|
|
702
|
-
result = await checkout.handle(
|
|
738
|
+
result = await checkout.handle(
|
|
739
|
+
_req(
|
|
740
|
+
headers={
|
|
741
|
+
"authorization": "Payment eyJjaGFsbGVuZ2UiOiB7ImlkIjogImNoXzEiLCAicmVhbG0iOiAiYXBpLmV4YW1wbGUifSwgInBheWxvYWQiOiB7InR5cGUiOiAiaGFzaCIsICJoYXNoIjogIjB4YWJjIn19"
|
|
742
|
+
}
|
|
743
|
+
)
|
|
744
|
+
)
|
|
703
745
|
assert result.status == 200
|
|
704
746
|
assert result.headers["payment-receipt"] == "explicit-value"
|
|
705
747
|
|
|
@@ -722,7 +764,13 @@ async def test_compose_mppx_receipt_to_header_that_throws_falls_through() -> Non
|
|
|
722
764
|
compute_pricing=lambda _ctx: PricingResult(amount_usd=10.0),
|
|
723
765
|
compose_mppx=compose_mppx,
|
|
724
766
|
)
|
|
725
|
-
result = await checkout.handle(
|
|
767
|
+
result = await checkout.handle(
|
|
768
|
+
_req(
|
|
769
|
+
headers={
|
|
770
|
+
"authorization": "Payment eyJjaGFsbGVuZ2UiOiB7ImlkIjogImNoXzEiLCAicmVhbG0iOiAiYXBpLmV4YW1wbGUifSwgInBheWxvYWQiOiB7InR5cGUiOiAiaGFzaCIsICJoYXNoIjogIjB4YWJjIn19"
|
|
771
|
+
}
|
|
772
|
+
)
|
|
773
|
+
)
|
|
726
774
|
assert result.status == 200
|
|
727
775
|
assert "payment-receipt" not in result.headers
|
|
728
776
|
|
|
@@ -744,7 +792,13 @@ async def test_compose_mppx_returns_402_on_settle_leg_rejects_credential() -> No
|
|
|
744
792
|
compute_pricing=lambda _ctx: PricingResult(amount_usd=10.0),
|
|
745
793
|
compose_mppx=compose_mppx,
|
|
746
794
|
)
|
|
747
|
-
result = await checkout.handle(
|
|
795
|
+
result = await checkout.handle(
|
|
796
|
+
_req(
|
|
797
|
+
headers={
|
|
798
|
+
"authorization": "Payment eyJjaGFsbGVuZ2UiOiB7ImlkIjogImNoXzEiLCAicmVhbG0iOiAiYXBpLmV4YW1wbGUifSwgInBheWxvYWQiOiB7InR5cGUiOiAiaGFzaCIsICJoYXNoIjogIjB4YWJjIn19"
|
|
799
|
+
}
|
|
800
|
+
)
|
|
801
|
+
)
|
|
748
802
|
assert result.status == 400
|
|
749
803
|
assert result.headers["www-authenticate"] == 'Payment id="ord_x"'
|
|
750
804
|
assert result.body["error"]["code"] == "payment_proof_invalid"
|
|
@@ -770,7 +824,13 @@ async def test_compose_mppx_402_with_keychain_failure_surfaces_tempo_key_not_reg
|
|
|
770
824
|
compute_pricing=lambda _ctx: PricingResult(amount_usd=10.0),
|
|
771
825
|
compose_mppx=compose_mppx,
|
|
772
826
|
)
|
|
773
|
-
result = await checkout.handle(
|
|
827
|
+
result = await checkout.handle(
|
|
828
|
+
_req(
|
|
829
|
+
headers={
|
|
830
|
+
"authorization": "Payment eyJjaGFsbGVuZ2UiOiB7ImlkIjogImNoXzEiLCAicmVhbG0iOiAiYXBpLmV4YW1wbGUifSwgInBheWxvYWQiOiB7InR5cGUiOiAiaGFzaCIsICJoYXNoIjogIjB4YWJjIn19"
|
|
831
|
+
}
|
|
832
|
+
)
|
|
833
|
+
)
|
|
774
834
|
assert result.status == 401
|
|
775
835
|
assert result.body["error"]["code"] == "tempo_key_not_registered"
|
|
776
836
|
assert result.settle_phase == "verify_failed"
|