agentscore-commerce 2.1.0__tar.gz → 2.2.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.1.0 → agentscore_commerce-2.2.0}/.github/workflows/security.yml +8 -2
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/.gitignore +2 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/CLAUDE.md +11 -3
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/PKG-INFO +2 -2
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/README.md +1 -1
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/__init__.py +3 -1
- agentscore_commerce-2.2.0/agentscore_commerce/_warnings.py +39 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/challenge/how_to_pay.py +17 -6
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/checkout.py +222 -37
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/checkout_compute_first.py +100 -3
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/checkout_hooks.py +4 -2
- agentscore_commerce-2.2.0/agentscore_commerce/errors.py +40 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/identity/__init__.py +2 -1
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/identity/_response.py +33 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/identity/core.py +4 -4
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/identity/middleware.py +4 -4
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/identity/policy.py +2 -3
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/identity/types.py +4 -4
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/payment/compose_rails.py +41 -1
- agentscore_commerce-2.2.0/agentscore_commerce/payment/constants.py +16 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/payment/dispatch.py +19 -3
- agentscore_commerce-2.2.0/agentscore_commerce/payment/mppx_failures.py +59 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/stripe_multichain/pay_to_address.py +35 -7
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/stripe_multichain/payment_intent.py +11 -1
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/examples/compliance_merchant.py +11 -5
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/examples/compute_first_merchant.py +5 -1
- agentscore_commerce-2.2.0/osv-scanner.toml +25 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/pyproject.toml +1 -1
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/test_checkout.py +25 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/test_checkout_compute_first.py +1 -1
- agentscore_commerce-2.2.0/tests/test_checkout_compute_first_ofac.py +207 -0
- agentscore_commerce-2.2.0/tests/test_checkout_wallet_ofac_default.py +259 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/test_compose_rails.py +13 -1
- agentscore_commerce-2.2.0/tests/test_mppx_failures.py +39 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/test_pay_to_address.py +27 -1
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/test_payment_dispatch.py +12 -2
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/test_policy.py +5 -5
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/test_response.py +42 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/test_stripe_multichain.py +5 -1
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/uv.lock +130 -153
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/.github/dependabot.yml +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/.github/workflows/ci.yml +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/.github/workflows/publish.yml +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/CODE_OF_CONDUCT.md +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/CONTRIBUTING.md +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/LICENSE +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/SECURITY.md +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/_headers.py +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/_mppx_receipt.py +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/_redis.py +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/api/__init__.py +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/challenge/__init__.py +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/challenge/accepted_methods.py +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/challenge/agent_instructions.py +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/challenge/agent_memory.py +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/challenge/body.py +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/challenge/identity.py +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/challenge/pricing.py +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/challenge/receipt.py +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/challenge/respond_402.py +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/challenge/validation_error.py +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/discovery/__init__.py +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/discovery/agentscore_content.py +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/discovery/bazaar.py +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/discovery/llms_txt.py +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/discovery/openapi.py +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/discovery/probe.py +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/discovery/redemption_md.py +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/discovery/robots_tag.py +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/discovery/skill_md.py +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/discovery/well_known.py +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/discovery/well_known_mpp.py +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/discovery/well_known_x402.py +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/identity/_denial.py +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/identity/a2a.py +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/identity/address.py +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/identity/aiohttp.py +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/identity/cache.py +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/identity/default_denied.py +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/identity/django.py +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/identity/fastapi.py +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/identity/flask.py +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/identity/py.typed +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/identity/sanic.py +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/identity/sessions.py +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/identity/signer.py +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/identity/tokens.py +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/identity/ucp.py +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/identity/ucp_jwks.py +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/middleware/__init__.py +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/middleware/_core.py +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/middleware/aiohttp.py +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/middleware/asgi.py +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/middleware/django.py +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/middleware/fastapi.py +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/middleware/flask.py +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/middleware/sanic.py +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/payment/__init__.py +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/payment/amounts.py +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/payment/default_rails.py +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/payment/directive.py +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/payment/headers.py +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/payment/idempotency.py +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/payment/lazy.py +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/payment/mppx_server.py +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/payment/network_kind.py +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/payment/networks.py +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/payment/payment_header.py +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/payment/rail_spec.py +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/payment/rails.py +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/payment/settlement_override.py +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/payment/signer.py +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/payment/solana.py +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/payment/usdc.py +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/payment/wwwauthenticate.py +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/payment/x402.py +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/payment/x402_server.py +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/payment/x402_settle.py +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/payment/x402_validation.py +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/payment/zero_settle.py +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/quote_cache.py +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/stripe_multichain/__init__.py +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/stripe_multichain/mppx_stripe.py +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/stripe_multichain/pi_cache.py +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/stripe_multichain/simulate_deposit.py +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/stripe_multichain/simulate_dispatch.py +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/examples/README.md +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/examples/api_provider.py +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/examples/identity_only.py +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/examples/multi_rail_merchant.py +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/examples/per_product_policy_merchant.py +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/examples/signed_ucp_merchant.py +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/examples/stripe_multichain_merchant.py +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/lefthook.yml +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/ruff.toml +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/scripts/regenerate_cross_lang_fixtures.py +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/__init__.py +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/conftest.py +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/fixtures/cross-lang/node-agentscore-gate-blocked.json +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/fixtures/cross-lang/node-agentscore-gate-full.json +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/fixtures/cross-lang/node-capability.json +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/fixtures/cross-lang/node-emoji-keys.json +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/fixtures/cross-lang/node-es256-rails.json +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/fixtures/cross-lang/node-extras-int.json +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/fixtures/cross-lang/node-int-boundary.json +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/fixtures/cross-lang/node-minimal.json +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/fixtures/cross-lang/node-multikey.json +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/fixtures/cross-lang/node-unicode.json +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/fixtures/cross-lang/py-agentscore-gate-blocked.json +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/fixtures/cross-lang/py-agentscore-gate-full.json +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/fixtures/cross-lang/py-capability.json +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/fixtures/cross-lang/py-emoji-keys.json +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/fixtures/cross-lang/py-es256-rails.json +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/fixtures/cross-lang/py-extras-int.json +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/fixtures/cross-lang/py-int-boundary.json +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/fixtures/cross-lang/py-minimal.json +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/fixtures/cross-lang/py-multikey.json +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/fixtures/cross-lang/py-unicode.json +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/test_a2a.py +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/test_a2a_jws_roundtrip.py +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/test_address.py +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/test_agent_memory_emitter.py +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/test_aiohttp.py +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/test_amounts.py +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/test_api_reexport.py +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/test_cache.py +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/test_challenge.py +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/test_checkout_compute_first_adapters.py +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/test_checkout_compute_first_settle.py +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/test_checkout_coverage_gaps.py +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/test_classify_orchestration_error.py +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/test_core.py +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/test_coverage_fillers.py +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/test_default_denied.py +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/test_default_rails.py +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/test_default_read_only_on_denied.py +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/test_denial.py +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/test_discovery.py +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/test_dispatch.py +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/test_django.py +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/test_extract_owner_scope.py +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/test_fastapi.py +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/test_flask.py +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/test_gate_quota_info.py +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/test_get_signer_verdict.py +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/test_idempotency_helper.py +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/test_integration.py +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/test_internal_helpers.py +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/test_lifted_helpers.py +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/test_load_ucp_signing_key_from_env.py +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/test_middleware.py +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/test_mppx_receipt.py +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/test_network_kind.py +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/test_payment_directive.py +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/test_payment_handlers.py +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/test_payment_header.py +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/test_payment_headers.py +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/test_payment_misc.py +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/test_payment_servers.py +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/test_payment_signer.py +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/test_pricing.py +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/test_public_surface.py +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/test_quote_cache.py +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/test_quote_cache_redis.py +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/test_rail_spec.py +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/test_rate_limit.py +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/test_redis_internal.py +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/test_robots_tag.py +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/test_sanic.py +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/test_seamless_helpers.py +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/test_sessions.py +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/test_signer_match.py +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/test_simulate_dispatch.py +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/test_skill_md.py +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/test_solana.py +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/test_solana_mpp_rail_spec_post_init.py +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/test_tokens.py +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/test_ucp.py +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/test_ucp_cross_lang.py +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/test_ucp_jwks.py +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/test_validation_error.py +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/test_x402_settle_extra.py +0 -0
- {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/test_zero_settle.py +0 -0
|
@@ -27,7 +27,10 @@ jobs:
|
|
|
27
27
|
chmod +x osv-scanner
|
|
28
28
|
|
|
29
29
|
- name: Scan dependencies
|
|
30
|
-
|
|
30
|
+
# `--config osv-scanner.toml` honors the in-repo allowlist (e.g. PYSEC-2025-183;
|
|
31
|
+
# see the toml for justifications). Keep the pip-audit `--ignore-vuln` flags below
|
|
32
|
+
# in sync with the same IDs.
|
|
33
|
+
run: ./osv-scanner scan source --lockfile=uv.lock --config=osv-scanner.toml --format=table
|
|
31
34
|
|
|
32
35
|
pip-audit:
|
|
33
36
|
name: Python Audit
|
|
@@ -44,6 +47,9 @@ jobs:
|
|
|
44
47
|
run: pip install pip-audit
|
|
45
48
|
|
|
46
49
|
- name: Audit dependencies
|
|
50
|
+
# `--ignore-vuln` allowlist mirrors `osv-scanner.toml` (e.g. PYSEC-2025-183;
|
|
51
|
+
# see that file for justifications). Keep both lists in sync.
|
|
47
52
|
run: |
|
|
48
53
|
uv export --format requirements-txt --no-hashes > requirements.txt
|
|
49
|
-
pip-audit -r requirements.txt --disable-pip --no-deps
|
|
54
|
+
pip-audit -r requirements.txt --disable-pip --no-deps \
|
|
55
|
+
--ignore-vuln PYSEC-2025-183
|
|
@@ -11,7 +11,7 @@ Every helper is extracted from a real consumer, not speculated.
|
|
|
11
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
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), `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), `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 |
|
|
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
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` |
|
|
@@ -80,11 +80,19 @@ Two identity types: wallet (`X-Wallet-Address`) and operator-token (`X-Operator-
|
|
|
80
80
|
|
|
81
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.
|
|
82
82
|
|
|
83
|
-
`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"`).
|
|
84
88
|
|
|
85
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.
|
|
86
90
|
|
|
87
|
-
Wallet-signer-match + signer-sanctions: the gate adapter calls `extract_payment_signer(x402_header)` pre-evaluate and passes `signer={address, network}` to the SDK's `assess`. The API returns both `signer_match` (wallet-binding) and `signer_sanctions` (OFAC SDN wallet-address) on the same response; commerce caches the raw body alongside the projected verdicts so `get_signer_verdict` is a pure cache read.
|
|
91
|
+
Wallet-signer-match + signer-sanctions: the gate adapter calls `extract_payment_signer(x402_header)` pre-evaluate and passes `signer={address, network}` to the SDK's `assess`. The API returns both `signer_match` (wallet-binding) and `signer_sanctions` (OFAC SDN wallet-address) on the same response; commerce caches the raw body alongside the projected verdicts so `get_signer_verdict` is a pure cache read. **Wallet-OFAC SDN enforcement on the `signer` block is unconditional** whenever a signer is present — no `policy.require_sanctions_clear` opt-in required. An SDN hit (or `sanctions_check_unavailable`) flips `decision -> deny` and the gate returns 403 before the handler runs.
|
|
92
|
+
|
|
93
|
+
### Always-on wallet OFAC default for gateless merchants
|
|
94
|
+
|
|
95
|
+
Merchants who do NOT configure a `gate` on `Checkout` (or `compute_first_checkout`) still get wallet OFAC SDN enforcement on every settle leg. The SDK resolves the API key from `AGENTSCORE_API_KEY` (or `gate.api_key` if a partial gate config is present), reads `AGENTSCORE_BASE_URL` for staging/dev overrides, extracts the payment signer, and calls `/v1/assess` with the signer block. SDN hit → 403 with `wallet_not_trusted` + `decision_reasons: ["sanctions_flagged"]`. No key → one-time `[checkout]` / `[<name>.compute_first]` warning + skip (dev/testnet pattern; see `agentscore_commerce/_warnings.py`). Stripe SPT (no extractable wallet signer) skips silently — Stripe runs its own OFAC screen at customer creation. `_has_identity_gate()` detects identity-bearing flags (`require_kyc`, `require_sanctions_clear`, `min_age`, `allowed/blocked_jurisdictions`); when none are set, the 402 body omits `agent_memory` and per-rail commands strip the `-H 'X-Operator-Token: ...'` flag (gateless merchants have no operator to identify). Gate configured WITHOUT `api_key` falls through to the wallet-OFAC-only path so the merchant gets the strict-liability floor instead of silently allowing.
|
|
88
96
|
|
|
89
97
|
### Fail-open (opt-in)
|
|
90
98
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: agentscore-commerce
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.2.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
|
|
@@ -224,7 +224,7 @@ For **variable-cost pay-per-result** endpoints (per-result search, per-token LLM
|
|
|
224
224
|
|
|
225
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
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. `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).
|
|
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
228
|
|
|
229
229
|
## Payment helpers
|
|
230
230
|
|
|
@@ -176,7 +176,7 @@ For **variable-cost pay-per-result** endpoints (per-result search, per-token LLM
|
|
|
176
176
|
|
|
177
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
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. `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).
|
|
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
180
|
|
|
181
181
|
## Payment helpers
|
|
182
182
|
|
|
@@ -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,
|
|
@@ -49,6 +48,7 @@ from agentscore_commerce.checkout_compute_first import (
|
|
|
49
48
|
compute_first_checkout,
|
|
50
49
|
)
|
|
51
50
|
from agentscore_commerce.checkout_hooks import make_mppx_compose_hook
|
|
51
|
+
from agentscore_commerce.errors import CheckoutValidationError
|
|
52
52
|
|
|
53
53
|
# Re-export the most commonly used helpers at the package root so consumers
|
|
54
54
|
# don't have to remember which submodule each one lives in. Mirrors node's
|
|
@@ -96,6 +96,7 @@ from agentscore_commerce.identity import (
|
|
|
96
96
|
build_jwks_response,
|
|
97
97
|
build_signer_mismatch_body,
|
|
98
98
|
build_ucp_profile,
|
|
99
|
+
build_verification_required_body,
|
|
99
100
|
create_default_on_denied,
|
|
100
101
|
default_read_only_on_denied,
|
|
101
102
|
denial_reason_status,
|
|
@@ -230,6 +231,7 @@ __all__ = [
|
|
|
230
231
|
"build_mppx_compose_rails",
|
|
231
232
|
"build_signer_mismatch_body",
|
|
232
233
|
"build_ucp_profile",
|
|
234
|
+
"build_verification_required_body",
|
|
233
235
|
"compute_first_checkout",
|
|
234
236
|
"create_default_on_denied",
|
|
235
237
|
"create_quote_cache",
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"""Shared one-shot warning helpers for the SDK.
|
|
2
|
+
|
|
3
|
+
Module-level state ensures each warning fires at most once per process,
|
|
4
|
+
regardless of how many ``Checkout`` / ``compute_first_checkout`` instances
|
|
5
|
+
trigger it. Mirrors ``node-commerce/src/_warnings.ts``.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import logging
|
|
11
|
+
|
|
12
|
+
_warned_no_api_key = False
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def warn_missing_api_key_once(label: str) -> None:
|
|
16
|
+
"""Emit a one-time warning when AGENTSCORE_API_KEY is unset.
|
|
17
|
+
|
|
18
|
+
Triggered on a settle path that would otherwise enforce wallet OFAC SDN
|
|
19
|
+
sanctions. Both ``Checkout`` and ``compute_first_checkout`` route through
|
|
20
|
+
this so a single multi-surface app sees the warning ONCE, not once per
|
|
21
|
+
surface.
|
|
22
|
+
|
|
23
|
+
``label`` is the caller's identifier for the log message; e.g.
|
|
24
|
+
``"checkout"`` or the compute-first handler's ``name``.
|
|
25
|
+
"""
|
|
26
|
+
global _warned_no_api_key
|
|
27
|
+
if _warned_no_api_key:
|
|
28
|
+
return
|
|
29
|
+
_warned_no_api_key = True
|
|
30
|
+
logging.getLogger(__name__).warning(
|
|
31
|
+
f"[{label}] AGENTSCORE_API_KEY is not set — wallet OFAC SDN sanctions are NOT being enforced. "
|
|
32
|
+
"Set the env var to enable strict-liability protection on settle."
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _reset_warned_no_api_key() -> None:
|
|
37
|
+
"""Test-only: reset the warn-once flag."""
|
|
38
|
+
global _warned_no_api_key
|
|
39
|
+
_warned_no_api_key = False
|
{agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/challenge/how_to_pay.py
RENAMED
|
@@ -43,7 +43,7 @@ async def build_how_to_pay(
|
|
|
43
43
|
retry_body_json: str,
|
|
44
44
|
total_usd: float | str,
|
|
45
45
|
rails: dict[str, TempoRailSpec | X402BaseRailSpec | SolanaMppRailSpec | StripeRailSpec],
|
|
46
|
-
op_token_placeholder: str = "<your_opc_token>", # noqa: S107 — literal placeholder, not a secret
|
|
46
|
+
op_token_placeholder: str | None = "<your_opc_token>", # noqa: S107 — literal placeholder, not a secret
|
|
47
47
|
max_spend: float | str | None = None,
|
|
48
48
|
decimals: int = 2,
|
|
49
49
|
) -> dict[str, Any]:
|
|
@@ -62,6 +62,12 @@ async def build_how_to_pay(
|
|
|
62
62
|
≥ $1 the default is still ``ceil(total) + 1`` formatted at ``decimals``; for
|
|
63
63
|
sub-dollar totals the default uses ``total.toFixed(decimals)`` so the cap
|
|
64
64
|
flag reflects the real amount instead of always being ``"1.00"``.
|
|
65
|
+
|
|
66
|
+
``op_token_placeholder`` defaults to ``"<your_opc_token>"``. Pass ``None``
|
|
67
|
+
(gateless merchants) to strip the ``-H 'X-Operator-Token: ...'`` snippet
|
|
68
|
+
from every rail command — appropriate when the merchant doesn't run an
|
|
69
|
+
identity gate. The wallet OFAC SDN default (TEC-311) does NOT need an
|
|
70
|
+
operator token, so gateless merchants emit cleaner commands.
|
|
65
71
|
"""
|
|
66
72
|
total_num = float(total_usd) if isinstance(total_usd, str) else total_usd
|
|
67
73
|
if max_spend is not None:
|
|
@@ -70,7 +76,9 @@ async def build_how_to_pay(
|
|
|
70
76
|
max_spend_str = f"{math.ceil(total_num) + 1:.{decimals}f}"
|
|
71
77
|
else:
|
|
72
78
|
max_spend_str = f"{total_num:.{decimals}f}"
|
|
73
|
-
|
|
79
|
+
gateless = op_token_placeholder is None
|
|
80
|
+
op_token = op_token_placeholder or "<your_opc_token>"
|
|
81
|
+
op_token_header_flag = "" if gateless else f"-H 'X-Operator-Token: {op_token}' "
|
|
74
82
|
block: dict[str, Any] = {}
|
|
75
83
|
|
|
76
84
|
tempo = rails.get("tempo")
|
|
@@ -79,11 +87,11 @@ async def build_how_to_pay(
|
|
|
79
87
|
chain_id = tempo.chain_id
|
|
80
88
|
recommend = tempo.recommend
|
|
81
89
|
tempo_command = (
|
|
82
|
-
f"tempo request -X POST
|
|
90
|
+
f"tempo request -X POST {op_token_header_flag}-H 'Content-Type: application/json' "
|
|
83
91
|
f"--json '{retry_body_json}' --max-spend {max_spend_str} {url}"
|
|
84
92
|
)
|
|
85
93
|
pay_command = (
|
|
86
|
-
f"agentscore-pay pay POST {url} --chain tempo
|
|
94
|
+
f"agentscore-pay pay POST {url} --chain tempo {op_token_header_flag}"
|
|
87
95
|
f"-H 'Content-Type: application/json' -d '{retry_body_json}' --max-spend {max_spend_str}"
|
|
88
96
|
)
|
|
89
97
|
entry: dict[str, Any] = {
|
|
@@ -115,7 +123,7 @@ async def build_how_to_pay(
|
|
|
115
123
|
f"least ${max_spend_str}. If the CLI is not installed, run the setup commands above first."
|
|
116
124
|
),
|
|
117
125
|
"command": (
|
|
118
|
-
f"agentscore-pay pay POST {url} --chain base
|
|
126
|
+
f"agentscore-pay pay POST {url} --chain base {op_token_header_flag}"
|
|
119
127
|
f"-H 'Content-Type: application/json' -d '{retry_body_json}' --max-spend {max_spend_str}"
|
|
120
128
|
),
|
|
121
129
|
"what_it_does": (
|
|
@@ -135,7 +143,7 @@ async def build_how_to_pay(
|
|
|
135
143
|
f"is at least ${max_spend_str}. If the CLI is not installed, run the setup commands above first."
|
|
136
144
|
),
|
|
137
145
|
"command": (
|
|
138
|
-
f"agentscore-pay pay POST {url} --chain solana
|
|
146
|
+
f"agentscore-pay pay POST {url} --chain solana {op_token_header_flag}"
|
|
139
147
|
f"-H 'Content-Type: application/json' -d '{retry_body_json}' --max-spend {max_spend_str}"
|
|
140
148
|
),
|
|
141
149
|
"what_it_does": (
|
|
@@ -182,6 +190,9 @@ async def build_how_to_pay(
|
|
|
182
190
|
),
|
|
183
191
|
(
|
|
184
192
|
f"link-cli mpp pay {url} --spend-request-id $SPEND_ID --method POST "
|
|
193
|
+
f"--data '{retry_body_json}' --output-json"
|
|
194
|
+
if gateless
|
|
195
|
+
else f"link-cli mpp pay {url} --spend-request-id $SPEND_ID --method POST "
|
|
185
196
|
f"--data '{retry_body_json}' --header 'X-Operator-Token: {op_token}' --output-json"
|
|
186
197
|
),
|
|
187
198
|
]
|
|
@@ -71,6 +71,7 @@ import json
|
|
|
71
71
|
import uuid
|
|
72
72
|
from collections.abc import Awaitable, Callable, Mapping
|
|
73
73
|
from dataclasses import dataclass, field
|
|
74
|
+
from decimal import Decimal
|
|
74
75
|
from typing import Any, Literal, TypeAlias
|
|
75
76
|
|
|
76
77
|
from agentscore_commerce._headers import normalize_headers_to_lowercase
|
|
@@ -83,6 +84,9 @@ from agentscore_commerce.challenge.how_to_pay import build_how_to_pay
|
|
|
83
84
|
from agentscore_commerce.challenge.pricing import PricingBlock, build_pricing_block
|
|
84
85
|
from agentscore_commerce.challenge.respond_402 import Respond402Result, respond_402
|
|
85
86
|
from agentscore_commerce.challenge.validation_error import build_validation_error
|
|
87
|
+
from agentscore_commerce.errors import CheckoutValidationError
|
|
88
|
+
from agentscore_commerce.payment.constants import STRIPE_MIN_CHARGE_USD
|
|
89
|
+
from agentscore_commerce.payment.mppx_failures import classify_mppx_failure
|
|
86
90
|
from agentscore_commerce.payment.payment_header import has_mppx_header, has_x402_header
|
|
87
91
|
from agentscore_commerce.payment.rail_spec import (
|
|
88
92
|
RecipientLike,
|
|
@@ -134,31 +138,6 @@ def _spec_method_name(spec: CheckoutRailSpec) -> str:
|
|
|
134
138
|
return "stripe/spt" # StripeRailSpec is the only remaining variant in CheckoutRailSpec.
|
|
135
139
|
|
|
136
140
|
|
|
137
|
-
class CheckoutValidationError(Exception):
|
|
138
|
-
"""Raised from a :attr:`Checkout.pre_validate` hook to short-circuit with a 4xx.
|
|
139
|
-
|
|
140
|
-
Checkout catches this and emits the canonical ``{error, next_steps}`` envelope
|
|
141
|
-
via :func:`build_validation_error` so merchants don't have to construct
|
|
142
|
-
``JSONResponse`` themselves in the pre-validate path.
|
|
143
|
-
"""
|
|
144
|
-
|
|
145
|
-
def __init__(
|
|
146
|
-
self,
|
|
147
|
-
*,
|
|
148
|
-
code: str,
|
|
149
|
-
message: str,
|
|
150
|
-
action: str = "fix_request",
|
|
151
|
-
status: int = 400,
|
|
152
|
-
extra: dict[str, Any] | None = None,
|
|
153
|
-
) -> None:
|
|
154
|
-
super().__init__(message)
|
|
155
|
-
self.code = code
|
|
156
|
-
self.message = message
|
|
157
|
-
self.action = action
|
|
158
|
-
self.status = status
|
|
159
|
-
self.extra = extra
|
|
160
|
-
|
|
161
|
-
|
|
162
141
|
@dataclass
|
|
163
142
|
class CheckoutRequest:
|
|
164
143
|
"""Framework-neutral HTTP request input to :meth:`Checkout.handle`.
|
|
@@ -512,6 +491,14 @@ class MppxComposeOutcome:
|
|
|
512
491
|
headers without parsing the JSON body."""
|
|
513
492
|
raw: Any = None
|
|
514
493
|
"""The underlying pympp compose result for ``on_settled`` introspection."""
|
|
494
|
+
failure_reason: str | None = None
|
|
495
|
+
"""For ``status=402``: optional reason string captured from the swallowed
|
|
496
|
+
inner verifier exception (e.g. ``"keychain validation failed: KeyNotFound"``
|
|
497
|
+
when a Tempo signer isn't enrolled). When set, ``_handle_mppx`` runs
|
|
498
|
+
:func:`classify_mppx_failure` and returns a typed envelope
|
|
499
|
+
(``tempo_key_not_registered``, etc.) instead of the generic
|
|
500
|
+
``payment_proof_invalid``. Populated by :func:`make_mppx_compose_hook` when
|
|
501
|
+
pympp's verifier raises; custom hooks can set it explicitly to opt in."""
|
|
515
502
|
|
|
516
503
|
|
|
517
504
|
@dataclass
|
|
@@ -719,6 +706,30 @@ class Checkout:
|
|
|
719
706
|
body's ``extensions`` field so Bazaar crawlers and other spec-compliant
|
|
720
707
|
clients read the route's declared input/output schema."""
|
|
721
708
|
|
|
709
|
+
def _has_identity_gate(self) -> bool:
|
|
710
|
+
"""Return True when the merchant configured an identity-bearing flag.
|
|
711
|
+
|
|
712
|
+
The identity-bearing flags are ``require_kyc``, ``require_sanctions_clear``
|
|
713
|
+
(name screening on the KYC identity), ``min_age``, or jurisdiction
|
|
714
|
+
lists. Wallet OFAC SDN enforcement (the always-on default) does NOT
|
|
715
|
+
count as an identity gate; agents don't need an AgentScore credential
|
|
716
|
+
to satisfy it.
|
|
717
|
+
|
|
718
|
+
Used to conditionally emit AgentScore identity boilerplate in 402
|
|
719
|
+
bodies (``agent_memory``, ``X-Operator-Token`` references in per-rail
|
|
720
|
+
commands).
|
|
721
|
+
"""
|
|
722
|
+
g = self.gate
|
|
723
|
+
if g is None:
|
|
724
|
+
return False
|
|
725
|
+
return bool(
|
|
726
|
+
g.require_kyc
|
|
727
|
+
or g.require_sanctions_clear
|
|
728
|
+
or g.min_age is not None
|
|
729
|
+
or (g.allowed_jurisdictions and len(g.allowed_jurisdictions) > 0)
|
|
730
|
+
or (g.blocked_jurisdictions and len(g.blocked_jurisdictions) > 0)
|
|
731
|
+
)
|
|
732
|
+
|
|
722
733
|
async def _get_x402_server(self) -> Any:
|
|
723
734
|
"""Resolve the x402 server.
|
|
724
735
|
|
|
@@ -912,13 +923,22 @@ class Checkout:
|
|
|
912
923
|
|
|
913
924
|
ctx.pricing = await _maybe_await(self.compute_pricing(ctx))
|
|
914
925
|
|
|
915
|
-
# Per-request
|
|
916
|
-
# passes through to 402).
|
|
917
|
-
#
|
|
918
|
-
#
|
|
926
|
+
# Per-request compliance: runs on the settle leg only (anonymous
|
|
927
|
+
# discovery passes through to 402).
|
|
928
|
+
#
|
|
929
|
+
# Two paths converge here:
|
|
930
|
+
# - Merchants with an explicit ``gate`` config run the full identity
|
|
931
|
+
# policy (KYC / age / sanctions / jurisdiction) via ``_run_gate``.
|
|
932
|
+
# - Merchants WITHOUT a ``gate`` config still get wallet OFAC SDN
|
|
933
|
+
# enforcement via ``_run_wallet_sanctions_only`` — the always-on
|
|
934
|
+
# strict-liability default. Falls back to AGENTSCORE_API_KEY env
|
|
935
|
+
# var when set; logs a warning and skips when no key is set
|
|
936
|
+
# (dev/testnet pattern).
|
|
919
937
|
has_payment_header = has_x402_header(request.headers) or has_mppx_header(request.headers)
|
|
920
|
-
if
|
|
921
|
-
gate_result =
|
|
938
|
+
if has_payment_header:
|
|
939
|
+
gate_result = (
|
|
940
|
+
await self._run_gate(ctx) if self.gate is not None else await self._run_wallet_sanctions_only(ctx)
|
|
941
|
+
)
|
|
922
942
|
if gate_result is not None:
|
|
923
943
|
return gate_result
|
|
924
944
|
|
|
@@ -1415,6 +1435,13 @@ class Checkout:
|
|
|
1415
1435
|
result = await _maybe_await(gate.run_gate(ctx))
|
|
1416
1436
|
return self._coerce_run_gate_result(ctx, result)
|
|
1417
1437
|
|
|
1438
|
+
# Gate configured without an API key — full policy enforcement requires
|
|
1439
|
+
# /v1/assess access, which we can't reach. Fall through to wallet OFAC
|
|
1440
|
+
# SDN enforcement (the strict-liability default) so the merchant still
|
|
1441
|
+
# gets the basic protection layer instead of silently allowing.
|
|
1442
|
+
if not gate.api_key:
|
|
1443
|
+
return await self._run_wallet_sanctions_only(ctx)
|
|
1444
|
+
|
|
1418
1445
|
# 2. per_request_policy resolves per-product compliance (e.g. wine vs
|
|
1419
1446
|
# generic merch). Return None to skip the gate entirely for this request.
|
|
1420
1447
|
policy: Any = None
|
|
@@ -1513,6 +1540,117 @@ class Checkout:
|
|
|
1513
1540
|
)
|
|
1514
1541
|
return None
|
|
1515
1542
|
|
|
1543
|
+
async def _run_wallet_sanctions_only(self, ctx: CheckoutContext) -> CheckoutResult | None:
|
|
1544
|
+
"""Wallet OFAC SDN enforcement.
|
|
1545
|
+
|
|
1546
|
+
Runs on settle (payment header present) when either ``self.gate`` is
|
|
1547
|
+
None OR a gate is configured but has no ``api_key`` to reach
|
|
1548
|
+
``/v1/assess`` for full policy enforcement (fallback to the
|
|
1549
|
+
strict-liability default).
|
|
1550
|
+
|
|
1551
|
+
Env knobs:
|
|
1552
|
+
- ``AGENTSCORE_API_KEY`` — required. No key → one-time warning + skip
|
|
1553
|
+
(dev/testnet pattern; production should always configure a key).
|
|
1554
|
+
- ``AGENTSCORE_BASE_URL`` — optional override for staging/dev API
|
|
1555
|
+
(e.g. ``https://api-dev.agentscore.sh`` or ``http://localhost:3002``).
|
|
1556
|
+
|
|
1557
|
+
Stripe SPT (no extractable wallet signer) → skip silently; Stripe runs
|
|
1558
|
+
its own OFAC screen on the buyer's Stripe account at customer creation.
|
|
1559
|
+
|
|
1560
|
+
Calls ``/v1/assess`` with the signer wallet as both the primary address
|
|
1561
|
+
and the signer block. The API enforces signer-sanctions unconditionally
|
|
1562
|
+
when a signer is present (no policy flag needed). Denies on OFAC SDN
|
|
1563
|
+
hit; fail-closed on unavailable lookup (strict liability — falsely
|
|
1564
|
+
allowing a sanctioned settle is an OFAC violation, falsely denying a
|
|
1565
|
+
clean buyer is just bad UX).
|
|
1566
|
+
"""
|
|
1567
|
+
import os
|
|
1568
|
+
|
|
1569
|
+
from agentscore_commerce._warnings import warn_missing_api_key_once
|
|
1570
|
+
|
|
1571
|
+
api_key = (self.gate.api_key if self.gate is not None else None) or os.environ.get("AGENTSCORE_API_KEY")
|
|
1572
|
+
if not api_key:
|
|
1573
|
+
warn_missing_api_key_once("checkout")
|
|
1574
|
+
return None
|
|
1575
|
+
|
|
1576
|
+
from agentscore_commerce.payment.signer import extract_payment_signer, read_x402_payment_header
|
|
1577
|
+
|
|
1578
|
+
x402_header = read_x402_payment_header(ctx.request.headers)
|
|
1579
|
+
authorization_header: str | None = None
|
|
1580
|
+
for header_key, header_value in ctx.request.headers.items():
|
|
1581
|
+
if header_key.lower() == "authorization":
|
|
1582
|
+
authorization_header = header_value
|
|
1583
|
+
break
|
|
1584
|
+
signer = extract_payment_signer(x402_header, authorization_header=authorization_header)
|
|
1585
|
+
if signer is None:
|
|
1586
|
+
# Stripe SPT path — no wallet signer, no OFAC check possible. Stripe
|
|
1587
|
+
# screens its own customer accounts; we have nothing to add here.
|
|
1588
|
+
return None
|
|
1589
|
+
|
|
1590
|
+
from agentscore_commerce.api import AgentScore
|
|
1591
|
+
from agentscore_commerce.identity._denial import denial_reason_status
|
|
1592
|
+
from agentscore_commerce.identity._response import denial_reason_to_body
|
|
1593
|
+
from agentscore_commerce.identity.types import DenialReason
|
|
1594
|
+
|
|
1595
|
+
base_url = os.environ.get("AGENTSCORE_BASE_URL")
|
|
1596
|
+
client_kwargs: dict[str, Any] = {"api_key": api_key}
|
|
1597
|
+
if base_url:
|
|
1598
|
+
client_kwargs["base_url"] = base_url
|
|
1599
|
+
client = AgentScore(**client_kwargs)
|
|
1600
|
+
from agentscore.errors import (
|
|
1601
|
+
AgentScoreError,
|
|
1602
|
+
InvalidCredentialError,
|
|
1603
|
+
TokenExpiredError,
|
|
1604
|
+
)
|
|
1605
|
+
|
|
1606
|
+
try:
|
|
1607
|
+
result = await client.aassess(
|
|
1608
|
+
address=signer.address,
|
|
1609
|
+
signer={"address": signer.address, "network": signer.network},
|
|
1610
|
+
)
|
|
1611
|
+
except (TokenExpiredError, InvalidCredentialError) as err:
|
|
1612
|
+
# 401 — credential issues map to invalid_credential. These are
|
|
1613
|
+
# unusual on the wallet-OFAC-only path (no operator_token) but
|
|
1614
|
+
# carried for parity with node's mapping.
|
|
1615
|
+
reason = DenialReason(
|
|
1616
|
+
code="invalid_credential" if isinstance(err, InvalidCredentialError) else "token_expired",
|
|
1617
|
+
message=str(err),
|
|
1618
|
+
)
|
|
1619
|
+
return CheckoutResult(
|
|
1620
|
+
status=denial_reason_status(reason),
|
|
1621
|
+
body=denial_reason_to_body(reason),
|
|
1622
|
+
headers={},
|
|
1623
|
+
reference_id=ctx.reference_id,
|
|
1624
|
+
settled=False,
|
|
1625
|
+
)
|
|
1626
|
+
except (AgentScoreError, Exception) as err:
|
|
1627
|
+
# 503 — API outage or network failure. Fail-closed: strict-liability.
|
|
1628
|
+
reason = DenialReason(code="api_error", message=str(err))
|
|
1629
|
+
return CheckoutResult(
|
|
1630
|
+
status=denial_reason_status(reason),
|
|
1631
|
+
body=denial_reason_to_body(reason),
|
|
1632
|
+
headers={},
|
|
1633
|
+
reference_id=ctx.reference_id,
|
|
1634
|
+
settled=False,
|
|
1635
|
+
)
|
|
1636
|
+
|
|
1637
|
+
decision = result.get("decision") if isinstance(result, dict) else None
|
|
1638
|
+
if decision == "deny":
|
|
1639
|
+
decision_reasons = result.get("decision_reasons") or [] if isinstance(result, dict) else []
|
|
1640
|
+
reason = DenialReason(
|
|
1641
|
+
code="wallet_not_trusted",
|
|
1642
|
+
reasons=list(decision_reasons),
|
|
1643
|
+
decision=decision,
|
|
1644
|
+
)
|
|
1645
|
+
return CheckoutResult(
|
|
1646
|
+
status=denial_reason_status(reason),
|
|
1647
|
+
body=denial_reason_to_body(reason),
|
|
1648
|
+
headers={},
|
|
1649
|
+
reference_id=ctx.reference_id,
|
|
1650
|
+
settled=False,
|
|
1651
|
+
)
|
|
1652
|
+
return None
|
|
1653
|
+
|
|
1516
1654
|
def _coerce_run_gate_result(
|
|
1517
1655
|
self,
|
|
1518
1656
|
ctx: CheckoutContext,
|
|
@@ -1776,10 +1914,25 @@ class Checkout:
|
|
|
1776
1914
|
)
|
|
1777
1915
|
return await self._build_success(ctx, outcome)
|
|
1778
1916
|
# _handle_mppx is only invoked when an ``Authorization: Payment`` header
|
|
1779
|
-
# was present, so a 402 here means mppx REJECTED the credential.
|
|
1780
|
-
#
|
|
1781
|
-
#
|
|
1782
|
-
#
|
|
1917
|
+
# was present, so a 402 here means mppx REJECTED the credential. Try to
|
|
1918
|
+
# classify the swallowed inner error (e.g. Tempo ``KeyNotFound``) into a
|
|
1919
|
+
# typed envelope agents can route on; fall back to the generic
|
|
1920
|
+
# ``payment_proof_invalid`` regenerate hint otherwise.
|
|
1921
|
+
classified = classify_mppx_failure(composed.failure_reason)
|
|
1922
|
+
if classified is not None:
|
|
1923
|
+
return CheckoutResult(
|
|
1924
|
+
status=classified.status,
|
|
1925
|
+
body=build_validation_error(
|
|
1926
|
+
code=classified.code,
|
|
1927
|
+
message=classified.message,
|
|
1928
|
+
next_steps=classified.next_steps,
|
|
1929
|
+
extra=classified.extra or None,
|
|
1930
|
+
),
|
|
1931
|
+
headers=dict(composed.headers or {}),
|
|
1932
|
+
reference_id=ctx.reference_id,
|
|
1933
|
+
settled=False,
|
|
1934
|
+
settle_phase="verify_failed",
|
|
1935
|
+
)
|
|
1783
1936
|
return CheckoutResult(
|
|
1784
1937
|
status=400,
|
|
1785
1938
|
body=build_validation_error(
|
|
@@ -1801,9 +1954,34 @@ class Checkout:
|
|
|
1801
1954
|
if ctx.pricing is None:
|
|
1802
1955
|
msg = "Checkout._emit_402: pricing not computed"
|
|
1803
1956
|
raise RuntimeError(msg)
|
|
1804
|
-
|
|
1957
|
+
try:
|
|
1958
|
+
await self._resolve_recipients(ctx)
|
|
1959
|
+
except CheckoutValidationError as err:
|
|
1960
|
+
return CheckoutResult(
|
|
1961
|
+
status=err.status,
|
|
1962
|
+
body=build_validation_error(
|
|
1963
|
+
code=err.code,
|
|
1964
|
+
message=err.message,
|
|
1965
|
+
next_steps={"action": err.action, "user_message": err.message},
|
|
1966
|
+
extra=err.extra,
|
|
1967
|
+
),
|
|
1968
|
+
headers={},
|
|
1969
|
+
reference_id=ctx.reference_id,
|
|
1970
|
+
settled=False,
|
|
1971
|
+
settle_phase="mint_recipients_failed",
|
|
1972
|
+
)
|
|
1805
1973
|
emit_rails = _apply_recipient_overrides(self.rails, ctx.recipients)
|
|
1806
1974
|
|
|
1975
|
+
# Auto-drop stripe when priced below Stripe's $0.50 USD minimum so the
|
|
1976
|
+
# emitted accepted_methods + how_to_pay stay consistent with what the
|
|
1977
|
+
# mppx compose layer will actually accept (see build_mppx_compose_rails).
|
|
1978
|
+
# Without this, the 402 body advertises a stripe rail that has no
|
|
1979
|
+
# matching WWW-Authenticate challenge — agents see it offered but any
|
|
1980
|
+
# SPT pay attempt fails. The compose-time auto-drop emits the
|
|
1981
|
+
# user-facing warn; here we just strip the slot from the discovery body.
|
|
1982
|
+
if Decimal(str(ctx.pricing.amount_usd)) < STRIPE_MIN_CHARGE_USD and "stripe" in emit_rails:
|
|
1983
|
+
emit_rails = {k: v for k, v in emit_rails.items() if k != "stripe"}
|
|
1984
|
+
|
|
1807
1985
|
accepted = await build_accepted_methods(
|
|
1808
1986
|
tempo=_pick(emit_rails, "tempo", TempoRailSpec),
|
|
1809
1987
|
x402_base=_pick(emit_rails, "x402_base", X402BaseRailSpec),
|
|
@@ -1822,6 +2000,10 @@ class Checkout:
|
|
|
1822
2000
|
total_usd=f"{ctx.pricing.amount_usd:.{pricing_decimals}f}",
|
|
1823
2001
|
rails=how_to_pay_rails,
|
|
1824
2002
|
decimals=pricing_decimals,
|
|
2003
|
+
# Merchants without an identity-bearing policy flag get clean commands
|
|
2004
|
+
# without an X-Operator-Token header — agents don't need one to satisfy
|
|
2005
|
+
# wallet OFAC enforcement (the always-on default per TEC-311).
|
|
2006
|
+
op_token_placeholder=None if not self._has_identity_gate() else "<your_opc_token>",
|
|
1825
2007
|
)
|
|
1826
2008
|
pricing_block = ctx.pricing.block or build_pricing_block(
|
|
1827
2009
|
subtotal_cents=ctx.pricing.amount_usd * 100,
|
|
@@ -1871,7 +2053,10 @@ class Checkout:
|
|
|
1871
2053
|
pricing=pricing_block,
|
|
1872
2054
|
amount_usd=f"{ctx.pricing.amount_usd:.{pricing_decimals}f}",
|
|
1873
2055
|
retry_body=ctx.request.body,
|
|
1874
|
-
|
|
2056
|
+
# Merchants without an identity-bearing gate get a clean 402: no
|
|
2057
|
+
# AgentScore-identity bootstrap describing a verification flow they
|
|
2058
|
+
# don't run. Wallet OFAC (the always-on default) doesn't need it.
|
|
2059
|
+
agent_memory=first_encounter_agent_memory(first_encounter=self._has_identity_gate()),
|
|
1875
2060
|
product=ctx.pricing.product,
|
|
1876
2061
|
extra=ctx.pricing.body_extras,
|
|
1877
2062
|
x402=X402PaymentRequired(
|