agentscore-commerce 2.0.2__tar.gz → 2.1.1__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.0.2 → agentscore_commerce-2.1.1}/.github/workflows/ci.yml +1 -1
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/.gitignore +2 -0
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/CLAUDE.md +14 -12
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/PKG-INFO +32 -10
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/README.md +29 -9
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/agentscore_commerce/__init__.py +61 -1
- agentscore_commerce-2.1.1/agentscore_commerce/_headers.py +20 -0
- agentscore_commerce-2.1.1/agentscore_commerce/_mppx_receipt.py +106 -0
- agentscore_commerce-2.1.1/agentscore_commerce/_redis.py +99 -0
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/agentscore_commerce/challenge/how_to_pay.py +13 -1
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/agentscore_commerce/challenge/pricing.py +21 -16
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/agentscore_commerce/checkout.py +88 -92
- agentscore_commerce-2.1.1/agentscore_commerce/checkout_compute_first.py +924 -0
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/agentscore_commerce/checkout_hooks.py +11 -15
- agentscore_commerce-2.1.1/agentscore_commerce/errors.py +40 -0
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/agentscore_commerce/identity/__init__.py +25 -6
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/agentscore_commerce/identity/_response.py +33 -0
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/agentscore_commerce/identity/a2a.py +107 -98
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/agentscore_commerce/identity/aiohttp.py +50 -43
- agentscore_commerce-2.1.1/agentscore_commerce/identity/default_denied.py +146 -0
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/agentscore_commerce/identity/django.py +21 -13
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/agentscore_commerce/identity/fastapi.py +45 -18
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/agentscore_commerce/identity/flask.py +39 -21
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/agentscore_commerce/identity/middleware.py +24 -14
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/agentscore_commerce/identity/policy.py +2 -3
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/agentscore_commerce/identity/sanic.py +48 -43
- agentscore_commerce-2.1.1/agentscore_commerce/identity/tokens.py +53 -0
- agentscore_commerce-2.1.1/agentscore_commerce/middleware/__init__.py +7 -0
- agentscore_commerce-2.1.1/agentscore_commerce/middleware/_core.py +114 -0
- agentscore_commerce-2.1.1/agentscore_commerce/middleware/aiohttp.py +64 -0
- agentscore_commerce-2.1.1/agentscore_commerce/middleware/asgi.py +97 -0
- agentscore_commerce-2.1.1/agentscore_commerce/middleware/django.py +69 -0
- agentscore_commerce-2.1.1/agentscore_commerce/middleware/fastapi.py +80 -0
- agentscore_commerce-2.1.1/agentscore_commerce/middleware/flask.py +83 -0
- agentscore_commerce-2.1.1/agentscore_commerce/middleware/sanic.py +71 -0
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/agentscore_commerce/payment/__init__.py +15 -0
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/agentscore_commerce/payment/amounts.py +9 -3
- agentscore_commerce-2.1.1/agentscore_commerce/payment/compose_rails.py +128 -0
- agentscore_commerce-2.1.1/agentscore_commerce/payment/constants.py +16 -0
- agentscore_commerce-2.1.1/agentscore_commerce/payment/default_rails.py +69 -0
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/agentscore_commerce/payment/dispatch.py +23 -5
- agentscore_commerce-2.1.1/agentscore_commerce/payment/mppx_failures.py +59 -0
- agentscore_commerce-2.1.1/agentscore_commerce/payment/network_kind.py +36 -0
- agentscore_commerce-2.1.1/agentscore_commerce/payment/payment_header.py +98 -0
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/agentscore_commerce/payment/rail_spec.py +9 -1
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/agentscore_commerce/payment/signer.py +20 -10
- agentscore_commerce-2.1.1/agentscore_commerce/quote_cache.py +133 -0
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/agentscore_commerce/stripe_multichain/__init__.py +12 -0
- agentscore_commerce-2.1.1/agentscore_commerce/stripe_multichain/pay_to_address.py +127 -0
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/agentscore_commerce/stripe_multichain/payment_intent.py +11 -1
- agentscore_commerce-2.1.1/agentscore_commerce/stripe_multichain/simulate_dispatch.py +100 -0
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/examples/api_provider.py +2 -0
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/examples/compliance_merchant.py +13 -5
- agentscore_commerce-2.1.1/examples/compute_first_merchant.py +104 -0
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/examples/identity_only.py +2 -0
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/examples/multi_rail_merchant.py +26 -21
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/examples/per_product_policy_merchant.py +2 -0
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/examples/signed_ucp_merchant.py +2 -0
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/examples/stripe_multichain_merchant.py +2 -0
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/lefthook.yml +1 -1
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/pyproject.toml +17 -1
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/tests/test_a2a.py +157 -79
- agentscore_commerce-2.1.1/tests/test_a2a_jws_roundtrip.py +118 -0
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/tests/test_amounts.py +31 -0
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/tests/test_challenge.py +14 -0
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/tests/test_checkout.py +25 -0
- agentscore_commerce-2.1.1/tests/test_checkout_compute_first.py +230 -0
- agentscore_commerce-2.1.1/tests/test_checkout_compute_first_adapters.py +180 -0
- agentscore_commerce-2.1.1/tests/test_checkout_compute_first_settle.py +288 -0
- agentscore_commerce-2.1.1/tests/test_checkout_coverage_gaps.py +406 -0
- agentscore_commerce-2.1.1/tests/test_compose_rails.py +68 -0
- agentscore_commerce-2.1.1/tests/test_default_denied.py +73 -0
- agentscore_commerce-2.1.1/tests/test_default_rails.py +46 -0
- agentscore_commerce-2.1.1/tests/test_default_read_only_on_denied.py +35 -0
- agentscore_commerce-2.1.1/tests/test_extract_owner_scope.py +49 -0
- agentscore_commerce-2.1.1/tests/test_internal_helpers.py +78 -0
- agentscore_commerce-2.1.1/tests/test_mppx_failures.py +39 -0
- agentscore_commerce-2.1.1/tests/test_mppx_receipt.py +131 -0
- agentscore_commerce-2.1.1/tests/test_network_kind.py +36 -0
- agentscore_commerce-2.1.1/tests/test_pay_to_address.py +198 -0
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/tests/test_payment_dispatch.py +12 -2
- agentscore_commerce-2.1.1/tests/test_payment_header.py +74 -0
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/tests/test_policy.py +5 -5
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/tests/test_pricing.py +8 -0
- agentscore_commerce-2.1.1/tests/test_quote_cache.py +63 -0
- agentscore_commerce-2.1.1/tests/test_quote_cache_redis.py +148 -0
- agentscore_commerce-2.1.1/tests/test_rate_limit.py +264 -0
- agentscore_commerce-2.1.1/tests/test_redis_internal.py +43 -0
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/tests/test_response.py +42 -0
- agentscore_commerce-2.1.1/tests/test_simulate_dispatch.py +87 -0
- agentscore_commerce-2.1.1/tests/test_solana.py +36 -0
- agentscore_commerce-2.1.1/tests/test_solana_mpp_rail_spec_post_init.py +40 -0
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/tests/test_stripe_multichain.py +5 -1
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/tests/test_ucp_jwks.py +1 -2
- agentscore_commerce-2.1.1/tests/test_x402_settle_extra.py +125 -0
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/uv.lock +135 -91
- agentscore_commerce-2.0.2/agentscore_commerce/identity/tokens.py +0 -20
- agentscore_commerce-2.0.2/examples/variable_cost_merchant.py +0 -192
- agentscore_commerce-2.0.2/vulture_whitelist.py +0 -19
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/.github/dependabot.yml +0 -0
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/.github/workflows/publish.yml +0 -0
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/.github/workflows/security.yml +0 -0
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/CODE_OF_CONDUCT.md +0 -0
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/CONTRIBUTING.md +0 -0
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/LICENSE +0 -0
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/SECURITY.md +0 -0
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/agentscore_commerce/api/__init__.py +0 -0
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/agentscore_commerce/challenge/__init__.py +0 -0
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/agentscore_commerce/challenge/accepted_methods.py +0 -0
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/agentscore_commerce/challenge/agent_instructions.py +0 -0
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/agentscore_commerce/challenge/agent_memory.py +0 -0
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/agentscore_commerce/challenge/body.py +0 -0
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/agentscore_commerce/challenge/identity.py +0 -0
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/agentscore_commerce/challenge/receipt.py +0 -0
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/agentscore_commerce/challenge/respond_402.py +0 -0
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/agentscore_commerce/challenge/validation_error.py +0 -0
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/agentscore_commerce/discovery/__init__.py +0 -0
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/agentscore_commerce/discovery/agentscore_content.py +0 -0
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/agentscore_commerce/discovery/bazaar.py +0 -0
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/agentscore_commerce/discovery/llms_txt.py +0 -0
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/agentscore_commerce/discovery/openapi.py +0 -0
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/agentscore_commerce/discovery/probe.py +0 -0
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/agentscore_commerce/discovery/redemption_md.py +0 -0
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/agentscore_commerce/discovery/robots_tag.py +0 -0
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/agentscore_commerce/discovery/skill_md.py +0 -0
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/agentscore_commerce/discovery/well_known.py +0 -0
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/agentscore_commerce/discovery/well_known_mpp.py +0 -0
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/agentscore_commerce/discovery/well_known_x402.py +0 -0
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/agentscore_commerce/identity/_denial.py +0 -0
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/agentscore_commerce/identity/address.py +0 -0
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/agentscore_commerce/identity/cache.py +0 -0
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/agentscore_commerce/identity/core.py +0 -0
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/agentscore_commerce/identity/py.typed +0 -0
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/agentscore_commerce/identity/sessions.py +0 -0
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/agentscore_commerce/identity/signer.py +0 -0
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/agentscore_commerce/identity/types.py +0 -0
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/agentscore_commerce/identity/ucp.py +0 -0
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/agentscore_commerce/identity/ucp_jwks.py +0 -0
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/agentscore_commerce/payment/directive.py +0 -0
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/agentscore_commerce/payment/headers.py +0 -0
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/agentscore_commerce/payment/idempotency.py +0 -0
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/agentscore_commerce/payment/lazy.py +0 -0
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/agentscore_commerce/payment/mppx_server.py +0 -0
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/agentscore_commerce/payment/networks.py +0 -0
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/agentscore_commerce/payment/rails.py +0 -0
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/agentscore_commerce/payment/settlement_override.py +0 -0
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/agentscore_commerce/payment/solana.py +0 -0
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/agentscore_commerce/payment/usdc.py +0 -0
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/agentscore_commerce/payment/wwwauthenticate.py +0 -0
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/agentscore_commerce/payment/x402.py +0 -0
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/agentscore_commerce/payment/x402_server.py +0 -0
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/agentscore_commerce/payment/x402_settle.py +0 -0
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/agentscore_commerce/payment/x402_validation.py +0 -0
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/agentscore_commerce/payment/zero_settle.py +0 -0
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/agentscore_commerce/stripe_multichain/mppx_stripe.py +0 -0
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/agentscore_commerce/stripe_multichain/pi_cache.py +0 -0
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/agentscore_commerce/stripe_multichain/simulate_deposit.py +0 -0
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/examples/README.md +0 -0
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/ruff.toml +0 -0
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/scripts/regenerate_cross_lang_fixtures.py +0 -0
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/tests/__init__.py +0 -0
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/tests/conftest.py +0 -0
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/tests/fixtures/cross-lang/node-agentscore-gate-blocked.json +0 -0
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/tests/fixtures/cross-lang/node-agentscore-gate-full.json +0 -0
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/tests/fixtures/cross-lang/node-capability.json +0 -0
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/tests/fixtures/cross-lang/node-emoji-keys.json +0 -0
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/tests/fixtures/cross-lang/node-es256-rails.json +0 -0
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/tests/fixtures/cross-lang/node-extras-int.json +0 -0
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/tests/fixtures/cross-lang/node-int-boundary.json +0 -0
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/tests/fixtures/cross-lang/node-minimal.json +0 -0
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/tests/fixtures/cross-lang/node-multikey.json +0 -0
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/tests/fixtures/cross-lang/node-unicode.json +0 -0
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/tests/fixtures/cross-lang/py-agentscore-gate-blocked.json +0 -0
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/tests/fixtures/cross-lang/py-agentscore-gate-full.json +0 -0
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/tests/fixtures/cross-lang/py-capability.json +0 -0
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/tests/fixtures/cross-lang/py-emoji-keys.json +0 -0
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/tests/fixtures/cross-lang/py-es256-rails.json +0 -0
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/tests/fixtures/cross-lang/py-extras-int.json +0 -0
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/tests/fixtures/cross-lang/py-int-boundary.json +0 -0
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/tests/fixtures/cross-lang/py-minimal.json +0 -0
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/tests/fixtures/cross-lang/py-multikey.json +0 -0
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/tests/fixtures/cross-lang/py-unicode.json +0 -0
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/tests/test_address.py +0 -0
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/tests/test_agent_memory_emitter.py +0 -0
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/tests/test_aiohttp.py +0 -0
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/tests/test_api_reexport.py +0 -0
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/tests/test_cache.py +0 -0
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/tests/test_classify_orchestration_error.py +0 -0
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/tests/test_core.py +0 -0
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/tests/test_coverage_fillers.py +0 -0
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/tests/test_denial.py +0 -0
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/tests/test_discovery.py +0 -0
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/tests/test_dispatch.py +0 -0
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/tests/test_django.py +0 -0
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/tests/test_fastapi.py +0 -0
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/tests/test_flask.py +0 -0
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/tests/test_gate_quota_info.py +0 -0
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/tests/test_get_signer_verdict.py +0 -0
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/tests/test_idempotency_helper.py +0 -0
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/tests/test_integration.py +0 -0
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/tests/test_lifted_helpers.py +0 -0
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/tests/test_load_ucp_signing_key_from_env.py +0 -0
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/tests/test_middleware.py +0 -0
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/tests/test_payment_directive.py +0 -0
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/tests/test_payment_handlers.py +0 -0
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/tests/test_payment_headers.py +0 -0
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/tests/test_payment_misc.py +0 -0
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/tests/test_payment_servers.py +0 -0
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/tests/test_payment_signer.py +0 -0
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/tests/test_public_surface.py +0 -0
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/tests/test_rail_spec.py +0 -0
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/tests/test_robots_tag.py +0 -0
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/tests/test_sanic.py +0 -0
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/tests/test_seamless_helpers.py +0 -0
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/tests/test_sessions.py +0 -0
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/tests/test_signer_match.py +0 -0
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/tests/test_skill_md.py +0 -0
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/tests/test_tokens.py +0 -0
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/tests/test_ucp.py +0 -0
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/tests/test_ucp_cross_lang.py +0 -0
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/tests/test_validation_error.py +0 -0
- {agentscore_commerce-2.0.2 → agentscore_commerce-2.1.1}/tests/test_zero_settle.py +0 -0
|
@@ -47,7 +47,7 @@ jobs:
|
|
|
47
47
|
run: uv run ty check agentscore_commerce/
|
|
48
48
|
|
|
49
49
|
- name: Vulture
|
|
50
|
-
run: uv run vulture agentscore_commerce/
|
|
50
|
+
run: uv run vulture agentscore_commerce/ --min-confidence 80
|
|
51
51
|
|
|
52
52
|
- name: Tests
|
|
53
53
|
run: uv run pytest tests/ -v --cov=agentscore_commerce --cov-report=term-missing
|
|
@@ -8,14 +8,15 @@ 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 factories: `pricing_result` (cents → typed `PricingResult`), `validation_response_{fastapi,flask,django,aiohttp,sanic}` (4xx envelope per framework), `Receipt`/`ReceiptNextSteps`/`ProductInfo`/`ShippingAddress` (canonical 200-receipt dataclasses, universal across goods + API merchants) |
|
|
12
|
-
| `agentscore_commerce.identity.{fastapi,flask,django,aiohttp,sanic,middleware}` | Trust gate middleware (KYC, age, sanctions on both account name and signer wallet, jurisdiction) |
|
|
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), `Receipt`/`ReceiptNextSteps`/`ProductInfo`/`ShippingAddress` (canonical 200-receipt dataclasses, universal across goods + API merchants) |
|
|
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 existing `agentscore_gate(app, ...)` and `AgentScoreGate` accept an optional `condition=` callable for inline gating. |
|
|
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
|
-
| `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), `process_x402_settle` (verify+settle in one call), `create_mppx_server` (wraps `pympp[server,tempo,stripe]>=0.6`), dispatch-by-network, signer extraction, WWW-Authenticate header, Settlement-Overrides header |
|
|
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 |
|
|
15
15
|
| `agentscore_commerce.discovery` | Discovery probe (`is_discovery_probe_request`, `build_discovery_probe_response`), Bazaar wrapper, `/.well-known/mpp.json`, `llms.txt` builder, `skill.md` builder (Claude-Skill-compatible agent-discovery manifest), `build_redemption_skill_md` (delivery-neutral; printed/emailed/API-trial codes all covered via `delivery_intro`/`body_shape`/`body_rules`/`extra_recovery_rows` overrides), `build_merchant_index_json` + `standard_endpoint_descriptions(kind=)` (canonical `/` discovery body for goods or API merchants), `build_success_next_steps` (universal Passport-active success block), `build_agentscore_onboarding_steps`, OpenAPI snippets, `NoindexNonDiscoveryMiddleware` ASGI middleware. Plus the UCP/JWKS publish surface: `build_signed_ucp_response`, `build_signed_jwks_response`, `well_known_preflight_response`, `default_a2a_services`, `bootstrap_ucp_signing_key`, framework-neutral `SignedDiscoveryResponse` + per-framework wrappers `signed_response_{fastapi,flask,django,aiohttp,sanic}` |
|
|
16
16
|
| `agentscore_commerce.challenge` | 402-body builders: accepted_methods, identity_metadata (auto-attached by `Checkout` when wallet header present), how_to_pay, agent_instructions, build_402_body, pricing, agent_memory, `build_validation_error` (4xx body builder), `Receipt`/`ReceiptNextSteps`/`ProductInfo`/`ShippingAddress` (canonical 200-receipt dataclasses) |
|
|
17
|
-
| `agentscore_commerce.stripe_multichain` | Multichain PaymentIntent helper (`create_multichain_payment_intent` returns `MultichainPaymentIntentResult`; read `result.deposit_addresses[network]` directly),
|
|
17
|
+
| `agentscore_commerce.stripe_multichain` | Multichain PaymentIntent helper (`create_multichain_payment_intent` returns `MultichainPaymentIntentResult`; read `result.deposit_addresses[network]` directly), `create_pay_to_address_from_stripe_pi(authorization_header=, amount_cents=, stripe=, pi_cache=, networks=, metadata=, order_id=, preferred_network=)` — one-call per-order payTo resolver matching `Checkout.mint_recipients`: on the settle leg, reuses the buyer's signed-against payTo from the MPP credential (after `pi_cache.has_address` check); on the discovery leg, mints a fresh PI via `create_multichain_payment_intent` and caches the addresses + PI mapping. Testnet simulator (`simulate_crypto_deposit`, `simulate_deposit_if_test_mode`), `simulate_deposit_for_outcome(outcome=, deposit_address=, get_payment_intent_id=, stripe_secret_key=, stripe_version=)` (dispatches the simulator based on a Checkout / compute_first_checkout settle outcome; replaces the per-merchant rail-switch + thin `simulate_deposit_if_testnet(addr, network)` wrapper), `network_for_outcome` (outcome → simulator network arg, handles both Checkout-shaped `rail_key` and compute-first-shaped `mpp_method`, accepts bare scheme names AND `<scheme>/charge` forms), `create_pi_cache`, `create_mppx_stripe` |
|
|
18
18
|
| `agentscore_commerce.api` | Re-exports `AgentScore` from `agentscore` SDK |
|
|
19
|
+
| `agentscore_commerce.middleware.{fastapi,flask,django,aiohttp,sanic,asgi}` | Framework-specific rate-limit middleware. FastAPI exposes `rate_limit_fastapi(...)` (FastAPI dependency) plus the ASGI `RateLimitMiddleware` re-export; Flask exposes `rate_limit_flask(app, ...)` (installs a `before_request` hook); Django exposes a class-based async `RateLimitMiddleware` configured via `settings.AGENTSCORE_RATE_LIMIT`; aiohttp exposes `rate_limit_aiohttp(...)` middleware factory; Sanic exposes `rate_limit_sanic(app, ...)` installer; `asgi.RateLimitMiddleware` is a generic ASGI middleware that works with any starlette-compatible app. Shared options: `window_seconds` (default 60), `max_requests` (default 60), `key_resolver` (default first hop of `x-forwarded-for`), `redis_url` (lazy-imports `redis.asyncio` when set, in-memory `dict` fallback otherwise), `key_prefix`. `redis` is an optional peer dep (install via the `redis` extra). |
|
|
19
20
|
|
|
20
21
|
## Architecture
|
|
21
22
|
|
|
@@ -44,7 +45,7 @@ Peer-dep pattern: payment/x402/mppx/stripe modules import lazily at runtime; ven
|
|
|
44
45
|
| `identity_only.py` | Compliance gate without payment (vendor handles their own) |
|
|
45
46
|
| `multi_rail_merchant.py` | Full agent-commerce: identity + Tempo MPP + x402 + Stripe SPT |
|
|
46
47
|
| `stripe_multichain_merchant.py` | Stripe-anchored multichain (PaymentIntent → tempo/base/solana deposit addresses) |
|
|
47
|
-
| `
|
|
48
|
+
| `compute_first_merchant.py` | Pay-per-result variable-cost merchant via the compute-first + exact-x402 helper (`compute_first_checkout`). Exact-mode rails only (x402-exact Base, tempo/charge, solana/charge, Stripe SPT); deliberately scoped out of x402-upto (Permit2) and Settlement-Overrides. Probe runs the work + caches by body content-hash; settle replays the cached result at the computed exact price. Pairs with `rate_limit_fastapi` since the probe leg runs work pre-payment. |
|
|
48
49
|
| `compliance_merchant.py` | Regulated-goods merchant: full compliance gate + custom `on_denied` composing the denial helpers (`verification_agent_instructions`, `is_fixable_denial`, `build_signer_mismatch_body`, `build_contact_support_next_steps`, `denial_reason_to_body`/`denial_reason_status`) |
|
|
49
50
|
| `per_product_policy_merchant.py` | Multi-product merchant where each row carries its own compliance policy. One product hard-gates KYC + age + state; another is anonymous; a third uses `enforcement="soft"` (request KYC but don't block sale). Demonstrates `PolicyBlock`, `build_gate_from_policy`, `run_gate_with_enforcement`, `shipping_country_allowed`, `shipping_state_allowed`. |
|
|
50
51
|
| `signed_ucp_merchant.py` | Signed UCP profile (`/.well-known/ucp`) + JWKS endpoint (`/.well-known/jwks.json`). AgentScore's `agentscore-profile+jws` is a vendor extension on top of UCP for trust-mode verifiers (regulated-commerce, AP2-aware) that opt into auditable cryptographic provenance — UCP §6 itself does NOT mandate signing; production UCP merchants commonly ship unsigned. Wires ephemeral-for-dev / env-JWK-for-prod signing, kid rotation, and `Cache-Control` posture. Uses `generate_ucp_signing_key`, `sign_ucp_profile`, `build_jwks_response`, `UCPSigningKey.from_jwk`, `UCPVerificationError`. Demonstrates the payment-handler builders (`mpp_payment_handler`, `x402_payment_handler`, `stripe_spt_payment_handler` — see "Payment-handler builders" below). |
|
|
@@ -79,7 +80,11 @@ Two identity types: wallet (`X-Wallet-Address`) and operator-token (`X-Operator-
|
|
|
79
80
|
|
|
80
81
|
`DenialReason` codes (`missing_identity`, `identity_verification_required`, `token_expired`, `invalid_credential`, `wallet_signer_mismatch`, `wallet_auth_requires_wallet_signing`, `wallet_not_trusted`, `api_error`, `payment_required`) each carry a structured `agent_instructions` JSON block describing concrete recovery actions. See `agentscore_commerce/identity/_response.py` for the canned action copy.
|
|
81
82
|
|
|
82
|
-
`create_session_on_missing` auto-mints a verification session when no identity is present
|
|
83
|
+
`create_session_on_missing` auto-mints a verification session when no identity is present AND when `wallet_not_trusted` carries fixable reasons (`kyc_required` / `kyc_pending` / `kyc_failed`) — both paths rewrite the denial to `identity_verification_required` before reaching `on_denied`. When the merchant omits `create_session_on_missing` from `CheckoutGateConfig`, `Checkout` auto-defaults it from `gate.api_key` + `gate.base_url` + `gate.context` + `gate.merchant_name`. Merchants that need `on_before_session` side effects (e.g. pre-minting an order_id) supply their own config to override.
|
|
84
|
+
|
|
85
|
+
`build_verification_required_body(reason, message=?, agent_instructions=?, extra=?)` — canonical body builder for the `identity_verification_required` denial. Spreads `verify_url` / `session_id` / `poll_secret` / `poll_url` / `agent_instructions` from the gate-minted reason into a 4xx envelope with merchant-specific message + optional extras. Saves the per-merchant mapping boilerplate.
|
|
86
|
+
|
|
87
|
+
`get_signer_verdict(request)` (per-adapter) returns the cached `signer_match` + `signer_sanctions` verdicts the gate composed on its primary `/v1/assess` call (single round trip; merchants build a 403 with `build_signer_mismatch_body(result=verdict.signer_match)` when `kind != "pass"`).
|
|
83
88
|
|
|
84
89
|
Captured wallets: `capture_wallet(...)` is fire-and-forget. Reads `operator_token` stashed during gating and POSTs to `/v1/credentials/wallets`. No-ops for wallet-authenticated requests.
|
|
85
90
|
|
|
@@ -94,15 +99,12 @@ Wallet-signer-match + signer-sanctions: the gate adapter calls `extract_payment_
|
|
|
94
99
|
`AgentScoreGate(...)` (or `agentscore_gate(app, ...)` on Flask/Sanic) is mounted directly when the route is AgentScore-only; every request runs identity + policy. To support **anonymous discovery by any spec-compliant x402 wallet** (Coinbase awal, Phantom, Solflare, ...), wrap the gate so it fires only when a payment credential is attached:
|
|
95
100
|
|
|
96
101
|
```python
|
|
102
|
+
from agentscore_commerce.payment import has_payment_header
|
|
103
|
+
|
|
97
104
|
_gate = AgentScoreGate(api_key=..., require_kyc=True, ...)
|
|
98
105
|
|
|
99
106
|
async def gate_on_settle(request: Request) -> None:
|
|
100
|
-
|
|
101
|
-
request.headers.get("payment-signature")
|
|
102
|
-
or request.headers.get("x-payment")
|
|
103
|
-
or (request.headers.get("authorization") or "").startswith("Payment ")
|
|
104
|
-
)
|
|
105
|
-
if not has_payment_header:
|
|
107
|
+
if not has_payment_header(request):
|
|
106
108
|
return None
|
|
107
109
|
return await _gate(request)
|
|
108
110
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: agentscore-commerce
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.1.1
|
|
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
|
|
@@ -32,6 +32,8 @@ Provides-Extra: flask
|
|
|
32
32
|
Requires-Dist: flask>=2.0.0; extra == 'flask'
|
|
33
33
|
Provides-Extra: mppx
|
|
34
34
|
Requires-Dist: pympp[server,stripe,tempo]<1,>=0.6; extra == 'mppx'
|
|
35
|
+
Provides-Extra: redis
|
|
36
|
+
Requires-Dist: redis<7,>=5.0; extra == 'redis'
|
|
35
37
|
Provides-Extra: sanic
|
|
36
38
|
Requires-Dist: sanic>=23.0.0; extra == 'sanic'
|
|
37
39
|
Provides-Extra: starlette
|
|
@@ -72,15 +74,32 @@ pip install 'agentscore-commerce[fastapi,x402,coinbase]'
|
|
|
72
74
|
|---|---|
|
|
73
75
|
| `agentscore_commerce` (top-level) | `Checkout` orchestrator + `CheckoutContext` + `CheckoutGateConfig` + `CheckoutValidationError` + `DiscoveryProbeConfig` + `SettleOutcome` + `MppxComposeOutcome` + `PricingResult` (the 2.0 high-level surface: one config object, hooks for pre_validate/compute_pricing/on_settled/mint_recipients/compose_mppx, auto-derived x402+mppx 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}`); `pricing_result` (factory: cents-denominated → typed `PricingResult` with embedded `PricingBlock`); `validation_response_{fastapi,flask,django,aiohttp,sanic}` (per-framework 4xx envelope wrappers); `make_mppx_compose_hook` (canonical pympp compose adapter). |
|
|
74
76
|
| `agentscore_commerce.identity.{fastapi,flask,django,aiohttp,sanic,middleware}` | Trust gate middleware: KYC, sanctions (account name + signer wallet), age, jurisdiction. `AgentScoreGate(...)` (or `agentscore_gate(app, ...)` on Flask/Sanic), `get_agentscore_data(...)`, `capture_wallet(...)`, `get_signer_verdict(...)`. The gate extracts the payment signer pre-evaluate and passes it to `/v1/assess`, so the API composes both wallet-binding (`signer_match`) and OFAC SDN wallet-address (`signer_sanctions`) verdicts on one round trip. |
|
|
75
|
-
| `agentscore_commerce.identity` (package level) | Re-exports the denial helpers: `denial_reason_status`, `denial_reason_to_body`, `build_signer_mismatch_body`, `build_contact_support_next_steps`, `verification_agent_instructions`, `is_fixable_denial`, `FIXABLE_DENIAL_REASONS`. The per-framework adapter modules also expose `get_gate_quota_info(request)` for surfacing X-RateLimit info from gate state. Also re-exports the per-product policy helpers: `PolicyBlock`, `GateResult`, `EnforcementMode`, `IdentityStatus`, `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) — for multi-product merchants where each product carries its own compliance config: hard gate vs soft vs none, per-product shipping allowlists. Key + token helpers: `load_ucp_signing_key_from_env` (cached env-driven loader for the UCP signing key — reads `UCP_SIGNING_KEY_JWK_PRIVATE` JSON JWK, detects alg from shape, falls back to ephemeral when unset, sanitizes errors so key bytes never reach logs, concurrent-safe via `threading.Lock`; env-var names and `default_kid` / `default_alg` are overridable as kwargs); `hash_operator_token` (sha256 hex of plaintext `opc_...` — for merchants persisting `operator_token_id` to their own DB without ever storing the plaintext). |
|
|
77
|
+
| `agentscore_commerce.identity` (package level) | Re-exports the denial helpers: `denial_reason_status`, `denial_reason_to_body`, `build_signer_mismatch_body`, `build_contact_support_next_steps`, `verification_agent_instructions`, `is_fixable_denial`, `FIXABLE_DENIAL_REASONS`. The per-framework adapter modules also expose `get_gate_quota_info(request)` for surfacing X-RateLimit info from gate state. Also re-exports the per-product policy helpers: `PolicyBlock`, `GateResult`, `EnforcementMode`, `IdentityStatus`, `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) — for multi-product merchants where each product carries its own compliance config: hard gate vs soft vs none, per-product shipping allowlists. Key + token helpers: `load_ucp_signing_key_from_env` (cached env-driven loader for the UCP signing key — reads `UCP_SIGNING_KEY_JWK_PRIVATE` JSON JWK, detects alg from shape, falls back to ephemeral when unset, sanitizes errors so key bytes never reach logs, concurrent-safe via `threading.Lock`; env-var names and `default_kid` / `default_alg` are overridable as kwargs); `hash_operator_token` (sha256 hex of plaintext `opc_...` — for merchants persisting `operator_token_id` to their own DB without ever storing the plaintext); `extract_owner_scope(headers) -> OwnerScope` (canonical owner-identity extractor for caller-scoped resource queries — reads `X-Wallet-Address` / `X-Operator-Token`, hashes the token so plaintext never leaves the request); `has_payment_header` / `has_x402_header` / `has_mppx_header` (request discriminators — any-credential vs x402 vs MPP); `default_read_only_on_denied(reason)` (canonical `on_denied` for read-only resource gates: 401 + `Cache-Control: no-store` while still spreading `denial_reason_to_body`. Returns a `DefaultOnDeniedResult(body, status, headers)`; FastAPI / Flask / aiohttp / Sanic `on_denied` callbacks accept an optional 3-tuple `(body, status, headers)` to carry headers through; wrap with `lambda req, reason: (lambda r: (r.body, r.status, r.headers or {}))(default_read_only_on_denied(reason))`). |
|
|
76
78
|
| `agentscore_commerce.payment` | `networks`, `USDC`, `rails` registries; `payment_directive`, `build_payment_directive`, `www_authenticate_header`, `payment_required_header`, `alias_amount_fields` (v1↔v2 amount field shim that emits both `amount` and `maxAmountRequired` so v1-only x402 parsers like Coinbase awal can read v2 bodies), `settlement_override_header`, `dispatch_settlement_by_network`, `extract_payment_signer` (accepts positional `x402_payment_header` AND/OR `authorization_header=` kwarg; recovers signer from x402 EIP-3009 `payload.authorization.from` OR MPP `Authorization: Payment <base64>` `did:pkh:eip155:<chain>:<addr>` / `did:pkh:solana:<genesis>:<addr>` source DID), `detect_rail_from_headers` (returns `"x402"` / `"mpp"` / `None` from inbound headers), `register_x402_schemes_v1_v2`; drop-in x402 helpers: `validate_x402_network_config` (boot-time guard), `verify_x402_request` (parse + validate inbound X-Payment), `process_x402_settle` (verify-then-settle with one call), `classify_x402_settle_result` (maps the tagged settle result to a recommended HTTP status / code / next_steps so merchants get a controlled envelope without coupling to facilitator-specific error text), `classify_orchestration_error` (same `ClassifiedX402Error` shape but for uncaught exceptions thrown elsewhere in the orchestration; returns `None` for unknown errors so merchants rethrow instead of swallowing); `zero_amount_carve_out` (skip CDP / pympp upstream verify+settle for $0 settles where the upstream rejects value=0 payloads; parses the credential, lifts signer + network, returns a `ZeroSettleResult` shaped identically to the success path so callers branch on rail, not on result shape); `usd_to_atomic` (Decimal-based USD → atomic int, ROUND_HALF_UP — for Tempo / Solana / Base USDC amount construction). |
|
|
77
79
|
| `agentscore_commerce.discovery` | `is_discovery_probe_request`, `build_discovery_probe_response` (with optional `x402_sample` for x402-aware crawlers like `awal x402 details`), `sample_x402_accept_for_network` (USDC sample-accept builder for known CAIP-2 networks), `build_well_known_mpp`, `build_llms_txt` + `llms_txt_identity_section` + `llms_txt_payment_section` (compact + verbose modes), `build_skill_md` (Claude-Skill-compatible `/skill.md` agent-discovery manifest; strictly agent-facing data only, no internal posture), `build_redemption_skill_md` (delivery-neutral redemption-code template — printed mailers, emailed codes, API trial credits all covered; `endpoint_path`/`delivery_intro`/`body_shape`/`body_rules`/`extra_recovery_rows` overrides for non-goods shapes), `build_merchant_index_json` (canonical `/` discovery body), `standard_endpoint_descriptions(kind=)` (canonical method+path → description map for goods vs api merchants; optional `include_order_status_route` for goods), `build_success_next_steps` (universal Passport-active success block), `build_agentscore_onboarding_steps` (canonical skill.md onboarding for goods or API merchants), `agentscore_openapi_snippets`, `build_bazaar_discovery_payload`, `NoindexNonDiscoveryMiddleware` (ASGI middleware emitting `X-Robots-Tag: noindex` on every path except the agent-discovery surfaces; pure helpers `is_discovery_path` + `DEFAULT_DISCOVERY_PATHS` for non-ASGI frameworks). Plus the UCP/JWKS publish surface: `build_signed_ucp_response`, `build_signed_jwks_response`, `well_known_preflight_response`, `default_a2a_services`, `bootstrap_ucp_signing_key`, framework-neutral `SignedDiscoveryResponse` + per-framework wrappers `signed_response_{fastapi,flask,django,aiohttp,sanic}`. |
|
|
78
80
|
| `agentscore_commerce.challenge` | `build_402_body`, `build_accepted_methods`, `build_identity_metadata` (auto-attached by `Checkout` when an inbound `X-Wallet-Address` header is present), `build_how_to_pay`, `build_agent_instructions` (auto-emits per-rail `compatible_clients`: smoke-verified CLIs the agent should use; vendor override supported; pure helper `compatible_clients_by_rails(rails)` returns the same map for vendors building custom 402s), `build_pricing_block` (cents to dollar-string with optional shipping/tax), `first_encounter_agent_memory` (cross-merchant hint, returns the canonical block or `None` based on a per-merchant first-seen flag), `Receipt` + `ReceiptNextSteps` + `ProductInfo` + `ShippingAddress` (canonical 200-receipt dataclasses — universal across goods + API merchants); `respond_402`, a drop-in 402 emit that preserves pympp's `WWW-Authenticate` and layers x402's `PAYMENT-REQUIRED`. `build_validation_error`: structured 4xx body builder (`{error: {code, message}, required_fields?, example_body?, next_steps?, ...extra}`) so vendors compose body shapes by name instead of inlining at every validation site. |
|
|
79
|
-
| `agentscore_commerce.stripe_multichain` | `create_multichain_payment_intent` (returns `MultichainPaymentIntentResult(payment_intent_id, deposit_addresses)`; read `result.deposit_addresses[network]` directly), `simulate_crypto_deposit`; `create_pi_cache` (TTL'd PI / deposit-address cache, Redis-backed when `redis_url` set, in-memory otherwise), `simulate_deposit_if_test_mode` (gates on `sk_test_` and looks up the PI for you), `STRIPE_TEST_TX_HASH_SUCCESS` / `STRIPE_TEST_TX_HASH_FAILED` constants. Peer dep on `stripe`. |
|
|
81
|
+
| `agentscore_commerce.stripe_multichain` | `create_multichain_payment_intent` (returns `MultichainPaymentIntentResult(payment_intent_id, deposit_addresses)`; read `result.deposit_addresses[network]` directly), `create_pay_to_address_from_stripe_pi(authorization_header=, amount_cents=, stripe=, pi_cache=, networks=, metadata=, order_id=, preferred_network=)` — per-order payTo resolver: on the settle leg, reuses the buyer's signed-against payTo from the MPP credential (after `pi_cache.has_address` check); on the discovery leg, mints a fresh PI and caches it. `simulate_crypto_deposit`; `create_pi_cache` (TTL'd PI / deposit-address cache, Redis-backed when `redis_url` set, in-memory otherwise), `simulate_deposit_if_test_mode` (gates on `sk_test_` and looks up the PI for you), `STRIPE_TEST_TX_HASH_SUCCESS` / `STRIPE_TEST_TX_HASH_FAILED` constants. Peer dep on `stripe`. |
|
|
80
82
|
| `agentscore_commerce.api` | Everything from `agentscore-py` re-exported in one place: `AgentScore` + `AgentScoreError`, `AGENTSCORE_TEST_ADDRESSES` + `is_agentscore_test_address`. **Don't add `agentscore-py` as a separate dep**: the two can drift versions and cause subtle type mismatches. |
|
|
83
|
+
| `agentscore_commerce.middleware.{fastapi,flask,django,aiohttp,sanic,asgi}` | Framework-specific rate-limit middleware. FastAPI: `rate_limit_fastapi(...)` (FastAPI dependency) plus the ASGI `RateLimitMiddleware` re-export. Flask: `rate_limit_flask(app, ...)` installer. Django: class-based async `RateLimitMiddleware` configured via `settings.AGENTSCORE_RATE_LIMIT`. aiohttp: `rate_limit_aiohttp(...)` middleware factory. Sanic: `rate_limit_sanic(app, ...)` installer. `asgi.RateLimitMiddleware` works with any starlette-compatible app. Shared options: `window_seconds` (default 60), `max_requests` (default 60), `key_resolver` (default first hop of `x-forwarded-for`), `redis_url` (lazy-imports `redis.asyncio` when set, in-memory `dict` fallback otherwise), `key_prefix`. `redis` is an optional peer dep (install via the `redis` extra). |
|
|
81
84
|
|
|
82
85
|
## Quick start (FastAPI)
|
|
83
86
|
|
|
87
|
+
### Rate limiting
|
|
88
|
+
|
|
89
|
+
Mount globally before any payment route so probe and settle legs share the same bucket. Defaults: 60 req / 60 s / IP. Redis when `REDIS_URL` is set, in-memory fallback otherwise.
|
|
90
|
+
|
|
91
|
+
```python
|
|
92
|
+
from fastapi import FastAPI
|
|
93
|
+
from agentscore_commerce.middleware.asgi import RateLimitMiddleware
|
|
94
|
+
|
|
95
|
+
app = FastAPI()
|
|
96
|
+
app.add_middleware(RateLimitMiddleware, max_requests=60, window_seconds=60)
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Same factory shape per framework: `rate_limit_flask(app, ...)`, `rate_limit_aiohttp(...)`, `rate_limit_sanic(app, ...)`, Django's `RateLimitMiddleware` class in `MIDDLEWARE`, and `rate_limit_fastapi(...)` for a `Depends`-able per-route variant. Override `max_requests` / `window_seconds` / `key_resolver` / `redis_url` / `key_prefix` as needed.
|
|
100
|
+
|
|
101
|
+
### Identity gate
|
|
102
|
+
|
|
84
103
|
```python
|
|
85
104
|
from fastapi import Depends, FastAPI, Request
|
|
86
105
|
from agentscore_commerce.identity.fastapi import (
|
|
@@ -103,13 +122,10 @@ _gate = AgentScoreGate(
|
|
|
103
122
|
# Anonymous discovery (no payment header) flows through to the handler so any spec-
|
|
104
123
|
# compliant x402 wallet can read the 402 challenge with rails + pricing without first
|
|
105
124
|
# proving identity. Identity is verified at settle time on the retry leg.
|
|
125
|
+
from agentscore_commerce.payment import has_payment_header
|
|
126
|
+
|
|
106
127
|
async def gate_on_settle(request: Request) -> None:
|
|
107
|
-
|
|
108
|
-
request.headers.get("payment-signature")
|
|
109
|
-
or request.headers.get("x-payment")
|
|
110
|
-
or (request.headers.get("authorization") or "").startswith("Payment ")
|
|
111
|
-
)
|
|
112
|
-
if not has_payment_header:
|
|
128
|
+
if not has_payment_header(request):
|
|
113
129
|
return None
|
|
114
130
|
return await _gate(request)
|
|
115
131
|
|
|
@@ -204,6 +220,12 @@ async def purchase(request: Request):
|
|
|
204
220
|
|
|
205
221
|
The 402 body Checkout emits auto-attaches `identity_mode` + `required_signer` + `signer_constraint` (and `linked_wallets` when the gate populated them) when an inbound `X-Wallet-Address` header is present — so agents self-correct at discovery instead of at the 403 retry.
|
|
206
222
|
|
|
223
|
+
For **variable-cost pay-per-result** endpoints (per-result search, per-token LLM, per-byte transcoding), reach for `compute_first_checkout` — same config shape, but the probe leg runs the work, caches by body content-hash, and emits a 402 with the EXACT computed price. The retry pays that exact amount and receives the cached body. 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. Tradeoff: the work runs on the unpaid probe leg, so mount `rate_limit_fastapi` (from `agentscore_commerce.middleware.fastapi`) globally — it's load-bearing. See `examples/compute_first_merchant.py`.
|
|
224
|
+
|
|
225
|
+
For the `on_denied` hook on Checkout's gate config, `create_default_on_denied(merchant_name=, support_email=, ...)` returns the canonical denial callback that handles `wallet_signer_mismatch` / `wallet_not_trusted` unfixable fallback / `payment_required` / `token_expired` / `invalid_credential` / `api_error`. Merchants override `wallet_not_trusted_message` / `payment_required_message` / `support_context` for vendor-specific copy and keep their own merchant-specific branches (e.g. wine merchants add a fixable-denial-with-session branch on top).
|
|
226
|
+
|
|
227
|
+
`build_default_checkout_rails(tempo=, x402_base=, solana_mpp=, stripe=)` builds the canonical four-rail `rails` dict so merchants pass per-rail overrides instead of redeclaring the recipient sentinel + network/chain_id/token boilerplate. Flipping `network` alone is enough: Base Sepolia derives Sepolia USDC + chain_id 84532, Solana devnet derives the devnet USDC mint. 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=, ...)` builds the per-call mppx intent list. The helper auto-drops `stripe/charge` (with a one-time `logging.warning`) when `amount_usd < 0.50` since Stripe's fixed ~$0.30 fee makes sub-50-cent charges unprofitable; sub-50-cent APIs pass `include_stripe=False` explicitly to silence the warning. `simulate_deposit_for_outcome(outcome=, deposit_address=, get_payment_intent_id=, stripe_secret_key=)` dispatches the Stripe testnet simulator from `on_settled` based on the rail family (no per-merchant rail switch needed).
|
|
228
|
+
|
|
207
229
|
## Payment helpers
|
|
208
230
|
|
|
209
231
|
```python
|
|
@@ -279,7 +301,7 @@ body = build_402_body(Build402BodyInput(
|
|
|
279
301
|
))
|
|
280
302
|
```
|
|
281
303
|
|
|
282
|
-
`build_pricing_block` handles cents → dollar-string (with optional shipping). Pass `discount_cents` for redemption codes / coupons: `subtotal` stays the list price, the block surfaces `discount` as a dollar-string, and `total` becomes `subtotal + tax + shipping - discount` (floored at 0). `pricing_result` accepts the same `discount_cents` and propagates it to `block.discount` so agents reading the 402 see the savings line. `first_encounter_agent_memory` returns the canonical hint or `None` based on a per-merchant first-seen flag. `Receipt` (plus `ReceiptNextSteps`, `ProductInfo`, `ShippingAddress`) is a universal dataclass for the post-settlement 200 response shape — goods merchants populate the shipping/fulfillment/tracking slots, API merchants fill only the universal fields (id, created_at, pricing, payment_status, next_steps).
|
|
304
|
+
`build_pricing_block` handles cents → dollar-string (with optional shipping). Pass `discount_cents` for redemption codes / coupons: `subtotal` stays the list price, the block surfaces `discount` as a dollar-string, and `total` becomes `subtotal + tax + shipping - discount` (floored at 0). `pricing_result` accepts the same `discount_cents` and propagates it to `block.discount` so agents reading the 402 see the savings line. Pass `decimals: N` (default `2`) on either helper for sub-cent unit pricing — e.g. `decimals=4` advertises `$0.0005`-precision instead of rounding to two decimals. Set `decimals` on `PricingResult` and the SDK threads it through `build_how_to_pay`, `build_pricing_block`, and the x402 settle `price` string automatically; the cents inputs accept floats under that mode (per-token / per-byte unit pricing). `first_encounter_agent_memory` returns the canonical hint or `None` based on a per-merchant first-seen flag. `Receipt` (plus `ReceiptNextSteps`, `ProductInfo`, `ShippingAddress`) is a universal dataclass for the post-settlement 200 response shape — goods merchants populate the shipping/fulfillment/tracking slots, API merchants fill only the universal fields (id, created_at, pricing, payment_status, next_steps).
|
|
283
305
|
|
|
284
306
|
### Idempotency-key + multi-rail header bundle
|
|
285
307
|
|
|
@@ -26,15 +26,32 @@ pip install 'agentscore-commerce[fastapi,x402,coinbase]'
|
|
|
26
26
|
|---|---|
|
|
27
27
|
| `agentscore_commerce` (top-level) | `Checkout` orchestrator + `CheckoutContext` + `CheckoutGateConfig` + `CheckoutValidationError` + `DiscoveryProbeConfig` + `SettleOutcome` + `MppxComposeOutcome` + `PricingResult` (the 2.0 high-level surface: one config object, hooks for pre_validate/compute_pricing/on_settled/mint_recipients/compose_mppx, auto-derived x402+mppx 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}`); `pricing_result` (factory: cents-denominated → typed `PricingResult` with embedded `PricingBlock`); `validation_response_{fastapi,flask,django,aiohttp,sanic}` (per-framework 4xx envelope wrappers); `make_mppx_compose_hook` (canonical pympp compose adapter). |
|
|
28
28
|
| `agentscore_commerce.identity.{fastapi,flask,django,aiohttp,sanic,middleware}` | Trust gate middleware: KYC, sanctions (account name + signer wallet), age, jurisdiction. `AgentScoreGate(...)` (or `agentscore_gate(app, ...)` on Flask/Sanic), `get_agentscore_data(...)`, `capture_wallet(...)`, `get_signer_verdict(...)`. The gate extracts the payment signer pre-evaluate and passes it to `/v1/assess`, so the API composes both wallet-binding (`signer_match`) and OFAC SDN wallet-address (`signer_sanctions`) verdicts on one round trip. |
|
|
29
|
-
| `agentscore_commerce.identity` (package level) | Re-exports the denial helpers: `denial_reason_status`, `denial_reason_to_body`, `build_signer_mismatch_body`, `build_contact_support_next_steps`, `verification_agent_instructions`, `is_fixable_denial`, `FIXABLE_DENIAL_REASONS`. The per-framework adapter modules also expose `get_gate_quota_info(request)` for surfacing X-RateLimit info from gate state. Also re-exports the per-product policy helpers: `PolicyBlock`, `GateResult`, `EnforcementMode`, `IdentityStatus`, `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) — for multi-product merchants where each product carries its own compliance config: hard gate vs soft vs none, per-product shipping allowlists. Key + token helpers: `load_ucp_signing_key_from_env` (cached env-driven loader for the UCP signing key — reads `UCP_SIGNING_KEY_JWK_PRIVATE` JSON JWK, detects alg from shape, falls back to ephemeral when unset, sanitizes errors so key bytes never reach logs, concurrent-safe via `threading.Lock`; env-var names and `default_kid` / `default_alg` are overridable as kwargs); `hash_operator_token` (sha256 hex of plaintext `opc_...` — for merchants persisting `operator_token_id` to their own DB without ever storing the plaintext). |
|
|
29
|
+
| `agentscore_commerce.identity` (package level) | Re-exports the denial helpers: `denial_reason_status`, `denial_reason_to_body`, `build_signer_mismatch_body`, `build_contact_support_next_steps`, `verification_agent_instructions`, `is_fixable_denial`, `FIXABLE_DENIAL_REASONS`. The per-framework adapter modules also expose `get_gate_quota_info(request)` for surfacing X-RateLimit info from gate state. Also re-exports the per-product policy helpers: `PolicyBlock`, `GateResult`, `EnforcementMode`, `IdentityStatus`, `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) — for multi-product merchants where each product carries its own compliance config: hard gate vs soft vs none, per-product shipping allowlists. Key + token helpers: `load_ucp_signing_key_from_env` (cached env-driven loader for the UCP signing key — reads `UCP_SIGNING_KEY_JWK_PRIVATE` JSON JWK, detects alg from shape, falls back to ephemeral when unset, sanitizes errors so key bytes never reach logs, concurrent-safe via `threading.Lock`; env-var names and `default_kid` / `default_alg` are overridable as kwargs); `hash_operator_token` (sha256 hex of plaintext `opc_...` — for merchants persisting `operator_token_id` to their own DB without ever storing the plaintext); `extract_owner_scope(headers) -> OwnerScope` (canonical owner-identity extractor for caller-scoped resource queries — reads `X-Wallet-Address` / `X-Operator-Token`, hashes the token so plaintext never leaves the request); `has_payment_header` / `has_x402_header` / `has_mppx_header` (request discriminators — any-credential vs x402 vs MPP); `default_read_only_on_denied(reason)` (canonical `on_denied` for read-only resource gates: 401 + `Cache-Control: no-store` while still spreading `denial_reason_to_body`. Returns a `DefaultOnDeniedResult(body, status, headers)`; FastAPI / Flask / aiohttp / Sanic `on_denied` callbacks accept an optional 3-tuple `(body, status, headers)` to carry headers through; wrap with `lambda req, reason: (lambda r: (r.body, r.status, r.headers or {}))(default_read_only_on_denied(reason))`). |
|
|
30
30
|
| `agentscore_commerce.payment` | `networks`, `USDC`, `rails` registries; `payment_directive`, `build_payment_directive`, `www_authenticate_header`, `payment_required_header`, `alias_amount_fields` (v1↔v2 amount field shim that emits both `amount` and `maxAmountRequired` so v1-only x402 parsers like Coinbase awal can read v2 bodies), `settlement_override_header`, `dispatch_settlement_by_network`, `extract_payment_signer` (accepts positional `x402_payment_header` AND/OR `authorization_header=` kwarg; recovers signer from x402 EIP-3009 `payload.authorization.from` OR MPP `Authorization: Payment <base64>` `did:pkh:eip155:<chain>:<addr>` / `did:pkh:solana:<genesis>:<addr>` source DID), `detect_rail_from_headers` (returns `"x402"` / `"mpp"` / `None` from inbound headers), `register_x402_schemes_v1_v2`; drop-in x402 helpers: `validate_x402_network_config` (boot-time guard), `verify_x402_request` (parse + validate inbound X-Payment), `process_x402_settle` (verify-then-settle with one call), `classify_x402_settle_result` (maps the tagged settle result to a recommended HTTP status / code / next_steps so merchants get a controlled envelope without coupling to facilitator-specific error text), `classify_orchestration_error` (same `ClassifiedX402Error` shape but for uncaught exceptions thrown elsewhere in the orchestration; returns `None` for unknown errors so merchants rethrow instead of swallowing); `zero_amount_carve_out` (skip CDP / pympp upstream verify+settle for $0 settles where the upstream rejects value=0 payloads; parses the credential, lifts signer + network, returns a `ZeroSettleResult` shaped identically to the success path so callers branch on rail, not on result shape); `usd_to_atomic` (Decimal-based USD → atomic int, ROUND_HALF_UP — for Tempo / Solana / Base USDC amount construction). |
|
|
31
31
|
| `agentscore_commerce.discovery` | `is_discovery_probe_request`, `build_discovery_probe_response` (with optional `x402_sample` for x402-aware crawlers like `awal x402 details`), `sample_x402_accept_for_network` (USDC sample-accept builder for known CAIP-2 networks), `build_well_known_mpp`, `build_llms_txt` + `llms_txt_identity_section` + `llms_txt_payment_section` (compact + verbose modes), `build_skill_md` (Claude-Skill-compatible `/skill.md` agent-discovery manifest; strictly agent-facing data only, no internal posture), `build_redemption_skill_md` (delivery-neutral redemption-code template — printed mailers, emailed codes, API trial credits all covered; `endpoint_path`/`delivery_intro`/`body_shape`/`body_rules`/`extra_recovery_rows` overrides for non-goods shapes), `build_merchant_index_json` (canonical `/` discovery body), `standard_endpoint_descriptions(kind=)` (canonical method+path → description map for goods vs api merchants; optional `include_order_status_route` for goods), `build_success_next_steps` (universal Passport-active success block), `build_agentscore_onboarding_steps` (canonical skill.md onboarding for goods or API merchants), `agentscore_openapi_snippets`, `build_bazaar_discovery_payload`, `NoindexNonDiscoveryMiddleware` (ASGI middleware emitting `X-Robots-Tag: noindex` on every path except the agent-discovery surfaces; pure helpers `is_discovery_path` + `DEFAULT_DISCOVERY_PATHS` for non-ASGI frameworks). Plus the UCP/JWKS publish surface: `build_signed_ucp_response`, `build_signed_jwks_response`, `well_known_preflight_response`, `default_a2a_services`, `bootstrap_ucp_signing_key`, framework-neutral `SignedDiscoveryResponse` + per-framework wrappers `signed_response_{fastapi,flask,django,aiohttp,sanic}`. |
|
|
32
32
|
| `agentscore_commerce.challenge` | `build_402_body`, `build_accepted_methods`, `build_identity_metadata` (auto-attached by `Checkout` when an inbound `X-Wallet-Address` header is present), `build_how_to_pay`, `build_agent_instructions` (auto-emits per-rail `compatible_clients`: smoke-verified CLIs the agent should use; vendor override supported; pure helper `compatible_clients_by_rails(rails)` returns the same map for vendors building custom 402s), `build_pricing_block` (cents to dollar-string with optional shipping/tax), `first_encounter_agent_memory` (cross-merchant hint, returns the canonical block or `None` based on a per-merchant first-seen flag), `Receipt` + `ReceiptNextSteps` + `ProductInfo` + `ShippingAddress` (canonical 200-receipt dataclasses — universal across goods + API merchants); `respond_402`, a drop-in 402 emit that preserves pympp's `WWW-Authenticate` and layers x402's `PAYMENT-REQUIRED`. `build_validation_error`: structured 4xx body builder (`{error: {code, message}, required_fields?, example_body?, next_steps?, ...extra}`) so vendors compose body shapes by name instead of inlining at every validation site. |
|
|
33
|
-
| `agentscore_commerce.stripe_multichain` | `create_multichain_payment_intent` (returns `MultichainPaymentIntentResult(payment_intent_id, deposit_addresses)`; read `result.deposit_addresses[network]` directly), `simulate_crypto_deposit`; `create_pi_cache` (TTL'd PI / deposit-address cache, Redis-backed when `redis_url` set, in-memory otherwise), `simulate_deposit_if_test_mode` (gates on `sk_test_` and looks up the PI for you), `STRIPE_TEST_TX_HASH_SUCCESS` / `STRIPE_TEST_TX_HASH_FAILED` constants. Peer dep on `stripe`. |
|
|
33
|
+
| `agentscore_commerce.stripe_multichain` | `create_multichain_payment_intent` (returns `MultichainPaymentIntentResult(payment_intent_id, deposit_addresses)`; read `result.deposit_addresses[network]` directly), `create_pay_to_address_from_stripe_pi(authorization_header=, amount_cents=, stripe=, pi_cache=, networks=, metadata=, order_id=, preferred_network=)` — per-order payTo resolver: on the settle leg, reuses the buyer's signed-against payTo from the MPP credential (after `pi_cache.has_address` check); on the discovery leg, mints a fresh PI and caches it. `simulate_crypto_deposit`; `create_pi_cache` (TTL'd PI / deposit-address cache, Redis-backed when `redis_url` set, in-memory otherwise), `simulate_deposit_if_test_mode` (gates on `sk_test_` and looks up the PI for you), `STRIPE_TEST_TX_HASH_SUCCESS` / `STRIPE_TEST_TX_HASH_FAILED` constants. Peer dep on `stripe`. |
|
|
34
34
|
| `agentscore_commerce.api` | Everything from `agentscore-py` re-exported in one place: `AgentScore` + `AgentScoreError`, `AGENTSCORE_TEST_ADDRESSES` + `is_agentscore_test_address`. **Don't add `agentscore-py` as a separate dep**: the two can drift versions and cause subtle type mismatches. |
|
|
35
|
+
| `agentscore_commerce.middleware.{fastapi,flask,django,aiohttp,sanic,asgi}` | Framework-specific rate-limit middleware. FastAPI: `rate_limit_fastapi(...)` (FastAPI dependency) plus the ASGI `RateLimitMiddleware` re-export. Flask: `rate_limit_flask(app, ...)` installer. Django: class-based async `RateLimitMiddleware` configured via `settings.AGENTSCORE_RATE_LIMIT`. aiohttp: `rate_limit_aiohttp(...)` middleware factory. Sanic: `rate_limit_sanic(app, ...)` installer. `asgi.RateLimitMiddleware` works with any starlette-compatible app. Shared options: `window_seconds` (default 60), `max_requests` (default 60), `key_resolver` (default first hop of `x-forwarded-for`), `redis_url` (lazy-imports `redis.asyncio` when set, in-memory `dict` fallback otherwise), `key_prefix`. `redis` is an optional peer dep (install via the `redis` extra). |
|
|
35
36
|
|
|
36
37
|
## Quick start (FastAPI)
|
|
37
38
|
|
|
39
|
+
### Rate limiting
|
|
40
|
+
|
|
41
|
+
Mount globally before any payment route so probe and settle legs share the same bucket. Defaults: 60 req / 60 s / IP. Redis when `REDIS_URL` is set, in-memory fallback otherwise.
|
|
42
|
+
|
|
43
|
+
```python
|
|
44
|
+
from fastapi import FastAPI
|
|
45
|
+
from agentscore_commerce.middleware.asgi import RateLimitMiddleware
|
|
46
|
+
|
|
47
|
+
app = FastAPI()
|
|
48
|
+
app.add_middleware(RateLimitMiddleware, max_requests=60, window_seconds=60)
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Same factory shape per framework: `rate_limit_flask(app, ...)`, `rate_limit_aiohttp(...)`, `rate_limit_sanic(app, ...)`, Django's `RateLimitMiddleware` class in `MIDDLEWARE`, and `rate_limit_fastapi(...)` for a `Depends`-able per-route variant. Override `max_requests` / `window_seconds` / `key_resolver` / `redis_url` / `key_prefix` as needed.
|
|
52
|
+
|
|
53
|
+
### Identity gate
|
|
54
|
+
|
|
38
55
|
```python
|
|
39
56
|
from fastapi import Depends, FastAPI, Request
|
|
40
57
|
from agentscore_commerce.identity.fastapi import (
|
|
@@ -57,13 +74,10 @@ _gate = AgentScoreGate(
|
|
|
57
74
|
# Anonymous discovery (no payment header) flows through to the handler so any spec-
|
|
58
75
|
# compliant x402 wallet can read the 402 challenge with rails + pricing without first
|
|
59
76
|
# proving identity. Identity is verified at settle time on the retry leg.
|
|
77
|
+
from agentscore_commerce.payment import has_payment_header
|
|
78
|
+
|
|
60
79
|
async def gate_on_settle(request: Request) -> None:
|
|
61
|
-
|
|
62
|
-
request.headers.get("payment-signature")
|
|
63
|
-
or request.headers.get("x-payment")
|
|
64
|
-
or (request.headers.get("authorization") or "").startswith("Payment ")
|
|
65
|
-
)
|
|
66
|
-
if not has_payment_header:
|
|
80
|
+
if not has_payment_header(request):
|
|
67
81
|
return None
|
|
68
82
|
return await _gate(request)
|
|
69
83
|
|
|
@@ -158,6 +172,12 @@ async def purchase(request: Request):
|
|
|
158
172
|
|
|
159
173
|
The 402 body Checkout emits auto-attaches `identity_mode` + `required_signer` + `signer_constraint` (and `linked_wallets` when the gate populated them) when an inbound `X-Wallet-Address` header is present — so agents self-correct at discovery instead of at the 403 retry.
|
|
160
174
|
|
|
175
|
+
For **variable-cost pay-per-result** endpoints (per-result search, per-token LLM, per-byte transcoding), reach for `compute_first_checkout` — same config shape, but the probe leg runs the work, caches by body content-hash, and emits a 402 with the EXACT computed price. The retry pays that exact amount and receives the cached body. 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. Tradeoff: the work runs on the unpaid probe leg, so mount `rate_limit_fastapi` (from `agentscore_commerce.middleware.fastapi`) globally — it's load-bearing. See `examples/compute_first_merchant.py`.
|
|
176
|
+
|
|
177
|
+
For the `on_denied` hook on Checkout's gate config, `create_default_on_denied(merchant_name=, support_email=, ...)` returns the canonical denial callback that handles `wallet_signer_mismatch` / `wallet_not_trusted` unfixable fallback / `payment_required` / `token_expired` / `invalid_credential` / `api_error`. Merchants override `wallet_not_trusted_message` / `payment_required_message` / `support_context` for vendor-specific copy and keep their own merchant-specific branches (e.g. wine merchants add a fixable-denial-with-session branch on top).
|
|
178
|
+
|
|
179
|
+
`build_default_checkout_rails(tempo=, x402_base=, solana_mpp=, stripe=)` builds the canonical four-rail `rails` dict so merchants pass per-rail overrides instead of redeclaring the recipient sentinel + network/chain_id/token boilerplate. Flipping `network` alone is enough: Base Sepolia derives Sepolia USDC + chain_id 84532, Solana devnet derives the devnet USDC mint. 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=, ...)` builds the per-call mppx intent list. The helper auto-drops `stripe/charge` (with a one-time `logging.warning`) when `amount_usd < 0.50` since Stripe's fixed ~$0.30 fee makes sub-50-cent charges unprofitable; sub-50-cent APIs pass `include_stripe=False` explicitly to silence the warning. `simulate_deposit_for_outcome(outcome=, deposit_address=, get_payment_intent_id=, stripe_secret_key=)` dispatches the Stripe testnet simulator from `on_settled` based on the rail family (no per-merchant rail switch needed).
|
|
180
|
+
|
|
161
181
|
## Payment helpers
|
|
162
182
|
|
|
163
183
|
```python
|
|
@@ -233,7 +253,7 @@ body = build_402_body(Build402BodyInput(
|
|
|
233
253
|
))
|
|
234
254
|
```
|
|
235
255
|
|
|
236
|
-
`build_pricing_block` handles cents → dollar-string (with optional shipping). Pass `discount_cents` for redemption codes / coupons: `subtotal` stays the list price, the block surfaces `discount` as a dollar-string, and `total` becomes `subtotal + tax + shipping - discount` (floored at 0). `pricing_result` accepts the same `discount_cents` and propagates it to `block.discount` so agents reading the 402 see the savings line. `first_encounter_agent_memory` returns the canonical hint or `None` based on a per-merchant first-seen flag. `Receipt` (plus `ReceiptNextSteps`, `ProductInfo`, `ShippingAddress`) is a universal dataclass for the post-settlement 200 response shape — goods merchants populate the shipping/fulfillment/tracking slots, API merchants fill only the universal fields (id, created_at, pricing, payment_status, next_steps).
|
|
256
|
+
`build_pricing_block` handles cents → dollar-string (with optional shipping). Pass `discount_cents` for redemption codes / coupons: `subtotal` stays the list price, the block surfaces `discount` as a dollar-string, and `total` becomes `subtotal + tax + shipping - discount` (floored at 0). `pricing_result` accepts the same `discount_cents` and propagates it to `block.discount` so agents reading the 402 see the savings line. Pass `decimals: N` (default `2`) on either helper for sub-cent unit pricing — e.g. `decimals=4` advertises `$0.0005`-precision instead of rounding to two decimals. Set `decimals` on `PricingResult` and the SDK threads it through `build_how_to_pay`, `build_pricing_block`, and the x402 settle `price` string automatically; the cents inputs accept floats under that mode (per-token / per-byte unit pricing). `first_encounter_agent_memory` returns the canonical hint or `None` based on a per-merchant first-seen flag. `Receipt` (plus `ReceiptNextSteps`, `ProductInfo`, `ShippingAddress`) is a universal dataclass for the post-settlement 200 response shape — goods merchants populate the shipping/fulfillment/tracking slots, API merchants fill only the universal fields (id, created_at, pricing, payment_status, next_steps).
|
|
237
257
|
|
|
238
258
|
### Idempotency-key + multi-rail header bundle
|
|
239
259
|
|
|
@@ -20,7 +20,6 @@ from agentscore_commerce.checkout import (
|
|
|
20
20
|
CheckoutRailSpec,
|
|
21
21
|
CheckoutRequest,
|
|
22
22
|
CheckoutResult,
|
|
23
|
-
CheckoutValidationError,
|
|
24
23
|
DiscoveryProbeConfig,
|
|
25
24
|
MppxComposeOutcome,
|
|
26
25
|
PricingResult,
|
|
@@ -34,12 +33,29 @@ from agentscore_commerce.checkout import (
|
|
|
34
33
|
validation_response_flask,
|
|
35
34
|
validation_response_sanic,
|
|
36
35
|
)
|
|
36
|
+
from agentscore_commerce.checkout_compute_first import (
|
|
37
|
+
ComputeFirstCheckout,
|
|
38
|
+
ComputeFirstMintContext,
|
|
39
|
+
ComputeFirstMppContext,
|
|
40
|
+
ComputeFirstMppResult,
|
|
41
|
+
ComputeFirstRails,
|
|
42
|
+
ComputeFirstRequest,
|
|
43
|
+
ComputeFirstSettledContext,
|
|
44
|
+
ComputeFirstWorkContext,
|
|
45
|
+
MintedRecipients,
|
|
46
|
+
SuccessBodyArgs,
|
|
47
|
+
WorkOutcome,
|
|
48
|
+
compute_first_checkout,
|
|
49
|
+
)
|
|
37
50
|
from agentscore_commerce.checkout_hooks import make_mppx_compose_hook
|
|
51
|
+
from agentscore_commerce.errors import CheckoutValidationError
|
|
38
52
|
|
|
39
53
|
# Re-export the most commonly used helpers at the package root so consumers
|
|
40
54
|
# don't have to remember which submodule each one lives in. Mirrors node's
|
|
41
55
|
# top-level `index.ts` surface; submodule imports still work for power users.
|
|
42
56
|
from agentscore_commerce.identity import (
|
|
57
|
+
A2A_DEFAULT_TRANSPORT,
|
|
58
|
+
A2A_PROTOCOL_VERSION,
|
|
43
59
|
AGENTSCORE_UCP_CAPABILITY,
|
|
44
60
|
FIXABLE_DENIAL_REASONS,
|
|
45
61
|
UCP_A2A_EXTENSION_URI,
|
|
@@ -55,12 +71,14 @@ from agentscore_commerce.identity import (
|
|
|
55
71
|
AgentScoreCore,
|
|
56
72
|
AgentScoreGatePolicy,
|
|
57
73
|
AssessResult,
|
|
74
|
+
DefaultOnDeniedResult,
|
|
58
75
|
DenialCode,
|
|
59
76
|
DenialReason,
|
|
60
77
|
EnforcementMode,
|
|
61
78
|
GateResult,
|
|
62
79
|
GeneratedUCPKey,
|
|
63
80
|
IdentityStatus,
|
|
81
|
+
OwnerScope,
|
|
64
82
|
PolicyBlock,
|
|
65
83
|
SignerVerdict,
|
|
66
84
|
UCPCapabilityBinding,
|
|
@@ -78,8 +96,12 @@ from agentscore_commerce.identity import (
|
|
|
78
96
|
build_jwks_response,
|
|
79
97
|
build_signer_mismatch_body,
|
|
80
98
|
build_ucp_profile,
|
|
99
|
+
build_verification_required_body,
|
|
100
|
+
create_default_on_denied,
|
|
101
|
+
default_read_only_on_denied,
|
|
81
102
|
denial_reason_status,
|
|
82
103
|
denial_reason_to_body,
|
|
104
|
+
extract_owner_scope,
|
|
83
105
|
generate_ucp_signing_key,
|
|
84
106
|
hash_operator_token,
|
|
85
107
|
is_fixable_denial,
|
|
@@ -106,12 +128,20 @@ from agentscore_commerce.payment import (
|
|
|
106
128
|
TempoRailSpec,
|
|
107
129
|
TempoSessionRailSpec,
|
|
108
130
|
X402BaseRailSpec,
|
|
131
|
+
build_default_checkout_rails,
|
|
132
|
+
build_mppx_compose_rails,
|
|
109
133
|
extract_payment_signer,
|
|
110
134
|
extract_signer_for_precheck,
|
|
111
135
|
format_usd_cents,
|
|
136
|
+
has_mppx_header,
|
|
137
|
+
has_payment_header,
|
|
138
|
+
has_x402_header,
|
|
139
|
+
is_evm_network,
|
|
140
|
+
is_solana_network,
|
|
112
141
|
load_solana_fee_payer,
|
|
113
142
|
read_x402_payment_header,
|
|
114
143
|
)
|
|
144
|
+
from agentscore_commerce.quote_cache import CachedQuote, QuoteCache, create_quote_cache
|
|
115
145
|
|
|
116
146
|
try:
|
|
117
147
|
__version__ = _pkg_version("agentscore-commerce")
|
|
@@ -122,6 +152,8 @@ except PackageNotFoundError:
|
|
|
122
152
|
__version__ = "0.0.0+local"
|
|
123
153
|
|
|
124
154
|
__all__ = [
|
|
155
|
+
"A2A_DEFAULT_TRANSPORT",
|
|
156
|
+
"A2A_PROTOCOL_VERSION",
|
|
125
157
|
"AGENTSCORE_UCP_CAPABILITY",
|
|
126
158
|
"FIXABLE_DENIAL_REASONS",
|
|
127
159
|
"UCP_A2A_EXTENSION_URI",
|
|
@@ -137,6 +169,7 @@ __all__ = [
|
|
|
137
169
|
"AgentScoreCore",
|
|
138
170
|
"AgentScoreGatePolicy",
|
|
139
171
|
"AssessResult",
|
|
172
|
+
"CachedQuote",
|
|
140
173
|
"Checkout",
|
|
141
174
|
"CheckoutContext",
|
|
142
175
|
"CheckoutGateConfig",
|
|
@@ -144,7 +177,16 @@ __all__ = [
|
|
|
144
177
|
"CheckoutRequest",
|
|
145
178
|
"CheckoutResult",
|
|
146
179
|
"CheckoutValidationError",
|
|
180
|
+
"ComputeFirstCheckout",
|
|
181
|
+
"ComputeFirstMintContext",
|
|
182
|
+
"ComputeFirstMppContext",
|
|
183
|
+
"ComputeFirstMppResult",
|
|
184
|
+
"ComputeFirstRails",
|
|
185
|
+
"ComputeFirstRequest",
|
|
186
|
+
"ComputeFirstSettledContext",
|
|
187
|
+
"ComputeFirstWorkContext",
|
|
147
188
|
"CreateSessionOnMissing",
|
|
189
|
+
"DefaultOnDeniedResult",
|
|
148
190
|
"DenialCode",
|
|
149
191
|
"DenialReason",
|
|
150
192
|
"DiscoveryProbeConfig",
|
|
@@ -152,17 +194,21 @@ __all__ = [
|
|
|
152
194
|
"GateResult",
|
|
153
195
|
"GeneratedUCPKey",
|
|
154
196
|
"IdentityStatus",
|
|
197
|
+
"MintedRecipients",
|
|
155
198
|
"MppxComposeOutcome",
|
|
199
|
+
"OwnerScope",
|
|
156
200
|
"PaymentSigner",
|
|
157
201
|
"PolicyBlock",
|
|
158
202
|
"PolicyCheck",
|
|
159
203
|
"PolicyResult",
|
|
160
204
|
"PricingResult",
|
|
205
|
+
"QuoteCache",
|
|
161
206
|
"SettleOutcome",
|
|
162
207
|
"SignerNetwork",
|
|
163
208
|
"SignerVerdict",
|
|
164
209
|
"SolanaMppRailSpec",
|
|
165
210
|
"StripeRailSpec",
|
|
211
|
+
"SuccessBodyArgs",
|
|
166
212
|
"TempoRailSpec",
|
|
167
213
|
"TempoSessionRailSpec",
|
|
168
214
|
"UCPCapabilityBinding",
|
|
@@ -173,24 +219,38 @@ __all__ = [
|
|
|
173
219
|
"UCPSigningKey",
|
|
174
220
|
"UCPVerificationError",
|
|
175
221
|
"VerifyWalletSignerResult",
|
|
222
|
+
"WorkOutcome",
|
|
176
223
|
"X402BaseRailSpec",
|
|
177
224
|
"__version__",
|
|
178
225
|
"build_a2a_agent_card",
|
|
179
226
|
"build_agent_memory_hint",
|
|
180
227
|
"build_contact_support_next_steps",
|
|
228
|
+
"build_default_checkout_rails",
|
|
181
229
|
"build_gate_from_policy",
|
|
182
230
|
"build_jwks_response",
|
|
231
|
+
"build_mppx_compose_rails",
|
|
183
232
|
"build_signer_mismatch_body",
|
|
184
233
|
"build_ucp_profile",
|
|
234
|
+
"build_verification_required_body",
|
|
235
|
+
"compute_first_checkout",
|
|
236
|
+
"create_default_on_denied",
|
|
237
|
+
"create_quote_cache",
|
|
238
|
+
"default_read_only_on_denied",
|
|
185
239
|
"denial_reason_status",
|
|
186
240
|
"denial_reason_to_body",
|
|
241
|
+
"extract_owner_scope",
|
|
187
242
|
"extract_payment_signer",
|
|
188
243
|
"extract_signer_for_precheck",
|
|
189
244
|
"format_pydantic_errors",
|
|
190
245
|
"format_usd_cents",
|
|
191
246
|
"generate_ucp_signing_key",
|
|
247
|
+
"has_mppx_header",
|
|
248
|
+
"has_payment_header",
|
|
249
|
+
"has_x402_header",
|
|
192
250
|
"hash_operator_token",
|
|
251
|
+
"is_evm_network",
|
|
193
252
|
"is_fixable_denial",
|
|
253
|
+
"is_solana_network",
|
|
194
254
|
"load_solana_fee_payer",
|
|
195
255
|
"load_ucp_signing_key_from_env",
|
|
196
256
|
"make_mppx_compose_hook",
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""Internal header helpers — case-normalization for HTTP headers.
|
|
2
|
+
|
|
3
|
+
Replaces hand-rolled ``{k.lower(): v for k, v in headers.items()}`` loops in
|
|
4
|
+
``checkout``, ``signer`` and ``challenge.respond_402``. Mirrors node-commerce
|
|
5
|
+
``src/_headers.ts``.
|
|
6
|
+
|
|
7
|
+
Not part of the public API; consumed by SDK internals only.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from typing import TYPE_CHECKING
|
|
13
|
+
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
from collections.abc import Mapping
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def normalize_headers_to_lowercase(headers: Mapping[str, str]) -> dict[str, str]:
|
|
19
|
+
"""Lowercase every header key, preserve values. Idempotent."""
|
|
20
|
+
return {k.lower(): v for k, v in headers.items()}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
"""Internal helpers for extracting the ``Payment-Receipt`` header.
|
|
2
|
+
|
|
3
|
+
Shared by ``Checkout.handle_mppx`` and ``compute_first_checkout``'s MPP
|
|
4
|
+
settle path so the rail-label / signer derivation stays one source of truth.
|
|
5
|
+
|
|
6
|
+
Mirrors node-commerce ``src/_mppx_receipt.ts``. Not part of the public API.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import logging
|
|
12
|
+
from typing import Any
|
|
13
|
+
|
|
14
|
+
log = logging.getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def extract_mppx_receipt_header_from_raw(raw: Any) -> str | None:
|
|
18
|
+
"""Pull the ``Payment-Receipt`` header value from an mppx compose result.
|
|
19
|
+
|
|
20
|
+
Covers three shapes hand-rolled hooks commonly return:
|
|
21
|
+
|
|
22
|
+
* ``raw.receipt_header`` — pympp's current direct-attribute shape.
|
|
23
|
+
* ``raw.to_payment_receipt()`` — pympp's older Receipt return-method shape
|
|
24
|
+
(also reached when ``raw`` is a ``(credential, receipt)`` tuple OR a
|
|
25
|
+
dict/object carrying ``.receipt``).
|
|
26
|
+
* ``raw.with_receipt(response) -> Response`` — node-compat shape that
|
|
27
|
+
wraps an outgoing Response and attaches the header.
|
|
28
|
+
|
|
29
|
+
Returns ``None`` when none match or the underlying call raises.
|
|
30
|
+
"""
|
|
31
|
+
if raw is None:
|
|
32
|
+
return None
|
|
33
|
+
# Shape 1: direct attribute.
|
|
34
|
+
header = getattr(raw, "receipt_header", None)
|
|
35
|
+
if isinstance(header, str) and header:
|
|
36
|
+
return header
|
|
37
|
+
# Shape 2: pympp's `to_payment_receipt()` callable on raw itself or a
|
|
38
|
+
# carried receipt. Build candidate list; first match wins.
|
|
39
|
+
candidates: list[Any] = [raw]
|
|
40
|
+
if isinstance(raw, (tuple, list)) and len(raw) >= 2:
|
|
41
|
+
candidates.append(raw[1])
|
|
42
|
+
if isinstance(raw, dict) and "receipt" in raw:
|
|
43
|
+
candidates.append(raw["receipt"])
|
|
44
|
+
inner_receipt = getattr(raw, "receipt", None)
|
|
45
|
+
if inner_receipt is not None:
|
|
46
|
+
candidates.append(inner_receipt)
|
|
47
|
+
for candidate in candidates:
|
|
48
|
+
to_header = getattr(candidate, "to_payment_receipt", None)
|
|
49
|
+
if not callable(to_header):
|
|
50
|
+
continue
|
|
51
|
+
try:
|
|
52
|
+
value = to_header()
|
|
53
|
+
except Exception as exc:
|
|
54
|
+
log.debug("[_mppx_receipt] to_payment_receipt() raised: %s", exc)
|
|
55
|
+
continue
|
|
56
|
+
if isinstance(value, str) and value:
|
|
57
|
+
return value
|
|
58
|
+
# Shape 3: node-style with_receipt(response) decorator.
|
|
59
|
+
with_receipt = getattr(raw, "with_receipt", None)
|
|
60
|
+
if callable(with_receipt):
|
|
61
|
+
try:
|
|
62
|
+
wrapped = with_receipt(None)
|
|
63
|
+
headers = getattr(wrapped, "headers", None)
|
|
64
|
+
if headers is not None and hasattr(headers, "get"):
|
|
65
|
+
val = headers.get("Payment-Receipt")
|
|
66
|
+
if isinstance(val, str) and val:
|
|
67
|
+
return val
|
|
68
|
+
except Exception:
|
|
69
|
+
return None
|
|
70
|
+
return None
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def extract_mppx_receipt_method(header: str) -> str | None:
|
|
74
|
+
"""Deserialize the receipt header via mppx and return the ``method`` field.
|
|
75
|
+
|
|
76
|
+
The returned method is ``'tempo'`` / ``'solana'`` / ``'stripe'``, or the
|
|
77
|
+
legacy ``'<scheme>/charge'`` form. Returns ``None`` when the header is
|
|
78
|
+
malformed or mppx isn't importable. Uses ``Receipt.from_payment_receipt``
|
|
79
|
+
(Python pympp) — equivalent to node's ``Receipt.deserialize``.
|
|
80
|
+
"""
|
|
81
|
+
try:
|
|
82
|
+
from mpp import Receipt # type: ignore[import-untyped]
|
|
83
|
+
except Exception:
|
|
84
|
+
return None
|
|
85
|
+
try:
|
|
86
|
+
receipt = Receipt.from_payment_receipt(header)
|
|
87
|
+
except Exception:
|
|
88
|
+
return None
|
|
89
|
+
method = getattr(receipt, "method", None)
|
|
90
|
+
return method if isinstance(method, str) else None
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def derive_mppx_receipt_method(raw: Any) -> str | None:
|
|
94
|
+
"""Resolve the receipt method from a compose-success raw result in one call.
|
|
95
|
+
|
|
96
|
+
Tries the direct ``raw.receipt.method`` path first, then falls back to the
|
|
97
|
+
receipt-header path. Returns ``None`` when neither yields a method.
|
|
98
|
+
"""
|
|
99
|
+
receipt = getattr(raw, "receipt", None)
|
|
100
|
+
direct = getattr(receipt, "method", None) if receipt is not None else None
|
|
101
|
+
if isinstance(direct, str) and direct:
|
|
102
|
+
return direct
|
|
103
|
+
header = extract_mppx_receipt_header_from_raw(raw)
|
|
104
|
+
if not header:
|
|
105
|
+
return None
|
|
106
|
+
return extract_mppx_receipt_method(header)
|