agentscore-commerce 2.2.0__tar.gz → 2.3.0__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.2.0 → agentscore_commerce-2.3.0}/CLAUDE.md +1 -1
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/PKG-INFO +2 -2
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/README.md +1 -1
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/agentscore_commerce/payment/rail_spec.py +24 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/agentscore_commerce/stripe_multichain/__init__.py +4 -0
- agentscore_commerce-2.3.0/agentscore_commerce/stripe_multichain/pay_to_address.py +280 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/examples/README.md +1 -1
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/examples/multi_rail_merchant.py +26 -13
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/pyproject.toml +1 -1
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/tests/test_pay_to_address.py +120 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/uv.lock +40 -40
- agentscore_commerce-2.2.0/agentscore_commerce/stripe_multichain/pay_to_address.py +0 -127
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/.github/dependabot.yml +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/.github/workflows/ci.yml +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/.github/workflows/publish.yml +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/.github/workflows/security.yml +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/.gitignore +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/CODE_OF_CONDUCT.md +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/CONTRIBUTING.md +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/LICENSE +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/SECURITY.md +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/agentscore_commerce/__init__.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/agentscore_commerce/_headers.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/agentscore_commerce/_mppx_receipt.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/agentscore_commerce/_redis.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/agentscore_commerce/_warnings.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/agentscore_commerce/api/__init__.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/agentscore_commerce/challenge/__init__.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/agentscore_commerce/challenge/accepted_methods.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/agentscore_commerce/challenge/agent_instructions.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/agentscore_commerce/challenge/agent_memory.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/agentscore_commerce/challenge/body.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/agentscore_commerce/challenge/how_to_pay.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/agentscore_commerce/challenge/identity.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/agentscore_commerce/challenge/pricing.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/agentscore_commerce/challenge/receipt.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/agentscore_commerce/challenge/respond_402.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/agentscore_commerce/challenge/validation_error.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/agentscore_commerce/checkout.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/agentscore_commerce/checkout_compute_first.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/agentscore_commerce/checkout_hooks.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/agentscore_commerce/discovery/__init__.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/agentscore_commerce/discovery/agentscore_content.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/agentscore_commerce/discovery/bazaar.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/agentscore_commerce/discovery/llms_txt.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/agentscore_commerce/discovery/openapi.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/agentscore_commerce/discovery/probe.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/agentscore_commerce/discovery/redemption_md.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/agentscore_commerce/discovery/robots_tag.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/agentscore_commerce/discovery/skill_md.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/agentscore_commerce/discovery/well_known.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/agentscore_commerce/discovery/well_known_mpp.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/agentscore_commerce/discovery/well_known_x402.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/agentscore_commerce/errors.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/agentscore_commerce/identity/__init__.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/agentscore_commerce/identity/_denial.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/agentscore_commerce/identity/_response.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/agentscore_commerce/identity/a2a.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/agentscore_commerce/identity/address.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/agentscore_commerce/identity/aiohttp.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/agentscore_commerce/identity/cache.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/agentscore_commerce/identity/core.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/agentscore_commerce/identity/default_denied.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/agentscore_commerce/identity/django.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/agentscore_commerce/identity/fastapi.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/agentscore_commerce/identity/flask.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/agentscore_commerce/identity/middleware.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/agentscore_commerce/identity/policy.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/agentscore_commerce/identity/py.typed +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/agentscore_commerce/identity/sanic.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/agentscore_commerce/identity/sessions.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/agentscore_commerce/identity/signer.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/agentscore_commerce/identity/tokens.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/agentscore_commerce/identity/types.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/agentscore_commerce/identity/ucp.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/agentscore_commerce/identity/ucp_jwks.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/agentscore_commerce/middleware/__init__.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/agentscore_commerce/middleware/_core.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/agentscore_commerce/middleware/aiohttp.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/agentscore_commerce/middleware/asgi.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/agentscore_commerce/middleware/django.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/agentscore_commerce/middleware/fastapi.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/agentscore_commerce/middleware/flask.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/agentscore_commerce/middleware/sanic.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/agentscore_commerce/payment/__init__.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/agentscore_commerce/payment/amounts.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/agentscore_commerce/payment/compose_rails.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/agentscore_commerce/payment/constants.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/agentscore_commerce/payment/default_rails.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/agentscore_commerce/payment/directive.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/agentscore_commerce/payment/dispatch.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/agentscore_commerce/payment/headers.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/agentscore_commerce/payment/idempotency.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/agentscore_commerce/payment/lazy.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/agentscore_commerce/payment/mppx_failures.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/agentscore_commerce/payment/mppx_server.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/agentscore_commerce/payment/network_kind.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/agentscore_commerce/payment/networks.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/agentscore_commerce/payment/payment_header.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/agentscore_commerce/payment/rails.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/agentscore_commerce/payment/settlement_override.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/agentscore_commerce/payment/signer.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/agentscore_commerce/payment/solana.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/agentscore_commerce/payment/usdc.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/agentscore_commerce/payment/wwwauthenticate.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/agentscore_commerce/payment/x402.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/agentscore_commerce/payment/x402_server.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/agentscore_commerce/payment/x402_settle.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/agentscore_commerce/payment/x402_validation.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/agentscore_commerce/payment/zero_settle.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/agentscore_commerce/quote_cache.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/agentscore_commerce/stripe_multichain/mppx_stripe.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/agentscore_commerce/stripe_multichain/payment_intent.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/agentscore_commerce/stripe_multichain/pi_cache.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/agentscore_commerce/stripe_multichain/simulate_deposit.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/agentscore_commerce/stripe_multichain/simulate_dispatch.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/examples/api_provider.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/examples/compliance_merchant.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/examples/compute_first_merchant.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/examples/identity_only.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/examples/per_product_policy_merchant.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/examples/signed_ucp_merchant.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/examples/stripe_multichain_merchant.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/lefthook.yml +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/osv-scanner.toml +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/ruff.toml +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/scripts/regenerate_cross_lang_fixtures.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/tests/__init__.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/tests/conftest.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/tests/fixtures/cross-lang/node-agentscore-gate-blocked.json +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/tests/fixtures/cross-lang/node-agentscore-gate-full.json +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/tests/fixtures/cross-lang/node-capability.json +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/tests/fixtures/cross-lang/node-emoji-keys.json +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/tests/fixtures/cross-lang/node-es256-rails.json +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/tests/fixtures/cross-lang/node-extras-int.json +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/tests/fixtures/cross-lang/node-int-boundary.json +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/tests/fixtures/cross-lang/node-minimal.json +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/tests/fixtures/cross-lang/node-multikey.json +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/tests/fixtures/cross-lang/node-unicode.json +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/tests/fixtures/cross-lang/py-agentscore-gate-blocked.json +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/tests/fixtures/cross-lang/py-agentscore-gate-full.json +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/tests/fixtures/cross-lang/py-capability.json +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/tests/fixtures/cross-lang/py-emoji-keys.json +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/tests/fixtures/cross-lang/py-es256-rails.json +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/tests/fixtures/cross-lang/py-extras-int.json +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/tests/fixtures/cross-lang/py-int-boundary.json +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/tests/fixtures/cross-lang/py-minimal.json +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/tests/fixtures/cross-lang/py-multikey.json +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/tests/fixtures/cross-lang/py-unicode.json +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/tests/test_a2a.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/tests/test_a2a_jws_roundtrip.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/tests/test_address.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/tests/test_agent_memory_emitter.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/tests/test_aiohttp.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/tests/test_amounts.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/tests/test_api_reexport.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/tests/test_cache.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/tests/test_challenge.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/tests/test_checkout.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/tests/test_checkout_compute_first.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/tests/test_checkout_compute_first_adapters.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/tests/test_checkout_compute_first_ofac.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/tests/test_checkout_compute_first_settle.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/tests/test_checkout_coverage_gaps.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/tests/test_checkout_wallet_ofac_default.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/tests/test_classify_orchestration_error.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/tests/test_compose_rails.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/tests/test_core.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/tests/test_coverage_fillers.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/tests/test_default_denied.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/tests/test_default_rails.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/tests/test_default_read_only_on_denied.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/tests/test_denial.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/tests/test_discovery.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/tests/test_dispatch.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/tests/test_django.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/tests/test_extract_owner_scope.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/tests/test_fastapi.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/tests/test_flask.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/tests/test_gate_quota_info.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/tests/test_get_signer_verdict.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/tests/test_idempotency_helper.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/tests/test_integration.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/tests/test_internal_helpers.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/tests/test_lifted_helpers.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/tests/test_load_ucp_signing_key_from_env.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/tests/test_middleware.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/tests/test_mppx_failures.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/tests/test_mppx_receipt.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/tests/test_network_kind.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/tests/test_payment_directive.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/tests/test_payment_dispatch.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/tests/test_payment_handlers.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/tests/test_payment_header.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/tests/test_payment_headers.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/tests/test_payment_misc.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/tests/test_payment_servers.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/tests/test_payment_signer.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/tests/test_policy.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/tests/test_pricing.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/tests/test_public_surface.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/tests/test_quote_cache.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/tests/test_quote_cache_redis.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/tests/test_rail_spec.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/tests/test_rate_limit.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/tests/test_redis_internal.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/tests/test_response.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/tests/test_robots_tag.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/tests/test_sanic.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/tests/test_seamless_helpers.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/tests/test_sessions.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/tests/test_signer_match.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/tests/test_simulate_dispatch.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/tests/test_skill_md.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/tests/test_solana.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/tests/test_solana_mpp_rail_spec_post_init.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/tests/test_stripe_multichain.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/tests/test_tokens.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/tests/test_ucp.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/tests/test_ucp_cross_lang.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/tests/test_ucp_jwks.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/tests/test_validation_error.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/tests/test_x402_settle_extra.py +0 -0
- {agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/tests/test_zero_settle.py +0 -0
|
@@ -14,7 +14,7 @@ Every helper is extracted from a real consumer, not speculated.
|
|
|
14
14
|
| `agentscore_commerce.payment` | Networks/USDC/rails registries, paymentauth.org directive builders, `create_x402_server` (wraps `x402[evm]>=2.9` + `cdp-sdk` for `facilitator="coinbase"`; install via the `coinbase` extra), `build_x402_accepts_for_402` (build the 402's `accepts[]` from the registered scheme; derives the right `extra.name` per network), `build_default_checkout_rails(tempo=, x402_base=, solana_mpp=, stripe=)` (canonical 4-rail `rails` dict factory: merchants pass per-rail overrides instead of redeclaring the recipient sentinel + network/chain_id/token boilerplate. When a caller flips `network` without pinning `token` / `chain_id`, the underlying dataclass derives them from the network: Base Sepolia → Sepolia USDC + chain_id 84532, Solana devnet → devnet USDC mint. Explicit overrides always win. Solana's `network` field accepts both CAIP-2 (`solana:5eykt4UsFv8…` / `solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1`) AND the raw `@solana/mpp` form (`mainnet-beta` / `devnet` / `localnet`)), `build_mppx_compose_rails(amount_usd=, tempo_recipient=, solana_recipient=, ...)` (per-call intent factory replacing the hand-rolled `[("tempo/charge", {...}), ("solana/charge", {...}), ("stripe/charge", {...})]` list; auto-handles USD→atomic conversion for Solana; auto-drops the `stripe/charge` rail with a one-time `logging.warning` when `amount_usd < 0.50` since Stripe's fixed ~$0.30 fee makes sub-50-cent charges unprofitable — many Stripe accounts also reject PI creation below the floor with `amount_too_small`; sub-50-cent APIs pass `include_stripe=False` explicitly to silence the warning), `process_x402_settle` (verify+settle in one call), `create_mppx_server` (wraps `pympp[server,tempo,stripe]>=0.6`), `is_evm_network`/`is_solana_network` (CAIP-2 discriminators that hide the `startswith("eip155:")` / `startswith("solana:")` prefix matching), `has_payment_header` (settle-leg vs discovery-leg discriminator), `parse_did_pkh_address` (parses ``did:pkh:<family>:<chain>:<addr>`` into a `PaymentSigner`), dispatch-by-network, signer extraction, WWW-Authenticate header, Settlement-Overrides header |
|
|
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), `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
|
|
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=, static_recipients=, 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 OR a `static_recipients` match — the static address is always-accepted because the merchant owns it); on the discovery leg, mints a fresh PI for the rails NOT covered by `static_recipients`, caches the merged map, registers static addresses with `pi_cache.cache_address`. `mint_multichain_recipients(...same kwargs) -> MintMultichainRecipientsResult(recipients, payment_intent_id, reused_from_credential)` — structured variant returning the full per-rail map; prefer this when the merchant's `mint_recipients` hook needs all rail addresses (typical multi-rail merchant), and to avoid the "returned-string-is-ambiguous" trap on the settle leg when `static_recipients` is configured. Use `static_recipients={"solana": "<wallet>"}` for low-margin endpoints where rotating per-PI Solana addresses can't absorb MPP spec §13.6's ~$0.50 ATA rent per call — the SDK skips Stripe minting on that network and reuses the static recipient forever; pair with a one-time external USDC pre-funding of the recipient's ATA and every settle pays only the per-tx fee. `SolanaMppRailSpec.ata_creation_required` defaults to `True` (data-only — solana method registration through `create_mppx_server` is a follow-up; merchants building the solana method directly via `pympp` should pass the flag themselves to the charge factory). 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
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). |
|
|
20
20
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: agentscore-commerce
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.3.0
|
|
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
|
|
@@ -78,7 +78,7 @@ pip install 'agentscore-commerce[fastapi,x402,coinbase]'
|
|
|
78
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). |
|
|
79
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}`. |
|
|
80
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. |
|
|
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
|
|
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=, static_recipients=, 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 OR a `static_recipients` match — the static address is always-accepted because the merchant owns it); on the discovery leg, mints a fresh PI for the rails NOT covered by `static_recipients`, caches the merged map, registers static addresses with `pi_cache.cache_address` so verify-leg lookups pass. `mint_multichain_recipients(...same kwargs)` — structured variant returning `MintMultichainRecipientsResult(recipients, payment_intent_id, reused_from_credential)` for the full per-rail map (typical multi-rail merchant hook). Use `static_recipients={"solana": "<wallet>"}` for low-margin endpoints where Solana per-call ATA rent (~$0.50 against MPP spec §13.6) dominates revenue — the SDK skips Stripe minting on that network, reuses the static recipient forever, and (with a one-time external pre-funding of the recipient's USDC ATA) lets every settle pay only the ~$0.001 per-tx fee. `SolanaMppRailSpec.ata_creation_required` defaults to `True` (data-only; solana method registration through `create_mppx_server` is a follow-up — merchants building the solana method directly via `pympp` should pass the flag themselves to the charge factory). `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`. |
|
|
82
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
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). |
|
|
84
84
|
|
|
@@ -30,7 +30,7 @@ pip install 'agentscore-commerce[fastapi,x402,coinbase]'
|
|
|
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), `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
|
|
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=, static_recipients=, 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 OR a `static_recipients` match — the static address is always-accepted because the merchant owns it); on the discovery leg, mints a fresh PI for the rails NOT covered by `static_recipients`, caches the merged map, registers static addresses with `pi_cache.cache_address` so verify-leg lookups pass. `mint_multichain_recipients(...same kwargs)` — structured variant returning `MintMultichainRecipientsResult(recipients, payment_intent_id, reused_from_credential)` for the full per-rail map (typical multi-rail merchant hook). Use `static_recipients={"solana": "<wallet>"}` for low-margin endpoints where Solana per-call ATA rent (~$0.50 against MPP spec §13.6) dominates revenue — the SDK skips Stripe minting on that network, reuses the static recipient forever, and (with a one-time external pre-funding of the recipient's USDC ATA) lets every settle pay only the ~$0.001 per-tx fee. `SolanaMppRailSpec.ata_creation_required` defaults to `True` (data-only; solana method registration through `create_mppx_server` is a follow-up — merchants building the solana method directly via `pympp` should pass the flag themselves to the charge factory). `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
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). |
|
|
36
36
|
|
{agentscore_commerce-2.2.0 → agentscore_commerce-2.3.0}/agentscore_commerce/payment/rail_spec.py
RENAMED
|
@@ -124,6 +124,30 @@ class SolanaMppRailSpec:
|
|
|
124
124
|
rpc_url: str | None = None
|
|
125
125
|
signer: Any | None = None
|
|
126
126
|
token_program: str | None = None
|
|
127
|
+
# Whether the recipient's ATA may be auto-created on first payment. Default True.
|
|
128
|
+
# When True (default), the SDK passes
|
|
129
|
+
# ``splits=[{"recipient": recipient, "amount": "0", "ataCreationRequired": True}]``
|
|
130
|
+
# to ``solana.charge``, putting the recipient in the MPP spec §13.6
|
|
131
|
+
# ``allowedAtaOwners`` allow-list. Required on ``@solana/mpp >= 0.6.0`` /
|
|
132
|
+
# ``pympp[solana] >= 0.6`` with a sponsored (fee-payer) setup — without it,
|
|
133
|
+
# every settle that emits a ``CreateIdempotent`` ATA instruction is rejected.
|
|
134
|
+
# On older runtimes the field is unknown and silently ignored, so the
|
|
135
|
+
# default is safe across versions.
|
|
136
|
+
#
|
|
137
|
+
# Opt out (``False``) only when every recipient's ATA is guaranteed to
|
|
138
|
+
# exist out-of-band (typically when the merchant pre-creates the ATA from
|
|
139
|
+
# an external wallet and refuses to let the fee-payer fund creation).
|
|
140
|
+
#
|
|
141
|
+
# Economic note: with rotating recipients (Stripe-multichain per-PI deposit
|
|
142
|
+
# addresses), the sponsor pays ~0.002 SOL (~$0.50) of rent per call into
|
|
143
|
+
# accounts the merchant can't close. Acceptable when settle amounts
|
|
144
|
+
# dominate ($50+); not viable for sub-dollar merchants.
|
|
145
|
+
#
|
|
146
|
+
# NOTE: python-commerce doesn't yet wire SolanaMppRailSpec through
|
|
147
|
+
# ``create_mppx_server`` (node parity gap); this field is data-only here
|
|
148
|
+
# today. Merchants building the solana method directly via ``pympp``
|
|
149
|
+
# should pass ``ata_creation_required`` themselves to the charge factory.
|
|
150
|
+
ata_creation_required: bool = True
|
|
127
151
|
|
|
128
152
|
def __post_init__(self) -> None:
|
|
129
153
|
# Mirror X402BaseRailSpec: when ``network`` flips to the devnet CAIP-2 (or the
|
|
@@ -5,7 +5,9 @@ from agentscore_commerce.stripe_multichain.mppx_stripe import (
|
|
|
5
5
|
create_mppx_stripe,
|
|
6
6
|
)
|
|
7
7
|
from agentscore_commerce.stripe_multichain.pay_to_address import (
|
|
8
|
+
MintMultichainRecipientsResult,
|
|
8
9
|
create_pay_to_address_from_stripe_pi,
|
|
10
|
+
mint_multichain_recipients,
|
|
9
11
|
)
|
|
10
12
|
from agentscore_commerce.stripe_multichain.payment_intent import (
|
|
11
13
|
MultichainPaymentIntentResult,
|
|
@@ -31,6 +33,7 @@ __all__ = [
|
|
|
31
33
|
"DEFAULT_PAYMENT_METHOD_TYPES",
|
|
32
34
|
"STRIPE_TEST_TX_HASH_FAILED",
|
|
33
35
|
"STRIPE_TEST_TX_HASH_SUCCESS",
|
|
36
|
+
"MintMultichainRecipientsResult",
|
|
34
37
|
"MultichainPaymentIntentResult",
|
|
35
38
|
"PiCache",
|
|
36
39
|
"SimulateNetwork",
|
|
@@ -39,6 +42,7 @@ __all__ = [
|
|
|
39
42
|
"create_multichain_payment_intent",
|
|
40
43
|
"create_pay_to_address_from_stripe_pi",
|
|
41
44
|
"create_pi_cache",
|
|
45
|
+
"mint_multichain_recipients",
|
|
42
46
|
"network_for_outcome",
|
|
43
47
|
"simulate_crypto_deposit",
|
|
44
48
|
"simulate_deposit_for_outcome",
|
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
"""Per-order Stripe-multichain ``pay_to`` resolver.
|
|
2
|
+
|
|
3
|
+
Stripe-multichain merchants need ONE function for their ``mint_recipients``
|
|
4
|
+
(or per-request payTo) hook that does the right thing on both legs:
|
|
5
|
+
|
|
6
|
+
- **Discovery leg** (no payment header): mint a fresh PaymentIntent so the 402
|
|
7
|
+
advertises a stable per-order deposit address.
|
|
8
|
+
- **Settle leg** (MPP credential attached): reuse the buyer's signed-against
|
|
9
|
+
payTo from the credential (after verifying it's in the local cache OR matches
|
|
10
|
+
a configured ``static_recipients`` entry) — otherwise the verify leg would
|
|
11
|
+
compare against a freshly-rotated address and reject the credential.
|
|
12
|
+
|
|
13
|
+
Stripe SPT and card methods don't carry an on-chain recipient, so the settle
|
|
14
|
+
leg still mints a fresh PaymentIntent for them.
|
|
15
|
+
|
|
16
|
+
Mirrors node-commerce ``src/stripe-multichain/pay_to_address.ts``.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
import asyncio
|
|
22
|
+
from dataclasses import dataclass
|
|
23
|
+
from typing import TYPE_CHECKING, Any
|
|
24
|
+
|
|
25
|
+
from agentscore_commerce.errors import CheckoutValidationError
|
|
26
|
+
from agentscore_commerce.stripe_multichain.payment_intent import (
|
|
27
|
+
create_multichain_payment_intent,
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
if TYPE_CHECKING:
|
|
31
|
+
from agentscore_commerce.stripe_multichain.pi_cache import PiCache
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
async def _maybe_await(value: Any) -> Any:
|
|
35
|
+
if asyncio.iscoroutine(value) or asyncio.isfuture(value):
|
|
36
|
+
return await value
|
|
37
|
+
return value
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
async def create_pay_to_address_from_stripe_pi(
|
|
41
|
+
*,
|
|
42
|
+
authorization_header: str | None,
|
|
43
|
+
amount_cents: int,
|
|
44
|
+
stripe: Any,
|
|
45
|
+
pi_cache: PiCache,
|
|
46
|
+
networks: list[str] | None = None,
|
|
47
|
+
static_recipients: dict[str, str] | None = None,
|
|
48
|
+
metadata: dict[str, str] | None = None,
|
|
49
|
+
order_id: str | None = None,
|
|
50
|
+
preferred_network: str = "tempo",
|
|
51
|
+
) -> str:
|
|
52
|
+
"""Resolve the on-chain ``pay_to`` for a Stripe-multichain order.
|
|
53
|
+
|
|
54
|
+
On the settle leg, when ``authorization_header`` carries an MPP credential
|
|
55
|
+
binding a ``tempo`` or ``solana`` recipient, returns THAT address (after
|
|
56
|
+
verifying it's in ``pi_cache`` OR matches a configured ``static_recipients``
|
|
57
|
+
entry — static addresses are always-accepted because the merchant owns
|
|
58
|
+
them). Otherwise mints a fresh :func:`create_multichain_payment_intent`
|
|
59
|
+
for the rails NOT covered by ``static_recipients``, caches the merged
|
|
60
|
+
address map, and registers static recipients with ``pi_cache.cache_address``
|
|
61
|
+
so future verify-leg lookups pass. Returns the address on the
|
|
62
|
+
``preferred_network`` (default ``"tempo"``, falling back to ``base`` then
|
|
63
|
+
``tempo``).
|
|
64
|
+
|
|
65
|
+
``static_recipients`` (optional, keyed by network) lets the merchant pin a
|
|
66
|
+
fixed receive wallet on chains where per-call rotation is expensive — Solana
|
|
67
|
+
in particular, since MPP spec §13.6 charges ~0.002 SOL of ATA rent per
|
|
68
|
+
new recipient into accounts the merchant can't close. Example:
|
|
69
|
+
``static_recipients={"solana": "FR96wd96urH..."}``. The SDK skips Stripe
|
|
70
|
+
minting for that network so the static address is reused forever; pair
|
|
71
|
+
with a one-time external USDC transfer to pre-create the recipient's USDC
|
|
72
|
+
ATA and every settle pays only the per-tx fee.
|
|
73
|
+
"""
|
|
74
|
+
if authorization_header:
|
|
75
|
+
recipient = await _try_resolve_from_credential(
|
|
76
|
+
authorization_header=authorization_header,
|
|
77
|
+
pi_cache=pi_cache,
|
|
78
|
+
static_recipients=static_recipients or {},
|
|
79
|
+
)
|
|
80
|
+
if recipient is not None:
|
|
81
|
+
return recipient
|
|
82
|
+
|
|
83
|
+
result_payto, _ = await _mint_and_cache(
|
|
84
|
+
amount_cents=amount_cents,
|
|
85
|
+
stripe=stripe,
|
|
86
|
+
pi_cache=pi_cache,
|
|
87
|
+
networks=networks or ["tempo", "base", "solana"],
|
|
88
|
+
static_recipients=static_recipients or {},
|
|
89
|
+
metadata=metadata,
|
|
90
|
+
order_id=order_id,
|
|
91
|
+
preferred_network=preferred_network,
|
|
92
|
+
)
|
|
93
|
+
return result_payto
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
@dataclass
|
|
97
|
+
class MintMultichainRecipientsResult:
|
|
98
|
+
"""Structured result for :func:`mint_multichain_recipients`.
|
|
99
|
+
|
|
100
|
+
Exposes the full per-network deposit map plus the PI id, so merchants don't
|
|
101
|
+
have to guess "is the returned string the tempo address or the solana
|
|
102
|
+
static?" when ``static_recipients`` is configured.
|
|
103
|
+
"""
|
|
104
|
+
|
|
105
|
+
recipients: dict[str, str]
|
|
106
|
+
"""Per-network deposit address map (merges Stripe-minted + static_recipients)."""
|
|
107
|
+
|
|
108
|
+
payment_intent_id: str | None
|
|
109
|
+
"""Stripe PI id, or None if all networks were covered by ``static_recipients``."""
|
|
110
|
+
|
|
111
|
+
reused_from_credential: bool
|
|
112
|
+
"""True when the settle leg short-circuited to the credential-bound recipient."""
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
async def mint_multichain_recipients(
|
|
116
|
+
*,
|
|
117
|
+
authorization_header: str | None,
|
|
118
|
+
amount_cents: int,
|
|
119
|
+
stripe: Any,
|
|
120
|
+
pi_cache: PiCache,
|
|
121
|
+
networks: list[str] | None = None,
|
|
122
|
+
static_recipients: dict[str, str] | None = None,
|
|
123
|
+
metadata: dict[str, str] | None = None,
|
|
124
|
+
order_id: str | None = None,
|
|
125
|
+
preferred_network: str = "tempo",
|
|
126
|
+
) -> MintMultichainRecipientsResult:
|
|
127
|
+
"""Structured variant of :func:`create_pay_to_address_from_stripe_pi`.
|
|
128
|
+
|
|
129
|
+
Returns the full per-rail deposit map. Prefer this when the merchant's
|
|
130
|
+
``mint_recipients`` hook needs every rail's address (typical multi-rail
|
|
131
|
+
merchant) — avoids the "returned string is ambiguous" trap when
|
|
132
|
+
``static_recipients`` is configured (settle leg's bound recipient may be
|
|
133
|
+
the solana static rather than the tempo per-PI address).
|
|
134
|
+
"""
|
|
135
|
+
static = static_recipients or {}
|
|
136
|
+
if authorization_header:
|
|
137
|
+
from agentscore_commerce.stripe_multichain.pi_cache import PiCache # noqa: F401
|
|
138
|
+
|
|
139
|
+
recipient = await _try_resolve_from_credential(
|
|
140
|
+
authorization_header=authorization_header,
|
|
141
|
+
pi_cache=pi_cache,
|
|
142
|
+
static_recipients=static,
|
|
143
|
+
)
|
|
144
|
+
if recipient is not None:
|
|
145
|
+
pi_id = pi_cache.get_payment_intent_id(recipient)
|
|
146
|
+
network_map: dict[str, str] = {}
|
|
147
|
+
if pi_id:
|
|
148
|
+
for net in ("tempo", "base", "solana"):
|
|
149
|
+
addr = pi_cache.get_network_deposit_address(pi_id, net)
|
|
150
|
+
if addr:
|
|
151
|
+
network_map[net] = addr
|
|
152
|
+
merged = {**network_map, **static}
|
|
153
|
+
return MintMultichainRecipientsResult(
|
|
154
|
+
recipients=merged,
|
|
155
|
+
payment_intent_id=pi_id,
|
|
156
|
+
reused_from_credential=True,
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
_, merged = await _mint_and_cache(
|
|
160
|
+
amount_cents=amount_cents,
|
|
161
|
+
stripe=stripe,
|
|
162
|
+
pi_cache=pi_cache,
|
|
163
|
+
networks=networks or ["tempo", "base", "solana"],
|
|
164
|
+
static_recipients=static,
|
|
165
|
+
metadata=metadata,
|
|
166
|
+
order_id=order_id,
|
|
167
|
+
preferred_network=preferred_network,
|
|
168
|
+
)
|
|
169
|
+
pi_id = pi_cache.get_payment_intent_id(next(iter(merged.values()), ""))
|
|
170
|
+
return MintMultichainRecipientsResult(
|
|
171
|
+
recipients=merged,
|
|
172
|
+
payment_intent_id=pi_id,
|
|
173
|
+
reused_from_credential=False,
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
async def _try_resolve_from_credential(
|
|
178
|
+
*,
|
|
179
|
+
authorization_header: str,
|
|
180
|
+
pi_cache: PiCache,
|
|
181
|
+
static_recipients: dict[str, str],
|
|
182
|
+
) -> str | None:
|
|
183
|
+
"""Parse the MPP credential and return the bound recipient when valid.
|
|
184
|
+
|
|
185
|
+
Returns the address when it's cached OR matches a configured
|
|
186
|
+
``static_recipients`` entry, or None to fall through to the mint path.
|
|
187
|
+
Raises CheckoutValidationError on malformed credentials.
|
|
188
|
+
"""
|
|
189
|
+
if not authorization_header.startswith("Payment "):
|
|
190
|
+
return None
|
|
191
|
+
from mpp import Credential # type: ignore[import-untyped]
|
|
192
|
+
|
|
193
|
+
try:
|
|
194
|
+
credential = Credential.from_authorization(authorization_header)
|
|
195
|
+
except Exception as err:
|
|
196
|
+
raise CheckoutValidationError(
|
|
197
|
+
code="invalid_credential",
|
|
198
|
+
message="The Authorization: Payment header is not a valid MPP credential.",
|
|
199
|
+
action="retry_without_credential",
|
|
200
|
+
status=401,
|
|
201
|
+
) from err
|
|
202
|
+
method = getattr(credential.challenge, "method", None)
|
|
203
|
+
if method not in ("tempo", "solana"):
|
|
204
|
+
return None
|
|
205
|
+
recipient = getattr(credential.challenge.request, "recipient", None)
|
|
206
|
+
if not isinstance(recipient, str) or not recipient:
|
|
207
|
+
raise CheckoutValidationError(
|
|
208
|
+
code="invalid_credential",
|
|
209
|
+
message="The MPP credential is missing its recipient field.",
|
|
210
|
+
action="retry_without_credential",
|
|
211
|
+
status=401,
|
|
212
|
+
)
|
|
213
|
+
static_for_method = static_recipients.get(method)
|
|
214
|
+
if static_for_method and static_for_method == recipient:
|
|
215
|
+
return recipient
|
|
216
|
+
if not await _maybe_await(pi_cache.has_address(recipient)):
|
|
217
|
+
raise CheckoutValidationError(
|
|
218
|
+
code="invalid_credential",
|
|
219
|
+
message=(
|
|
220
|
+
"The signed-against payTo recipient is not in this merchant's cache "
|
|
221
|
+
"(unknown or expired). Retry without the Authorization: Payment header "
|
|
222
|
+
"to receive a fresh 402 challenge."
|
|
223
|
+
),
|
|
224
|
+
action="retry_without_credential",
|
|
225
|
+
status=401,
|
|
226
|
+
)
|
|
227
|
+
return recipient
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
async def _mint_and_cache(
|
|
231
|
+
*,
|
|
232
|
+
amount_cents: int,
|
|
233
|
+
stripe: Any,
|
|
234
|
+
pi_cache: PiCache,
|
|
235
|
+
networks: list[str],
|
|
236
|
+
static_recipients: dict[str, str],
|
|
237
|
+
metadata: dict[str, str] | None,
|
|
238
|
+
order_id: str | None,
|
|
239
|
+
preferred_network: str,
|
|
240
|
+
) -> tuple[str, dict[str, str]]:
|
|
241
|
+
"""Mint a fresh PI for rails not covered by static_recipients.
|
|
242
|
+
|
|
243
|
+
Registers everything in the cache and returns (preferred_address, merged_map).
|
|
244
|
+
"""
|
|
245
|
+
stripe_networks = [n for n in networks if n not in static_recipients]
|
|
246
|
+
idempotency_key = f"pi-{order_id}-{amount_cents}" if order_id else None
|
|
247
|
+
result = create_multichain_payment_intent(
|
|
248
|
+
stripe=stripe,
|
|
249
|
+
amount=amount_cents,
|
|
250
|
+
networks=stripe_networks,
|
|
251
|
+
metadata=metadata,
|
|
252
|
+
idempotency_key=idempotency_key,
|
|
253
|
+
)
|
|
254
|
+
for address in result.deposit_addresses.values():
|
|
255
|
+
await _maybe_await(pi_cache.cache_address(address))
|
|
256
|
+
pi_cache.cache_payment_intent(address, result.payment_intent_id)
|
|
257
|
+
for address in static_recipients.values():
|
|
258
|
+
await _maybe_await(pi_cache.cache_address(address))
|
|
259
|
+
merged: dict[str, str] = {**result.deposit_addresses, **static_recipients}
|
|
260
|
+
pi_cache.cache_network_addresses(result.payment_intent_id, merged)
|
|
261
|
+
|
|
262
|
+
pay_to = merged.get(preferred_network) or merged.get("base") or merged.get("tempo")
|
|
263
|
+
if not pay_to:
|
|
264
|
+
raise CheckoutValidationError(
|
|
265
|
+
code="payment_provider_unavailable",
|
|
266
|
+
message=(
|
|
267
|
+
"Stripe returned deposit addresses but none matched the requested network (tempo / base / solana). "
|
|
268
|
+
"The account may have only a subset of multichain networks enabled."
|
|
269
|
+
),
|
|
270
|
+
action="retry_later",
|
|
271
|
+
status=503,
|
|
272
|
+
)
|
|
273
|
+
return pay_to, merged
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
__all__ = [
|
|
277
|
+
"MintMultichainRecipientsResult",
|
|
278
|
+
"create_pay_to_address_from_stripe_pi",
|
|
279
|
+
"mint_multichain_recipients",
|
|
280
|
+
]
|
|
@@ -7,7 +7,7 @@ Runnable, copy-pasteable example integrations covering the most common merchant
|
|
|
7
7
|
| [`identity_only.py`](./identity_only.py) | Compliance gate without payment | Minimal: wraps any endpoint with KYC + age + jurisdiction checks. Vendor handles their own payment. |
|
|
8
8
|
| [`api_provider.py`](./api_provider.py) | API provider (Exa-style) | Per-call billing on multiple rails: Tempo MPP + x402 (Base + Solana), all driven by `Checkout`. No identity gate. Demos `Checkout(discovery_probe=...)` for x402-crawler auto-routing, `build_merchant_index_json` + `standard_endpoint_descriptions(kind="api")` for `GET /` discovery, and `build_redemption_skill_md` with the trial-credit body shape on `GET /redemption.md`. |
|
|
9
9
|
| [`multi_rail_merchant.py`](./multi_rail_merchant.py) | Full agent-commerce merchant | Identity gate + Tempo MPP + x402 (Base + Solana) + Stripe SPT via `Checkout`. Demos `pricing_result` (cents → typed PricingResult), `Receipt` + `ReceiptNextSteps` + `build_success_next_steps` in `on_settled`, per-order Stripe-multichain deposit minting via `mint_recipients`, and `simulate_deposit_if_test_mode`. |
|
|
10
|
-
| [`stripe_multichain_merchant.py`](./stripe_multichain_merchant.py) | Stripe-anchored multi-chain | Stripe PaymentIntent with deposit_options for tempo/base/solana; crypto deposits flow through Stripe. Read `result.deposit_addresses[network]` directly. Includes testnet `simulate_crypto_deposit` helper. |
|
|
10
|
+
| [`stripe_multichain_merchant.py`](./stripe_multichain_merchant.py) | Stripe-anchored multi-chain | Stripe PaymentIntent with deposit_options for tempo/base/solana; crypto deposits flow through Stripe. Read `result.deposit_addresses[network]` directly. Includes testnet `simulate_crypto_deposit` helper. For low-margin endpoints (sub-dollar APIs), use `create_pay_to_address_from_stripe_pi` / `mint_multichain_recipients` with `static_recipients={"solana": "<wallet>"}` — see the `stripe_multichain` row in the main `README.md` for the full pattern and economics. |
|
|
11
11
|
| [`variable_cost_merchant.py`](./variable_cost_merchant.py) | Pay-per-actual-usage (LLM, transcode, etc.) | Same use case on **two protocols**: x402 upto (Permit2 authorize-max → `Settlement-Overrides` settle-actual) AND MPP tempo session (channel + SSE + mid-stream vouchers). Stays on lower-level helpers (`payment_directive`, `www_authenticate_header`, `settlement_override_header`) because variable-cost flows don't fit the one-shot `Checkout` model. |
|
|
12
12
|
| [`compliance_merchant.py`](./compliance_merchant.py) | Regulated-goods merchant (wine, cannabis, etc.) | Full compliance gate via `Checkout(gate=CheckoutGateConfig(...))` + custom `on_denied` composing commerce helpers: `verification_agent_instructions`, `is_fixable_denial`, `build_contact_support_next_steps`, `denial_reason_to_body`/`denial_reason_status`. Shows how vendors write only the business-specific denial branches and let commerce handle the rest. |
|
|
13
13
|
| [`per_product_policy_merchant.py`](./per_product_policy_merchant.py) | Multi-product merchant with mixed compliance needs | One product carries a hard gate (wine: KYC + 21 + US-state allowlist), another has no gate at all (anonymous merch, ships anywhere), a third uses `enforcement="soft"` (request KYC as a fraud signal but accept anonymous sales, stamping `identity_status="unverified"` on the order). Uses `PolicyBlock`, the one-call `validate_shipping_against_policy`, and `Checkout(gate=CheckoutGateConfig(per_request_policy=...))`. |
|
|
@@ -60,8 +60,8 @@ from agentscore_commerce.discovery import build_success_next_steps
|
|
|
60
60
|
from agentscore_commerce.middleware.fastapi import RateLimitMiddleware
|
|
61
61
|
from agentscore_commerce.payment import networks, validate_x402_network_config
|
|
62
62
|
from agentscore_commerce.stripe_multichain import (
|
|
63
|
-
create_multichain_payment_intent,
|
|
64
63
|
create_pi_cache,
|
|
64
|
+
mint_multichain_recipients,
|
|
65
65
|
simulate_deposit_for_outcome,
|
|
66
66
|
)
|
|
67
67
|
|
|
@@ -104,22 +104,35 @@ async def _compute_pricing(ctx: Any) -> PricingResult:
|
|
|
104
104
|
|
|
105
105
|
|
|
106
106
|
async def _mint_recipients(ctx: Any) -> dict[str, str]:
|
|
107
|
-
"""Per-order recipient mint: Stripe multichain PI → per-network deposit addresses.
|
|
107
|
+
"""Per-order recipient mint: Stripe multichain PI → per-network deposit addresses.
|
|
108
|
+
|
|
109
|
+
``mint_multichain_recipients`` returns the full per-rail map and registers
|
|
110
|
+
everything in the PI cache in one call. On the settle leg it short-circuits
|
|
111
|
+
to the buyer's signed-against payTo from the MPP credential; on the discovery
|
|
112
|
+
leg it mints a fresh multichain PI.
|
|
113
|
+
|
|
114
|
+
For low-margin endpoints (sub-dollar per call), pass
|
|
115
|
+
``static_recipients={"solana": os.environ["MERCHANT_SOLANA_RECIPIENT"]}`` to
|
|
116
|
+
skip Stripe minting on Solana — at $0.01/call MPP spec §13.6's ~$0.50 per-PI
|
|
117
|
+
ATA rent dominates revenue. With a stable merchant-owned recipient + one-time
|
|
118
|
+
external pre-funding of its USDC ATA, every settle pays only the per-tx fee.
|
|
119
|
+
"""
|
|
108
120
|
total_cents = round(ctx.pricing.amount_usd * 100)
|
|
109
|
-
result =
|
|
121
|
+
result = await mint_multichain_recipients(
|
|
122
|
+
authorization_header=ctx.request.headers.get("authorization"),
|
|
123
|
+
amount_cents=total_cents,
|
|
110
124
|
stripe=stripe_client,
|
|
111
|
-
|
|
125
|
+
pi_cache=pi_cache,
|
|
112
126
|
networks=["tempo", "base", "solana"],
|
|
113
127
|
)
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
"
|
|
121
|
-
|
|
122
|
-
}
|
|
128
|
+
out: dict[str, str] = {}
|
|
129
|
+
if "tempo" in result.recipients:
|
|
130
|
+
out["tempo"] = result.recipients["tempo"]
|
|
131
|
+
if "base" in result.recipients:
|
|
132
|
+
out["x402_base"] = result.recipients["base"]
|
|
133
|
+
if "solana" in result.recipients:
|
|
134
|
+
out["solana_mpp"] = result.recipients["solana"]
|
|
135
|
+
return out
|
|
123
136
|
|
|
124
137
|
|
|
125
138
|
async def _on_settled(ctx: Any, outcome: SettleOutcome) -> dict[str, Any]:
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "agentscore-commerce"
|
|
7
|
-
version = "2.
|
|
7
|
+
version = "2.3.0"
|
|
8
8
|
description = "Agent commerce SDK for Python — identity middleware (FastAPI, Flask, Django, AIOHTTP, Sanic, ASGI) + payment helpers + 402 builders + discovery + Stripe multichain. The full merchant-side toolkit for AgentScore-powered agent commerce."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "MIT"
|
|
@@ -9,7 +9,9 @@ from unittest.mock import patch
|
|
|
9
9
|
import pytest
|
|
10
10
|
|
|
11
11
|
from agentscore_commerce.stripe_multichain.pay_to_address import (
|
|
12
|
+
MintMultichainRecipientsResult,
|
|
12
13
|
create_pay_to_address_from_stripe_pi,
|
|
14
|
+
mint_multichain_recipients,
|
|
13
15
|
)
|
|
14
16
|
|
|
15
17
|
|
|
@@ -196,3 +198,121 @@ async def test_mints_fresh_when_credential_method_is_stripe() -> None:
|
|
|
196
198
|
pi_cache=cache, # type: ignore[arg-type]
|
|
197
199
|
)
|
|
198
200
|
assert result == "0xFRESH"
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
# ---------------------------------------------------------------------------
|
|
204
|
+
# static_recipients + mint_multichain_recipients
|
|
205
|
+
# ---------------------------------------------------------------------------
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
class _StripeRecordingNetworks:
|
|
209
|
+
"""Stripe stub that records which networks were requested on PI creation.
|
|
210
|
+
|
|
211
|
+
Lets us assert that ``static_recipients``-covered networks are excluded
|
|
212
|
+
from the underlying ``create_multichain_payment_intent`` call.
|
|
213
|
+
"""
|
|
214
|
+
|
|
215
|
+
def __init__(self, addresses: dict[str, str]) -> None:
|
|
216
|
+
self.payment_intents = self
|
|
217
|
+
self._addresses = addresses
|
|
218
|
+
self.last_networks: list[str] | None = None
|
|
219
|
+
self.last_idempotency_key: str | None = None
|
|
220
|
+
|
|
221
|
+
def create(self, params: dict[str, Any], idempotency_key: str | None = None) -> Any:
|
|
222
|
+
self.last_networks = list(params["payment_method_options"]["crypto"]["deposit_options"]["networks"])
|
|
223
|
+
self.last_idempotency_key = idempotency_key
|
|
224
|
+
deposits = {n: {"address": a} for n, a in self._addresses.items()}
|
|
225
|
+
|
|
226
|
+
class _PI:
|
|
227
|
+
id = "pi_test_456"
|
|
228
|
+
next_action = {"crypto_display_details": {"deposit_addresses": deposits}}
|
|
229
|
+
|
|
230
|
+
return _PI()
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
@pytest.mark.asyncio
|
|
234
|
+
async def test_static_recipients_excluded_from_stripe_pi_networks() -> None:
|
|
235
|
+
cache = FakePiCache()
|
|
236
|
+
stripe = _StripeRecordingNetworks({"tempo": "0xTEMPO", "base": "0xBASE"})
|
|
237
|
+
await create_pay_to_address_from_stripe_pi(
|
|
238
|
+
authorization_header=None,
|
|
239
|
+
amount_cents=100,
|
|
240
|
+
stripe=stripe, # type: ignore[arg-type]
|
|
241
|
+
pi_cache=cache, # type: ignore[arg-type]
|
|
242
|
+
static_recipients={"solana": "STATIC123"},
|
|
243
|
+
)
|
|
244
|
+
assert stripe.last_networks == ["tempo", "base"]
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
@pytest.mark.asyncio
|
|
248
|
+
async def test_static_recipients_registered_in_cache_and_merged_map() -> None:
|
|
249
|
+
cache = FakePiCache()
|
|
250
|
+
stripe = _fake_stripe({"tempo": "0xTEMPO", "base": "0xBASE"})
|
|
251
|
+
await create_pay_to_address_from_stripe_pi(
|
|
252
|
+
authorization_header=None,
|
|
253
|
+
amount_cents=100,
|
|
254
|
+
stripe=stripe,
|
|
255
|
+
pi_cache=cache, # type: ignore[arg-type]
|
|
256
|
+
static_recipients={"solana": "STATIC123"},
|
|
257
|
+
)
|
|
258
|
+
assert "STATIC123" in cache.cached_addresses
|
|
259
|
+
assert cache.cached_network_addresses == [
|
|
260
|
+
("pi_test_123", {"tempo": "0xTEMPO", "base": "0xBASE", "solana": "STATIC123"}),
|
|
261
|
+
]
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
@pytest.mark.asyncio
|
|
265
|
+
async def test_settle_leg_accepts_static_recipient_without_cache_check() -> None:
|
|
266
|
+
"""A bound recipient that matches static_recipients is always accepted,
|
|
267
|
+
even if the local PI cache wouldn't know about it (e.g. cold start).
|
|
268
|
+
"""
|
|
269
|
+
cache = FakePiCache(has_address_result=False)
|
|
270
|
+
with patch("mpp.Credential", FakeCredential):
|
|
271
|
+
result = await create_pay_to_address_from_stripe_pi(
|
|
272
|
+
authorization_header="Payment solana:STATIC123",
|
|
273
|
+
amount_cents=100,
|
|
274
|
+
stripe=_fake_stripe({}),
|
|
275
|
+
pi_cache=cache, # type: ignore[arg-type]
|
|
276
|
+
static_recipients={"solana": "STATIC123"},
|
|
277
|
+
)
|
|
278
|
+
assert result == "STATIC123"
|
|
279
|
+
assert cache.cached_addresses == []
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
@pytest.mark.asyncio
|
|
283
|
+
async def test_settle_leg_rejects_attacker_recipient_when_static_configured() -> None:
|
|
284
|
+
"""Attacker forges a credential bound to a different solana address;
|
|
285
|
+
static_recipients match check fails, then the cache check fails, then
|
|
286
|
+
the SDK raises invalid_credential.
|
|
287
|
+
"""
|
|
288
|
+
from agentscore_commerce.errors import CheckoutValidationError
|
|
289
|
+
|
|
290
|
+
cache = FakePiCache(has_address_result=False)
|
|
291
|
+
with (
|
|
292
|
+
patch("mpp.Credential", FakeCredential),
|
|
293
|
+
pytest.raises(CheckoutValidationError) as exc,
|
|
294
|
+
):
|
|
295
|
+
await create_pay_to_address_from_stripe_pi(
|
|
296
|
+
authorization_header="Payment solana:ATTACKER456",
|
|
297
|
+
amount_cents=100,
|
|
298
|
+
stripe=_fake_stripe({}),
|
|
299
|
+
pi_cache=cache, # type: ignore[arg-type]
|
|
300
|
+
static_recipients={"solana": "STATIC123"},
|
|
301
|
+
)
|
|
302
|
+
assert exc.value.code == "invalid_credential"
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
@pytest.mark.asyncio
|
|
306
|
+
async def test_mint_multichain_recipients_returns_full_merged_map() -> None:
|
|
307
|
+
cache = FakePiCache()
|
|
308
|
+
stripe = _fake_stripe({"tempo": "0xTEMPO", "base": "0xBASE"})
|
|
309
|
+
out = await mint_multichain_recipients(
|
|
310
|
+
authorization_header=None,
|
|
311
|
+
amount_cents=100,
|
|
312
|
+
stripe=stripe,
|
|
313
|
+
pi_cache=cache, # type: ignore[arg-type]
|
|
314
|
+
static_recipients={"solana": "STATIC123"},
|
|
315
|
+
)
|
|
316
|
+
assert isinstance(out, MintMultichainRecipientsResult)
|
|
317
|
+
assert out.recipients == {"tempo": "0xTEMPO", "base": "0xBASE", "solana": "STATIC123"}
|
|
318
|
+
assert out.reused_from_credential is False
|