agentscore-commerce 1.3.2__tar.gz → 1.3.4__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-1.3.2 → agentscore_commerce-1.3.4}/CLAUDE.md +1 -1
- {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/PKG-INFO +17 -1
- {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/README.md +16 -0
- {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/agentscore_commerce/discovery/probe.py +5 -1
- {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/agentscore_commerce/payment/__init__.py +8 -0
- {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/agentscore_commerce/payment/x402_server.py +65 -0
- {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/agentscore_commerce/payment/x402_settle.py +51 -6
- {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/examples/api_provider.py +10 -2
- {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/examples/multi_rail_merchant.py +11 -17
- {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/pyproject.toml +1 -1
- {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/tests/test_discovery.py +5 -1
- {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/tests/test_lifted_helpers.py +214 -0
- {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/tests/test_payment_servers.py +50 -0
- {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/uv.lock +1 -1
- {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/.github/dependabot.yml +0 -0
- {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/.github/workflows/ci.yml +0 -0
- {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/.github/workflows/publish.yml +0 -0
- {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/.github/workflows/security.yml +0 -0
- {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/.gitignore +0 -0
- {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/CODE_OF_CONDUCT.md +0 -0
- {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/CONTRIBUTING.md +0 -0
- {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/LICENSE +0 -0
- {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/SECURITY.md +0 -0
- {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/agentscore_commerce/__init__.py +0 -0
- {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/agentscore_commerce/api/__init__.py +0 -0
- {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/agentscore_commerce/challenge/__init__.py +0 -0
- {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/agentscore_commerce/challenge/accepted_methods.py +0 -0
- {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/agentscore_commerce/challenge/agent_instructions.py +0 -0
- {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/agentscore_commerce/challenge/agent_memory.py +0 -0
- {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/agentscore_commerce/challenge/body.py +0 -0
- {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/agentscore_commerce/challenge/how_to_pay.py +0 -0
- {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/agentscore_commerce/challenge/identity.py +0 -0
- {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/agentscore_commerce/challenge/order_receipt.py +0 -0
- {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/agentscore_commerce/challenge/pricing.py +0 -0
- {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/agentscore_commerce/challenge/respond_402.py +0 -0
- {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/agentscore_commerce/challenge/validation_error.py +0 -0
- {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/agentscore_commerce/discovery/__init__.py +0 -0
- {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/agentscore_commerce/discovery/bazaar.py +0 -0
- {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/agentscore_commerce/discovery/llms_txt.py +0 -0
- {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/agentscore_commerce/discovery/openapi.py +0 -0
- {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/agentscore_commerce/discovery/robots_tag.py +0 -0
- {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/agentscore_commerce/discovery/skill_md.py +0 -0
- {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/agentscore_commerce/discovery/well_known_mpp.py +0 -0
- {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/agentscore_commerce/discovery/well_known_x402.py +0 -0
- {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/agentscore_commerce/identity/__init__.py +0 -0
- {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/agentscore_commerce/identity/_denial.py +0 -0
- {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/agentscore_commerce/identity/_response.py +0 -0
- {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/agentscore_commerce/identity/a2a.py +0 -0
- {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/agentscore_commerce/identity/address.py +0 -0
- {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/agentscore_commerce/identity/aiohttp.py +0 -0
- {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/agentscore_commerce/identity/cache.py +0 -0
- {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/agentscore_commerce/identity/client.py +0 -0
- {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/agentscore_commerce/identity/django.py +0 -0
- {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/agentscore_commerce/identity/fastapi.py +0 -0
- {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/agentscore_commerce/identity/flask.py +0 -0
- {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/agentscore_commerce/identity/middleware.py +0 -0
- {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/agentscore_commerce/identity/policy.py +0 -0
- {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/agentscore_commerce/identity/py.typed +0 -0
- {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/agentscore_commerce/identity/sanic.py +0 -0
- {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/agentscore_commerce/identity/sessions.py +0 -0
- {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/agentscore_commerce/identity/signer.py +0 -0
- {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/agentscore_commerce/identity/types.py +0 -0
- {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/agentscore_commerce/identity/ucp.py +0 -0
- {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/agentscore_commerce/payment/directive.py +0 -0
- {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/agentscore_commerce/payment/dispatch.py +0 -0
- {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/agentscore_commerce/payment/headers.py +0 -0
- {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/agentscore_commerce/payment/idempotency.py +0 -0
- {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/agentscore_commerce/payment/mppx_server.py +0 -0
- {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/agentscore_commerce/payment/networks.py +0 -0
- {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/agentscore_commerce/payment/rails.py +0 -0
- {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/agentscore_commerce/payment/settlement_override.py +0 -0
- {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/agentscore_commerce/payment/signer.py +0 -0
- {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/agentscore_commerce/payment/usdc.py +0 -0
- {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/agentscore_commerce/payment/wwwauthenticate.py +0 -0
- {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/agentscore_commerce/payment/x402.py +0 -0
- {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/agentscore_commerce/payment/x402_validation.py +0 -0
- {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/agentscore_commerce/stripe_multichain/__init__.py +0 -0
- {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/agentscore_commerce/stripe_multichain/mppx_stripe.py +0 -0
- {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/agentscore_commerce/stripe_multichain/payment_intent.py +0 -0
- {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/agentscore_commerce/stripe_multichain/pi_cache.py +0 -0
- {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/agentscore_commerce/stripe_multichain/simulate_deposit.py +0 -0
- {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/examples/README.md +0 -0
- {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/examples/compliance_merchant.py +0 -0
- {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/examples/identity_only.py +0 -0
- {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/examples/per_product_policy_merchant.py +0 -0
- {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/examples/stripe_multichain_merchant.py +0 -0
- {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/examples/variable_cost_merchant.py +0 -0
- {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/lefthook.yml +0 -0
- {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/ruff.toml +0 -0
- {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/tests/__init__.py +0 -0
- {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/tests/conftest.py +0 -0
- {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/tests/test_a2a.py +0 -0
- {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/tests/test_address.py +0 -0
- {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/tests/test_agent_memory_emitter.py +0 -0
- {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/tests/test_aiohttp.py +0 -0
- {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/tests/test_api_reexport.py +0 -0
- {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/tests/test_cache.py +0 -0
- {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/tests/test_challenge.py +0 -0
- {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/tests/test_client.py +0 -0
- {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/tests/test_coverage_fillers.py +0 -0
- {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/tests/test_denial.py +0 -0
- {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/tests/test_django.py +0 -0
- {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/tests/test_fastapi.py +0 -0
- {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/tests/test_flask.py +0 -0
- {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/tests/test_gate_quota_info.py +0 -0
- {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/tests/test_idempotency_helper.py +0 -0
- {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/tests/test_integration.py +0 -0
- {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/tests/test_middleware.py +0 -0
- {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/tests/test_payment_directive.py +0 -0
- {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/tests/test_payment_dispatch.py +0 -0
- {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/tests/test_payment_headers.py +0 -0
- {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/tests/test_payment_misc.py +0 -0
- {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/tests/test_payment_signer.py +0 -0
- {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/tests/test_policy.py +0 -0
- {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/tests/test_pricing.py +0 -0
- {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/tests/test_response.py +0 -0
- {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/tests/test_robots_tag.py +0 -0
- {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/tests/test_sanic.py +0 -0
- {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/tests/test_sessions.py +0 -0
- {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/tests/test_signer_match.py +0 -0
- {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/tests/test_skill_md.py +0 -0
- {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/tests/test_stripe_multichain.py +0 -0
- {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/tests/test_ucp.py +0 -0
- {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/tests/test_validation_error.py +0 -0
- {agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/vulture_whitelist.py +0 -0
|
@@ -9,7 +9,7 @@ Every helper is extracted from a real consumer, not speculated.
|
|
|
9
9
|
| Submodule | What it is |
|
|
10
10
|
|---|---|
|
|
11
11
|
| `agentscore_commerce.identity.{fastapi,flask,django,aiohttp,sanic,middleware}` | Trust gate middleware (KYC, age, sanctions, jurisdiction) |
|
|
12
|
-
| `agentscore_commerce.payment` | Networks/USDC/rails registries, paymentauth.org directive builders, `create_x402_server` (wraps
|
|
12
|
+
| `agentscore_commerce.payment` | Networks/USDC/rails registries, paymentauth.org directive builders, `create_x402_server` (wraps `x402[evm]>=2.9` + `cdp-sdk` for `facilitator="coinbase"`; install via the `coinbase` extra), `build_x402_accepts_for_402` (build the 402's `accepts[]` from the registered scheme — derives the right `extra.name` per network), `process_x402_settle` (verify+settle in one call), `create_mppx_server` (wraps `pympp[server,tempo,stripe]>=0.6`), dispatch-by-network, signer extraction, WWW-Authenticate header, Settlement-Overrides header |
|
|
13
13
|
| `agentscore_commerce.discovery` | Discovery probe, Bazaar wrapper, `/.well-known/mpp.json`, `llms.txt` builder, `skill.md` builder (Claude-Skill-compatible agent-discovery manifest), OpenAPI snippets, `NoindexNonDiscoveryMiddleware` ASGI middleware |
|
|
14
14
|
| `agentscore_commerce.challenge` | 402-body builders: accepted_methods, identity_metadata, how_to_pay, agent_instructions, build_402_body, `build_validation_error` (4xx body builder) |
|
|
15
15
|
| `agentscore_commerce.stripe_multichain` | Multichain PaymentIntent helper, deposit-address lookup, testnet simulator, mppx Stripe wrapper |
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: agentscore-commerce
|
|
3
|
-
Version: 1.3.
|
|
3
|
+
Version: 1.3.4
|
|
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
|
|
@@ -291,6 +291,22 @@ await simulate_deposit_if_test_mode(SimulateDepositIfTestModeInput(
|
|
|
291
291
|
))
|
|
292
292
|
```
|
|
293
293
|
|
|
294
|
+
## Build the x402 accepts entry for the 402 challenge
|
|
295
|
+
|
|
296
|
+
```python
|
|
297
|
+
from agentscore_commerce.payment import build_x402_accepts_for_402
|
|
298
|
+
|
|
299
|
+
x402_accepts = build_x402_accepts_for_402(
|
|
300
|
+
x402_server,
|
|
301
|
+
network=X402_BASE,
|
|
302
|
+
price=f"${total_usd}",
|
|
303
|
+
pay_to=os.environ["TREASURY_BASE_RECIPIENT"],
|
|
304
|
+
max_timeout_seconds=300,
|
|
305
|
+
)
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
Returns a list of plain dicts ready for the 402 body's `accepts[]`. `extra.name` is derived from the registered scheme metadata so the EIP-712 domain matches the on-chain USDC contract.
|
|
309
|
+
|
|
294
310
|
## Drop-in 402 + settle (x402)
|
|
295
311
|
|
|
296
312
|
```python
|
|
@@ -247,6 +247,22 @@ await simulate_deposit_if_test_mode(SimulateDepositIfTestModeInput(
|
|
|
247
247
|
))
|
|
248
248
|
```
|
|
249
249
|
|
|
250
|
+
## Build the x402 accepts entry for the 402 challenge
|
|
251
|
+
|
|
252
|
+
```python
|
|
253
|
+
from agentscore_commerce.payment import build_x402_accepts_for_402
|
|
254
|
+
|
|
255
|
+
x402_accepts = build_x402_accepts_for_402(
|
|
256
|
+
x402_server,
|
|
257
|
+
network=X402_BASE,
|
|
258
|
+
price=f"${total_usd}",
|
|
259
|
+
pay_to=os.environ["TREASURY_BASE_RECIPIENT"],
|
|
260
|
+
max_timeout_seconds=300,
|
|
261
|
+
)
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
Returns a list of plain dicts ready for the 402 body's `accepts[]`. `extra.name` is derived from the registered scheme metadata so the EIP-712 domain matches the on-chain USDC contract.
|
|
265
|
+
|
|
250
266
|
## Drop-in 402 + settle (x402)
|
|
251
267
|
|
|
252
268
|
```python
|
{agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/agentscore_commerce/discovery/probe.py
RENAMED
|
@@ -39,7 +39,11 @@ def sample_x402_accept_for_network(caip2: str, amount_atomic: str = "1000000") -
|
|
|
39
39
|
"asset": USDC.base.mainnet.address,
|
|
40
40
|
"payTo": _ZERO_EVM_PAYTO,
|
|
41
41
|
"maxTimeoutSeconds": 300,
|
|
42
|
-
|
|
42
|
+
# ``extra.name`` mirrors the on-chain USDC contract's ``name()`` return value
|
|
43
|
+
# because EIP-712 domain hashes include this string. Wrong name → every
|
|
44
|
+
# signed payload fails facilitator verify with ``invalid_exact_evm_payload_signature``.
|
|
45
|
+
# Base mainnet USDC returns "USD Coin"; base sepolia USDC returns "USDC".
|
|
46
|
+
"extra": {"name": "USD Coin", "version": "2"},
|
|
43
47
|
}
|
|
44
48
|
if caip2 == networks.base.sepolia.caip2:
|
|
45
49
|
return {
|
{agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/agentscore_commerce/payment/__init__.py
RENAMED
|
@@ -52,6 +52,7 @@ from agentscore_commerce.payment.x402_server import (
|
|
|
52
52
|
CustomScheme,
|
|
53
53
|
X402FacilitatorChoice,
|
|
54
54
|
X402SymbolicRail,
|
|
55
|
+
build_x402_accepts_for_402,
|
|
55
56
|
create_x402_server,
|
|
56
57
|
)
|
|
57
58
|
from agentscore_commerce.payment.x402_settle import (
|
|
@@ -61,7 +62,10 @@ from agentscore_commerce.payment.x402_settle import (
|
|
|
61
62
|
ProcessX402SettleResult,
|
|
62
63
|
ProcessX402SettleSuccess,
|
|
63
64
|
classify_x402_settle_result,
|
|
65
|
+
coerce_payment_payload,
|
|
66
|
+
coerce_resource_config,
|
|
64
67
|
process_x402_settle,
|
|
68
|
+
settle_result_to_json_bytes,
|
|
65
69
|
)
|
|
66
70
|
from agentscore_commerce.payment.x402_validation import (
|
|
67
71
|
X402_SUPPORTED_BASE_NETWORKS,
|
|
@@ -114,7 +118,10 @@ __all__ = [
|
|
|
114
118
|
"build_payment_directive",
|
|
115
119
|
"build_payment_headers",
|
|
116
120
|
"build_payment_request_blob",
|
|
121
|
+
"build_x402_accepts_for_402",
|
|
117
122
|
"classify_x402_settle_result",
|
|
123
|
+
"coerce_payment_payload",
|
|
124
|
+
"coerce_resource_config",
|
|
118
125
|
"create_mppx_server",
|
|
119
126
|
"create_x402_server",
|
|
120
127
|
"dispatch_settlement_by_network",
|
|
@@ -130,6 +137,7 @@ __all__ = [
|
|
|
130
137
|
"rails",
|
|
131
138
|
"read_x402_payment_header",
|
|
132
139
|
"register_x402_schemes_v1_v2",
|
|
140
|
+
"settle_result_to_json_bytes",
|
|
133
141
|
"settlement_override_header",
|
|
134
142
|
"validate_x402_network_config",
|
|
135
143
|
"verify_x402_request",
|
{agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/agentscore_commerce/payment/x402_server.py
RENAMED
|
@@ -266,10 +266,75 @@ async def create_x402_server(
|
|
|
266
266
|
return server
|
|
267
267
|
|
|
268
268
|
|
|
269
|
+
def build_x402_accepts_for_402(
|
|
270
|
+
server: Any,
|
|
271
|
+
*,
|
|
272
|
+
network: str,
|
|
273
|
+
price: str,
|
|
274
|
+
pay_to: str,
|
|
275
|
+
scheme: str = "exact",
|
|
276
|
+
max_timeout_seconds: int = 300,
|
|
277
|
+
extensions: list[str] | None = None,
|
|
278
|
+
) -> list[dict[str, Any]]:
|
|
279
|
+
"""Build x402 ``accepts[]`` entries for a 402 challenge body.
|
|
280
|
+
|
|
281
|
+
Wraps ``server.build_payment_requirements(...)`` so merchants don't have to:
|
|
282
|
+
|
|
283
|
+
1. Import ``x402.schemas.config.ResourceConfig`` themselves
|
|
284
|
+
2. Remember to call ``model_dump(by_alias=True, mode="json")`` on each Pydantic
|
|
285
|
+
requirement so the surrounding JSON response can serialize it
|
|
286
|
+
3. Hardcode ``extra`` (which differs by the actual on-chain contract: base mainnet
|
|
287
|
+
USDC has ``name="USD Coin"``, base sepolia USDC has ``name="USDC"`` — EIP-712
|
|
288
|
+
domain hashes differ, so getting this wrong silently breaks every signature
|
|
289
|
+
verify at the facilitator)
|
|
290
|
+
|
|
291
|
+
Returns a list of plain dicts in the shape that x402 expects on the wire — drop
|
|
292
|
+
them straight into the ``accepts`` field of the 402 challenge body.
|
|
293
|
+
|
|
294
|
+
Raises ``Exception`` if the underlying ``build_payment_requirements`` raises;
|
|
295
|
+
callers should wrap with ``try/except`` and either omit x402 from the 402 or
|
|
296
|
+
surface a 5xx (depending on whether other rails are advertised).
|
|
297
|
+
"""
|
|
298
|
+
config_cls_module = _import_optional("x402.schemas.config")
|
|
299
|
+
config_cls = getattr(config_cls_module, "ResourceConfig", None) if config_cls_module else None
|
|
300
|
+
if config_cls is None:
|
|
301
|
+
msg = "x402 not installed — run `pip install 'x402[evm,fastapi]>=2.9,<3'` to use build_x402_accepts_for_402."
|
|
302
|
+
raise ImportError(msg)
|
|
303
|
+
config = config_cls(
|
|
304
|
+
scheme=scheme,
|
|
305
|
+
network=network,
|
|
306
|
+
price=price,
|
|
307
|
+
pay_to=pay_to,
|
|
308
|
+
max_timeout_seconds=max_timeout_seconds,
|
|
309
|
+
)
|
|
310
|
+
requirements = (
|
|
311
|
+
server.build_payment_requirements(config, extensions)
|
|
312
|
+
if extensions
|
|
313
|
+
else server.build_payment_requirements(config)
|
|
314
|
+
)
|
|
315
|
+
# Pydantic ``PaymentRequirements`` is the live shape under x402 2.9+. Older
|
|
316
|
+
# versions (and test stubs) return plain dicts that already match the wire form.
|
|
317
|
+
out: list[dict[str, Any]] = []
|
|
318
|
+
for req in requirements:
|
|
319
|
+
model_dump = getattr(req, "model_dump", None)
|
|
320
|
+
if callable(model_dump):
|
|
321
|
+
out.append(model_dump(by_alias=True, mode="json"))
|
|
322
|
+
elif isinstance(req, dict):
|
|
323
|
+
out.append(dict(req))
|
|
324
|
+
else:
|
|
325
|
+
msg = (
|
|
326
|
+
f"build_payment_requirements returned {type(req).__name__}; expected a "
|
|
327
|
+
"Pydantic PaymentRequirements or a dict."
|
|
328
|
+
)
|
|
329
|
+
raise TypeError(msg)
|
|
330
|
+
return out
|
|
331
|
+
|
|
332
|
+
|
|
269
333
|
__all__ = [
|
|
270
334
|
"CreateX402ServerOptions",
|
|
271
335
|
"CustomScheme",
|
|
272
336
|
"X402FacilitatorChoice",
|
|
273
337
|
"X402SymbolicRail",
|
|
338
|
+
"build_x402_accepts_for_402",
|
|
274
339
|
"create_x402_server",
|
|
275
340
|
]
|
{agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/agentscore_commerce/payment/x402_settle.py
RENAMED
|
@@ -189,7 +189,7 @@ def classify_x402_settle_result(result: ProcessX402SettleResult) -> ClassifiedX4
|
|
|
189
189
|
return None
|
|
190
190
|
|
|
191
191
|
|
|
192
|
-
def
|
|
192
|
+
def coerce_resource_config(config: Any) -> Any:
|
|
193
193
|
"""Best-effort dict → x402 ``ResourceConfig`` coercion.
|
|
194
194
|
|
|
195
195
|
Consumers ported from the JS / Hono stack often pass a plain dict with the JS-style
|
|
@@ -220,10 +220,40 @@ def _coerce_resource_config(config: Any) -> Any:
|
|
|
220
220
|
return config
|
|
221
221
|
|
|
222
222
|
|
|
223
|
+
def coerce_payment_payload(payload: Any) -> Any:
|
|
224
|
+
"""Best-effort dict → x402 ``PaymentPayload`` (v1 or v2) coercion.
|
|
225
|
+
|
|
226
|
+
``verify_x402_request`` returns ``payload`` as a plain dict (the result of
|
|
227
|
+
``json.loads(base64.b64decode(X-Payment))``), but x402 2.9's
|
|
228
|
+
``server.verify_payment`` / ``server.settle_payment`` call ``payload.get_scheme()``
|
|
229
|
+
and other typed-model methods on it. Without coercion, the dict raises
|
|
230
|
+
``AttributeError("'dict' object has no attribute 'get_scheme'")`` on the verify leg.
|
|
231
|
+
|
|
232
|
+
Routes by the ``x402Version`` field: ``1`` → ``PaymentPayloadV1`` (flat shape with
|
|
233
|
+
top-level ``scheme`` / ``network``); anything else → ``PaymentPayload`` (v2 shape
|
|
234
|
+
nested under ``accepted``). Falls back to the original dict on any failure so callers
|
|
235
|
+
that already pass typed instances or unusual shapes still flow through unchanged.
|
|
236
|
+
"""
|
|
237
|
+
if not isinstance(payload, dict):
|
|
238
|
+
return payload
|
|
239
|
+
try:
|
|
240
|
+
from x402.schemas import PaymentPayload
|
|
241
|
+
from x402.schemas.v1 import PaymentPayloadV1
|
|
242
|
+
except ImportError:
|
|
243
|
+
return payload
|
|
244
|
+
version = payload.get("x402Version")
|
|
245
|
+
model = PaymentPayloadV1 if version == 1 else PaymentPayload
|
|
246
|
+
try:
|
|
247
|
+
return model.model_validate(payload)
|
|
248
|
+
except Exception:
|
|
249
|
+
return payload
|
|
250
|
+
|
|
251
|
+
|
|
223
252
|
async def process_x402_settle(input: ProcessX402SettleInput) -> ProcessX402SettleResult:
|
|
224
253
|
"""Run the x402 verify→settle flow and return a tagged outcome."""
|
|
225
254
|
server = input.x402_server
|
|
226
|
-
resource_config =
|
|
255
|
+
resource_config = coerce_resource_config(input.resource_config)
|
|
256
|
+
payload = coerce_payment_payload(input.payload)
|
|
227
257
|
|
|
228
258
|
try:
|
|
229
259
|
built_requirements = server.build_payment_requirements(resource_config)
|
|
@@ -267,7 +297,7 @@ async def process_x402_settle(input: ProcessX402SettleInput) -> ProcessX402Settl
|
|
|
267
297
|
# — not ``process_payment_request`` (a fictional method that earlier versions of this
|
|
268
298
|
# helper called and only ever worked against test stubs).
|
|
269
299
|
try:
|
|
270
|
-
verify_result = await server.verify_payment(
|
|
300
|
+
verify_result = await server.verify_payment(payload, matched_requirement)
|
|
271
301
|
except Exception as err:
|
|
272
302
|
return ProcessX402SettleFailure(phase="facilitator_error", step="verify_payment", error=err)
|
|
273
303
|
|
|
@@ -288,11 +318,10 @@ async def process_x402_settle(input: ProcessX402SettleInput) -> ProcessX402Settl
|
|
|
288
318
|
return ProcessX402SettleFailure(phase="verify_failed", verify_result=verify_result)
|
|
289
319
|
|
|
290
320
|
try:
|
|
291
|
-
settle_result = await server.settle_payment(
|
|
321
|
+
settle_result = await server.settle_payment(payload, matched_requirement)
|
|
292
322
|
payment_response_header: str | None = None
|
|
293
323
|
if settle_result is not None:
|
|
294
|
-
|
|
295
|
-
payment_response_header = base64.b64encode(payload_bytes).decode()
|
|
324
|
+
payment_response_header = base64.b64encode(settle_result_to_json_bytes(settle_result)).decode()
|
|
296
325
|
return ProcessX402SettleSuccess(
|
|
297
326
|
matched_requirement=matched_requirement,
|
|
298
327
|
settle_result=settle_result,
|
|
@@ -301,3 +330,19 @@ async def process_x402_settle(input: ProcessX402SettleInput) -> ProcessX402Settl
|
|
|
301
330
|
)
|
|
302
331
|
except Exception as err:
|
|
303
332
|
return ProcessX402SettleFailure(phase="settle_failed", error=err, matched_requirement=matched_requirement)
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
def settle_result_to_json_bytes(settle_result: Any) -> bytes:
|
|
336
|
+
"""Serialize the settle result to a base64-friendly JSON byte string.
|
|
337
|
+
|
|
338
|
+
x402 2.9's ``settle_payment`` returns a Pydantic ``SettleResponse`` model that
|
|
339
|
+
``json.dumps`` rejects with ``TypeError: Object of type SettleResponse is not
|
|
340
|
+
JSON serializable``. Use ``model_dump_json(by_alias=True)`` for Pydantic models
|
|
341
|
+
(so emitted keys match the wire shape — ``errorReason`` / ``errorMessage`` rather
|
|
342
|
+
than the snake_case attrs) and fall through to ``json.dumps`` for plain dicts
|
|
343
|
+
(used by older x402 / test stubs).
|
|
344
|
+
"""
|
|
345
|
+
model_dump_json = getattr(settle_result, "model_dump_json", None)
|
|
346
|
+
if callable(model_dump_json):
|
|
347
|
+
return model_dump_json(by_alias=True).encode()
|
|
348
|
+
return json.dumps(settle_result, separators=(",", ":")).encode()
|
|
@@ -128,8 +128,16 @@ async def search(request: Request):
|
|
|
128
128
|
"payTo": os.environ["X402_BASE_RECIPIENT"],
|
|
129
129
|
"maxTimeoutSeconds": 300,
|
|
130
130
|
# EIP-712 domain required by every x402 EVM client to sign
|
|
131
|
-
# EIP-3009 TransferWithAuthorization.
|
|
132
|
-
|
|
131
|
+
# EIP-3009 TransferWithAuthorization. ``name`` MUST match the
|
|
132
|
+
# on-chain USDC contract's ``name()`` — base mainnet returns
|
|
133
|
+
# "USD Coin", base sepolia returns "USDC". Wrong value silently
|
|
134
|
+
# breaks signature verify at the facilitator. Production code
|
|
135
|
+
# should use ``build_x402_accepts_for_402(server, ...)`` which
|
|
136
|
+
# derives ``extra`` from the registered scheme metadata.
|
|
137
|
+
"extra": {
|
|
138
|
+
"name": "USD Coin" if X402_BASE_NETWORK.split(":")[-1] == "8453" else "USDC",
|
|
139
|
+
"version": "2",
|
|
140
|
+
},
|
|
133
141
|
},
|
|
134
142
|
]
|
|
135
143
|
return JSONResponse(
|
|
@@ -69,6 +69,7 @@ from agentscore_commerce.payment import (
|
|
|
69
69
|
ProcessX402SettleInput,
|
|
70
70
|
ValidateX402NetworkConfigInput,
|
|
71
71
|
VerifyX402RequestInput,
|
|
72
|
+
build_x402_accepts_for_402,
|
|
72
73
|
networks,
|
|
73
74
|
process_x402_settle,
|
|
74
75
|
validate_x402_network_config,
|
|
@@ -250,24 +251,17 @@ async def purchase(request: Request, assess: dict = Depends(get_assess_data)):
|
|
|
250
251
|
),
|
|
251
252
|
x402=PaymentRequiredHeaderInput(
|
|
252
253
|
x402_version=2,
|
|
254
|
+
# Base accept comes from the registered x402 scheme — `extra` (incl. the
|
|
255
|
+
# network-correct USDC `name`) is filled in automatically. Solana goes
|
|
256
|
+
# through MPP `solana/charge` not x402's exact scheme, so it stays inline.
|
|
253
257
|
accepts=[
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
"
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
USDC.base.sepolia.address
|
|
262
|
-
if networks.base.sepolia.caip2 == X402_BASE_NETWORK
|
|
263
|
-
else USDC.base.mainnet.address
|
|
264
|
-
),
|
|
265
|
-
"payTo": deposit_addresses["base"],
|
|
266
|
-
"maxTimeoutSeconds": 300,
|
|
267
|
-
# EIP-712 domain — required by every x402 EVM client to
|
|
268
|
-
# sign EIP-3009 TransferWithAuthorization.
|
|
269
|
-
"extra": {"name": "USDC", "version": "2"},
|
|
270
|
-
},
|
|
258
|
+
*build_x402_accepts_for_402(
|
|
259
|
+
x402_server,
|
|
260
|
+
network=X402_BASE_NETWORK,
|
|
261
|
+
price=f"${total_usd}",
|
|
262
|
+
pay_to=deposit_addresses["base"],
|
|
263
|
+
max_timeout_seconds=300,
|
|
264
|
+
),
|
|
271
265
|
{
|
|
272
266
|
"scheme": "exact",
|
|
273
267
|
"network": SOLANA_NETWORK_CAIP2,
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "agentscore-commerce"
|
|
7
|
-
version = "1.3.
|
|
7
|
+
version = "1.3.4"
|
|
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"
|
|
@@ -176,7 +176,9 @@ def test_sample_accept_base_mainnet() -> None:
|
|
|
176
176
|
assert e is not None
|
|
177
177
|
assert e["network"] == "eip155:8453"
|
|
178
178
|
assert e["scheme"] == "exact"
|
|
179
|
-
|
|
179
|
+
# Base mainnet USDC's contract returns ``name() == "USD Coin"``. ``extra.name``
|
|
180
|
+
# mirrors that for EIP-712 domain-hash parity with what the facilitator validates.
|
|
181
|
+
assert e["extra"] == {"name": "USD Coin", "version": "2"}
|
|
180
182
|
|
|
181
183
|
|
|
182
184
|
def test_sample_accept_base_sepolia() -> None:
|
|
@@ -185,6 +187,8 @@ def test_sample_accept_base_sepolia() -> None:
|
|
|
185
187
|
e = sample_x402_accept_for_network("eip155:84532")
|
|
186
188
|
assert e is not None
|
|
187
189
|
assert e["network"] == "eip155:84532"
|
|
190
|
+
# Base sepolia USDC contract returns ``name() == "USDC"`` (differs from mainnet).
|
|
191
|
+
assert e["extra"] == {"name": "USDC", "version": "2"}
|
|
188
192
|
|
|
189
193
|
|
|
190
194
|
def test_sample_accept_solana_mainnet() -> None:
|
|
@@ -534,6 +534,220 @@ async def test_process_x402_settle_passes_typed_resource_config_unchanged():
|
|
|
534
534
|
assert captured["cfg"] is typed
|
|
535
535
|
|
|
536
536
|
|
|
537
|
+
@pytest.mark.asyncio
|
|
538
|
+
async def test_process_x402_settle_coerces_dict_payload_v2_to_typed_payment_payload():
|
|
539
|
+
"""Same fix as resource_config: ``verify_x402_request`` returns ``payload`` as a
|
|
540
|
+
plain dict (the result of ``json.loads(base64.b64decode(X-Payment))``); x402 2.9's
|
|
541
|
+
``server.verify_payment`` calls ``payload.get_scheme()`` and other typed-model methods
|
|
542
|
+
on it. Coerce dicts → ``PaymentPayload`` (or ``PaymentPayloadV1`` when ``x402Version=1``)
|
|
543
|
+
so the helper doesn't ``AttributeError`` at the verify leg.
|
|
544
|
+
"""
|
|
545
|
+
captured: dict = {}
|
|
546
|
+
|
|
547
|
+
class _CapturingServer:
|
|
548
|
+
def build_payment_requirements(self, _cfg: object, _ext: object = None) -> list:
|
|
549
|
+
return [{"id": "req1"}]
|
|
550
|
+
|
|
551
|
+
async def verify_payment(self, payload: object, _req: object) -> dict:
|
|
552
|
+
captured["verify_payload"] = payload
|
|
553
|
+
return {"is_valid": True}
|
|
554
|
+
|
|
555
|
+
async def settle_payment(self, payload: object, _req: object) -> dict:
|
|
556
|
+
captured["settle_payload"] = payload
|
|
557
|
+
return {"tx_hash": "0xabc"}
|
|
558
|
+
|
|
559
|
+
payload_dict = {
|
|
560
|
+
"x402Version": 2,
|
|
561
|
+
"accepted": {
|
|
562
|
+
"scheme": "exact",
|
|
563
|
+
"network": "eip155:8453",
|
|
564
|
+
"amount": "100000",
|
|
565
|
+
"asset": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
|
|
566
|
+
"payTo": "0x000000000000000000000000000000000000dEaD",
|
|
567
|
+
"maxTimeoutSeconds": 300,
|
|
568
|
+
},
|
|
569
|
+
"payload": {
|
|
570
|
+
"authorization": {
|
|
571
|
+
"from": "0xeb2Ca790F72787c7e61bC6c861353a1e4ACDFCa5",
|
|
572
|
+
"to": "0x000000000000000000000000000000000000dEaD",
|
|
573
|
+
"value": "100000",
|
|
574
|
+
"validAfter": 0,
|
|
575
|
+
"validBefore": 9999999999,
|
|
576
|
+
"nonce": "0x" + "aa" * 32,
|
|
577
|
+
},
|
|
578
|
+
"signature": "0x" + "cc" * 65,
|
|
579
|
+
},
|
|
580
|
+
}
|
|
581
|
+
res = await process_x402_settle(
|
|
582
|
+
ProcessX402SettleInput(
|
|
583
|
+
x402_server=_CapturingServer(),
|
|
584
|
+
payload=payload_dict,
|
|
585
|
+
resource_config={
|
|
586
|
+
"scheme": "exact",
|
|
587
|
+
"network": "eip155:8453",
|
|
588
|
+
"price": "$0.10",
|
|
589
|
+
"payTo": "0x" + "00" * 19 + "dE" + "aD",
|
|
590
|
+
"maxTimeoutSeconds": 300,
|
|
591
|
+
},
|
|
592
|
+
resource_meta=_RESOURCE_META,
|
|
593
|
+
)
|
|
594
|
+
)
|
|
595
|
+
assert isinstance(res, ProcessX402SettleSuccess)
|
|
596
|
+
# Both verify and settle legs received the typed Pydantic model, not the raw dict.
|
|
597
|
+
verify_payload = captured["verify_payload"]
|
|
598
|
+
settle_payload = captured["settle_payload"]
|
|
599
|
+
assert type(verify_payload).__name__ == "PaymentPayload"
|
|
600
|
+
assert type(settle_payload).__name__ == "PaymentPayload"
|
|
601
|
+
# The typed model exposes the get_scheme() method that x402's facilitator calls.
|
|
602
|
+
assert hasattr(verify_payload, "get_scheme")
|
|
603
|
+
assert verify_payload.get_scheme() == "exact"
|
|
604
|
+
|
|
605
|
+
|
|
606
|
+
@pytest.mark.asyncio
|
|
607
|
+
async def test_process_x402_settle_serializes_pydantic_settle_result_to_payment_response_header():
|
|
608
|
+
"""x402 2.9's ``settle_payment`` returns a Pydantic ``SettleResponse`` model;
|
|
609
|
+
plain ``json.dumps`` rejects it with ``TypeError``. The helper must call
|
|
610
|
+
``model_dump_json(by_alias=True)`` so the X-Payment-Response header stays
|
|
611
|
+
base64'd JSON with the right wire keys (``errorReason`` not ``error_reason``).
|
|
612
|
+
"""
|
|
613
|
+
from x402.schemas import SettleResponse
|
|
614
|
+
|
|
615
|
+
pydantic_settle = SettleResponse(
|
|
616
|
+
success=True,
|
|
617
|
+
transaction="0xabc",
|
|
618
|
+
network="eip155:8453",
|
|
619
|
+
payer="0x000000000000000000000000000000000000dEaD",
|
|
620
|
+
amount=None,
|
|
621
|
+
)
|
|
622
|
+
|
|
623
|
+
class _PydanticSettleServer:
|
|
624
|
+
def build_payment_requirements(self, _cfg: object, _ext: object = None) -> list:
|
|
625
|
+
return [{"id": "req1"}]
|
|
626
|
+
|
|
627
|
+
async def verify_payment(self, _payload: object, _req: object) -> dict:
|
|
628
|
+
return {"is_valid": True}
|
|
629
|
+
|
|
630
|
+
async def settle_payment(self, _payload: object, _req: object) -> SettleResponse:
|
|
631
|
+
return pydantic_settle
|
|
632
|
+
|
|
633
|
+
res = await process_x402_settle(
|
|
634
|
+
ProcessX402SettleInput(
|
|
635
|
+
x402_server=_PydanticSettleServer(),
|
|
636
|
+
payload={},
|
|
637
|
+
resource_config={
|
|
638
|
+
"scheme": "exact",
|
|
639
|
+
"network": "eip155:8453",
|
|
640
|
+
"price": "$0.10",
|
|
641
|
+
"payTo": "0x" + "00" * 19 + "dE" + "aD",
|
|
642
|
+
"maxTimeoutSeconds": 300,
|
|
643
|
+
},
|
|
644
|
+
resource_meta=_RESOURCE_META,
|
|
645
|
+
)
|
|
646
|
+
)
|
|
647
|
+
assert isinstance(res, ProcessX402SettleSuccess)
|
|
648
|
+
assert res.payment_response_header is not None
|
|
649
|
+
decoded = json.loads(base64.b64decode(res.payment_response_header).decode())
|
|
650
|
+
assert decoded["success"] is True
|
|
651
|
+
assert decoded["transaction"] == "0xabc"
|
|
652
|
+
assert decoded["network"] == "eip155:8453"
|
|
653
|
+
# by_alias=True: wire shape uses errorReason / errorMessage (camelCase) — not snake_case.
|
|
654
|
+
assert "errorReason" in decoded
|
|
655
|
+
|
|
656
|
+
|
|
657
|
+
@pytest.mark.asyncio
|
|
658
|
+
async def test_process_x402_settle_serializes_dict_settle_result_for_legacy_stubs():
|
|
659
|
+
"""Plain-dict settle results (from older x402 / test stubs) still serialize."""
|
|
660
|
+
|
|
661
|
+
class _DictSettleServer:
|
|
662
|
+
def build_payment_requirements(self, _cfg: object, _ext: object = None) -> list:
|
|
663
|
+
return [{"id": "req1"}]
|
|
664
|
+
|
|
665
|
+
async def verify_payment(self, _payload: object, _req: object) -> dict:
|
|
666
|
+
return {"is_valid": True}
|
|
667
|
+
|
|
668
|
+
async def settle_payment(self, _payload: object, _req: object) -> dict:
|
|
669
|
+
return {"success": True, "transaction": "0xdef"}
|
|
670
|
+
|
|
671
|
+
res = await process_x402_settle(
|
|
672
|
+
ProcessX402SettleInput(
|
|
673
|
+
x402_server=_DictSettleServer(),
|
|
674
|
+
payload={},
|
|
675
|
+
resource_config={
|
|
676
|
+
"scheme": "exact",
|
|
677
|
+
"network": "eip155:8453",
|
|
678
|
+
"price": "$0.10",
|
|
679
|
+
"payTo": "0x" + "00" * 19 + "dE" + "aD",
|
|
680
|
+
"maxTimeoutSeconds": 300,
|
|
681
|
+
},
|
|
682
|
+
resource_meta=_RESOURCE_META,
|
|
683
|
+
)
|
|
684
|
+
)
|
|
685
|
+
assert isinstance(res, ProcessX402SettleSuccess)
|
|
686
|
+
assert res.payment_response_header is not None
|
|
687
|
+
decoded = json.loads(base64.b64decode(res.payment_response_header).decode())
|
|
688
|
+
assert decoded == {"success": True, "transaction": "0xdef"}
|
|
689
|
+
|
|
690
|
+
|
|
691
|
+
@pytest.mark.asyncio
|
|
692
|
+
async def test_process_x402_settle_passes_typed_payment_payload_unchanged():
|
|
693
|
+
"""If the caller already provides a typed PaymentPayload, pass it through unchanged."""
|
|
694
|
+
from x402.schemas import PaymentPayload
|
|
695
|
+
|
|
696
|
+
typed = PaymentPayload.model_validate(
|
|
697
|
+
{
|
|
698
|
+
"x402Version": 2,
|
|
699
|
+
"accepted": {
|
|
700
|
+
"scheme": "exact",
|
|
701
|
+
"network": "eip155:8453",
|
|
702
|
+
"amount": "100000",
|
|
703
|
+
"asset": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
|
|
704
|
+
"payTo": "0x000000000000000000000000000000000000dEaD",
|
|
705
|
+
"maxTimeoutSeconds": 300,
|
|
706
|
+
},
|
|
707
|
+
"payload": {
|
|
708
|
+
"authorization": {
|
|
709
|
+
"from": "0xeb2Ca790F72787c7e61bC6c861353a1e4ACDFCa5",
|
|
710
|
+
"to": "0x000000000000000000000000000000000000dEaD",
|
|
711
|
+
"value": "100000",
|
|
712
|
+
"validAfter": 0,
|
|
713
|
+
"validBefore": 9999999999,
|
|
714
|
+
"nonce": "0x" + "aa" * 32,
|
|
715
|
+
},
|
|
716
|
+
"signature": "0x" + "cc" * 65,
|
|
717
|
+
},
|
|
718
|
+
}
|
|
719
|
+
)
|
|
720
|
+
captured: dict = {}
|
|
721
|
+
|
|
722
|
+
class _CapturingServer:
|
|
723
|
+
def build_payment_requirements(self, _cfg: object, _ext: object = None) -> list:
|
|
724
|
+
return [{"id": "req1"}]
|
|
725
|
+
|
|
726
|
+
async def verify_payment(self, payload: object, _req: object) -> dict:
|
|
727
|
+
captured["payload"] = payload
|
|
728
|
+
return {"is_valid": True}
|
|
729
|
+
|
|
730
|
+
async def settle_payment(self, _payload: object, _req: object) -> dict:
|
|
731
|
+
return {"tx_hash": "0xabc"}
|
|
732
|
+
|
|
733
|
+
res = await process_x402_settle(
|
|
734
|
+
ProcessX402SettleInput(
|
|
735
|
+
x402_server=_CapturingServer(),
|
|
736
|
+
payload=typed,
|
|
737
|
+
resource_config={
|
|
738
|
+
"scheme": "exact",
|
|
739
|
+
"network": "eip155:8453",
|
|
740
|
+
"price": "$0.10",
|
|
741
|
+
"payTo": "0x" + "00" * 19 + "dE" + "aD",
|
|
742
|
+
"maxTimeoutSeconds": 300,
|
|
743
|
+
},
|
|
744
|
+
resource_meta=_RESOURCE_META,
|
|
745
|
+
)
|
|
746
|
+
)
|
|
747
|
+
assert isinstance(res, ProcessX402SettleSuccess)
|
|
748
|
+
assert captured["payload"] is typed
|
|
749
|
+
|
|
750
|
+
|
|
537
751
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
538
752
|
# process_x402_settle: facilitator_error wrap
|
|
539
753
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -91,6 +91,56 @@ async def test_create_x402_server_coinbase_without_creds_raises(monkeypatch: pyt
|
|
|
91
91
|
await create_x402_server(facilitator="coinbase", rails=["x402-base-mainnet"], initialize=False)
|
|
92
92
|
|
|
93
93
|
|
|
94
|
+
@pytest.mark.skipif(not _X402_INSTALLED, reason="x402 peer dep not installed")
|
|
95
|
+
def test_build_x402_accepts_for_402_returns_dicts_from_typed_requirements() -> None:
|
|
96
|
+
"""``build_x402_accepts_for_402`` wraps ``server.build_payment_requirements`` and
|
|
97
|
+
returns the requirements as wire-shape dicts (via ``model_dump(by_alias=True, mode="json")``)
|
|
98
|
+
so merchants can drop them straight into the 402 response body without importing
|
|
99
|
+
Pydantic types or remembering to serialize.
|
|
100
|
+
"""
|
|
101
|
+
from x402.schemas import PaymentRequirements
|
|
102
|
+
|
|
103
|
+
from agentscore_commerce.payment import build_x402_accepts_for_402
|
|
104
|
+
|
|
105
|
+
captured: dict = {}
|
|
106
|
+
|
|
107
|
+
class _CapturingServer:
|
|
108
|
+
def build_payment_requirements(self, config, _ext=None):
|
|
109
|
+
captured["config"] = config
|
|
110
|
+
return [
|
|
111
|
+
PaymentRequirements(
|
|
112
|
+
scheme="exact",
|
|
113
|
+
network="eip155:8453",
|
|
114
|
+
amount="100000",
|
|
115
|
+
asset="0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
|
|
116
|
+
pay_to="0x000000000000000000000000000000000000dEaD",
|
|
117
|
+
max_timeout_seconds=300,
|
|
118
|
+
extra={"name": "USD Coin", "version": "2"},
|
|
119
|
+
)
|
|
120
|
+
]
|
|
121
|
+
|
|
122
|
+
accepts = build_x402_accepts_for_402(
|
|
123
|
+
_CapturingServer(),
|
|
124
|
+
network="eip155:8453",
|
|
125
|
+
price="$0.10",
|
|
126
|
+
pay_to="0x000000000000000000000000000000000000dEaD",
|
|
127
|
+
)
|
|
128
|
+
# Caller-side: a typed ResourceConfig is passed to build_payment_requirements.
|
|
129
|
+
cfg = captured["config"]
|
|
130
|
+
assert cfg.network == "eip155:8453"
|
|
131
|
+
assert cfg.pay_to == "0x000000000000000000000000000000000000dEaD"
|
|
132
|
+
assert cfg.max_timeout_seconds == 300
|
|
133
|
+
# Returned shape: wire-form dicts, not Pydantic models.
|
|
134
|
+
assert isinstance(accepts, list)
|
|
135
|
+
assert len(accepts) == 1
|
|
136
|
+
assert isinstance(accepts[0], dict)
|
|
137
|
+
assert accepts[0]["network"] == "eip155:8453"
|
|
138
|
+
# Camel-case keys (by_alias=True) — facilitator + clients expect this shape.
|
|
139
|
+
assert accepts[0]["payTo"] == "0x000000000000000000000000000000000000dEaD"
|
|
140
|
+
assert accepts[0]["maxTimeoutSeconds"] == 300
|
|
141
|
+
assert accepts[0]["extra"] == {"name": "USD Coin", "version": "2"}
|
|
142
|
+
|
|
143
|
+
|
|
94
144
|
@pytest.mark.skipif(not _MPPX_INSTALLED or not _TEMPO_INSTALLED, reason="pympp[tempo] not installed")
|
|
95
145
|
@pytest.mark.asyncio
|
|
96
146
|
async def test_create_mppx_server_tempo_returns_mpp_instance() -> None:
|
{agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/.github/ISSUE_TEMPLATE/bug_report.md
RENAMED
|
File without changes
|
{agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/.github/ISSUE_TEMPLATE/feature_request.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/agentscore_commerce/challenge/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/agentscore_commerce/challenge/body.py
RENAMED
|
File without changes
|
{agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/agentscore_commerce/challenge/how_to_pay.py
RENAMED
|
File without changes
|
{agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/agentscore_commerce/challenge/identity.py
RENAMED
|
File without changes
|
|
File without changes
|
{agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/agentscore_commerce/challenge/pricing.py
RENAMED
|
File without changes
|
{agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/agentscore_commerce/challenge/respond_402.py
RENAMED
|
File without changes
|
|
File without changes
|
{agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/agentscore_commerce/discovery/__init__.py
RENAMED
|
File without changes
|
{agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/agentscore_commerce/discovery/bazaar.py
RENAMED
|
File without changes
|
{agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/agentscore_commerce/discovery/llms_txt.py
RENAMED
|
File without changes
|
{agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/agentscore_commerce/discovery/openapi.py
RENAMED
|
File without changes
|
{agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/agentscore_commerce/discovery/robots_tag.py
RENAMED
|
File without changes
|
{agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/agentscore_commerce/discovery/skill_md.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/agentscore_commerce/identity/__init__.py
RENAMED
|
File without changes
|
{agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/agentscore_commerce/identity/_denial.py
RENAMED
|
File without changes
|
{agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/agentscore_commerce/identity/_response.py
RENAMED
|
File without changes
|
|
File without changes
|
{agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/agentscore_commerce/identity/address.py
RENAMED
|
File without changes
|
{agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/agentscore_commerce/identity/aiohttp.py
RENAMED
|
File without changes
|
{agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/agentscore_commerce/identity/cache.py
RENAMED
|
File without changes
|
{agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/agentscore_commerce/identity/client.py
RENAMED
|
File without changes
|
{agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/agentscore_commerce/identity/django.py
RENAMED
|
File without changes
|
{agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/agentscore_commerce/identity/fastapi.py
RENAMED
|
File without changes
|
{agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/agentscore_commerce/identity/flask.py
RENAMED
|
File without changes
|
{agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/agentscore_commerce/identity/middleware.py
RENAMED
|
File without changes
|
{agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/agentscore_commerce/identity/policy.py
RENAMED
|
File without changes
|
{agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/agentscore_commerce/identity/py.typed
RENAMED
|
File without changes
|
{agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/agentscore_commerce/identity/sanic.py
RENAMED
|
File without changes
|
{agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/agentscore_commerce/identity/sessions.py
RENAMED
|
File without changes
|
{agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/agentscore_commerce/identity/signer.py
RENAMED
|
File without changes
|
{agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/agentscore_commerce/identity/types.py
RENAMED
|
File without changes
|
|
File without changes
|
{agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/agentscore_commerce/payment/directive.py
RENAMED
|
File without changes
|
{agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/agentscore_commerce/payment/dispatch.py
RENAMED
|
File without changes
|
{agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/agentscore_commerce/payment/headers.py
RENAMED
|
File without changes
|
{agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/agentscore_commerce/payment/idempotency.py
RENAMED
|
File without changes
|
{agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/agentscore_commerce/payment/mppx_server.py
RENAMED
|
File without changes
|
{agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/agentscore_commerce/payment/networks.py
RENAMED
|
File without changes
|
{agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/agentscore_commerce/payment/rails.py
RENAMED
|
File without changes
|
|
File without changes
|
{agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/agentscore_commerce/payment/signer.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/examples/per_product_policy_merchant.py
RENAMED
|
File without changes
|
{agentscore_commerce-1.3.2 → agentscore_commerce-1.3.4}/examples/stripe_multichain_merchant.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|