Skip to content

Commit e3ddf68

Browse files
vvillait88claude
andauthored
feat(commerce 1.3)!: kill x402 Solana, add MPP solana/charge + classify+extra_warnings + x402scan discovery (#7)
## Summary Mirrors `agentscore/node-commerce#8` on the Python side. Ships as `1.3.0` (next minor after `1.2.0`). **1. Kill x402 Solana (BREAKING)** - Drop `x402-solana-*` rails. Add `mpp-solana-*` rails (method=`solana`). - `validate_x402_network_config` now base-only (`base_network: str`, was `{base, svm}` object). - `verify_x402_request` takes single `accepted_network: str`. - Rename `X402SolanaConfig` → `SolanaMppConfig`, `X402SolanaRailConfig` → `SolanaMppRailConfig`. - pyproject `x402[evm,fastapi]>=2.8,<3` (no `svm` extra). - **Python parity gap (intentional):** there is no Python equivalent of `@solana/mpp`, so `create_mppx_server` does not ship a Solana rail. Python merchants on Solana implement MPP `solana/charge` server-side themselves. **2. New helpers** - `classify_x402_settle_result` collapses `process_x402_settle` failure phases (`verify_failed` / `settle_failed` / `facilitator_error` / `no_requirements`) to `(status, code, message, next_steps)`. Mapping is identical to the node side: 400 / 503 / 503 / 500. - `extra_warnings` field on `BuildAgentInstructionsInput`; append per-order warnings on top of defaults. - Solana-network rejection branch in `verify_x402_request`: clients presenting an x402 credential on a `solana:*` network get a behavior-only hint pointing at the `solana/charge` rail. - `process_x402_settle` wraps facilitator throws as `phase='facilitator_error'` across all steps. **3. x402scan discovery (Layer 1)** - `build_well_known_x402` builder. - OpenAPI extension helpers (`siwx_security_scheme`, `x_payment_info_extension`, `x_guidance_extension`). **4. Internal-disclosure cleanup** - Replaced threat-model rationale in source docstrings with behavior-only language (`stripe_multichain/pi_cache.py`, `identity/types.py`, `identity/signer.py`). - Genericized Martin Estate / martinestate.com / martin-estate-wine-commerce / "wine-purchase" baked into examples + tests + docstrings. - Removed `pending_identity` internal state name from agent-visible warnings. - Removed `x402[evm,svm]` references from CLAUDE.md (the upstream extra is gone; we depend on `x402[evm]`). ## Test plan - [x] 715 pytest tests passing (3 skipped) - [x] ruff + ty all green - [x] CodeQL + dependency + python audit scans green ## Downstream impact Once this lands and `agentscore-commerce==1.3.0` publishes: - `agentscore/core#230` will pass CI (currently red on `agentscore-commerce[mppx,x402]>=1.3.0` resolution). - The store route's `verify_x402_request` + `process_x402_settle` + `classify_x402_settle_result` wiring depends on this SDK release. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 0ef09bd commit e3ddf68

39 files changed

Lines changed: 822 additions & 435 deletions

CLAUDE.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ Every helper is extracted from a real consumer, not speculated.
99
| Submodule | What it is |
1010
|---|---|
1111
| `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 official `x402[evm,svm]>=2.8` peer dep with v1+v2 dual-register + bazaar extension), `create_mppx_server` (wraps `pympp[server,tempo,stripe]>=0.6` peer dep with Tempo charge/session + Stripe SPT helpers), dispatch-by-network, signer extraction, WWW-Authenticate header, Settlement-Overrides header |
12+
| `agentscore_commerce.payment` | Networks/USDC/rails registries, paymentauth.org directive builders, `create_x402_server` (wraps official `x402[evm]>=2.8` peer dep with v1+v2 dual-register + bazaar extension), `create_mppx_server` (wraps `pympp[server,tempo,stripe]>=0.6` peer dep with Tempo charge/session + Stripe SPT helpers), dispatch-by-network, signer extraction, WWW-Authenticate header, Settlement-Overrides header |
1313
| `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 |
1414
| `agentscore_commerce.challenge` | 402-body builders: accepted_methods, identity_metadata, how_to_pay, agent_instructions, build_402_body, `build_validation_error` (4xx body builder) |
1515
| `agentscore_commerce.stripe_multichain` | Multichain PaymentIntent helper, deposit-address lookup, testnet simulator, mppx Stripe wrapper |
@@ -30,15 +30,15 @@ Single Python package, hatchling-built, published to PyPI as `agentscore-commerc
3030
| `examples/` | Runnable single-file FastAPI apps for each common scenario |
3131
| `tests/` | pytest, one file per surface |
3232

33-
Peer-dep pattern: payment/x402/mppx/stripe modules import lazily at runtime — vendors install only what they use via extras (`pip install agentscore-commerce[fastapi,stripe]` etc.). Underlying packages: `x402[evm,svm]`, `pympp[server,tempo,stripe]`, `stripe`. Missing peer dep raises a guiding `ImportError` with the install command.
33+
Peer-dep pattern: payment/x402/mppx/stripe modules import lazily at runtime — vendors install only what they use via extras (`pip install agentscore-commerce[fastapi,stripe]` etc.). Underlying packages: `x402[evm]`, `pympp[server,tempo,stripe]`, `stripe`. Missing peer dep raises a guiding `ImportError` with the install command.
3434

3535
## Examples
3636

3737
`examples/` contains full single-file FastAPI apps for the most common merchant scenarios — copy-paste templates, not frameworks:
3838

3939
| Example | Scenario |
4040
|---|---|
41-
| `api_provider.py` | Per-call API billing on multiple rails: Tempo MPP + x402 (Base + Solana); no compliance gate |
41+
| `api_provider.py` | Per-call API billing on multiple rails: Tempo MPP + x402 Base + Solana MPP; no compliance gate |
4242
| `identity_only.py` | Compliance gate without payment (vendor handles their own) |
4343
| `multi_rail_merchant.py` | Full agent-commerce: identity + Tempo MPP + x402 + Stripe SPT |
4444
| `stripe_multichain_merchant.py` | Stripe-anchored multichain (PaymentIntent → tempo/base/solana deposit addresses) |

README.md

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ pip install agentscore-commerce[fastapi] # or [flask], [django], [aiohttp], [s
1717
|---|---|
1818
| `agentscore_commerce.identity.{fastapi,flask,django,aiohttp,sanic,middleware}` | Trust gate middleware: KYC, sanctions, age, jurisdiction. `AgentScoreGate(...)` (or `agentscore_gate(app, ...)` on Flask/Sanic), `get_assess_data(...)`, `capture_wallet(...)`, `verify_wallet_signer_match(...)`. |
1919
| `agentscore_commerce.identity` (package level) | Re-exports the denial helpers: `denial_reason_status`, `denial_reason_to_body`, `build_signer_mismatch_body`, `build_contact_support_next_steps`, `verification_agent_instructions`, `is_fixable_denial`, `FIXABLE_DENIAL_REASONS`. Also re-exports the per-product policy helpers: `PolicyBlock`, `GateResult`, `EnforcementMode`, `IdentityStatus`, `build_gate_from_policy`, `run_gate_with_enforcement`, `shipping_country_allowed`, `shipping_state_allowed` — for multi-product merchants where each product carries its own compliance config (hard gate vs soft vs none, per-product shipping allowlists). |
20-
| `agentscore_commerce.payment` | `networks`, `USDC`, `rails` registries; `payment_directive`, `build_payment_directive`, `www_authenticate_header`, `payment_required_header`, `alias_amount_fields` (v1↔v2 amount field shim — emits both `amount` and `maxAmountRequired` so v1-only x402 parsers like Coinbase awal can read v2 bodies), `settlement_override_header`, `dispatch_settlement_by_network`, `extract_payment_signer` (returns `PaymentSigner({address, network})`), `register_x402_schemes_v1_v2`; drop-in x402 helpers: `validate_x402_network_config` (boot-time guard), `verify_x402_request` (parse + validate inbound X-Payment), `process_x402_settle` (verify-then-settle with one call). |
20+
| `agentscore_commerce.payment` | `networks`, `USDC`, `rails` registries; `payment_directive`, `build_payment_directive`, `www_authenticate_header`, `payment_required_header`, `alias_amount_fields` (v1↔v2 amount field shim — emits both `amount` and `maxAmountRequired` so v1-only x402 parsers like Coinbase awal can read v2 bodies), `settlement_override_header`, `dispatch_settlement_by_network`, `extract_payment_signer` (returns `PaymentSigner({address, network})`), `register_x402_schemes_v1_v2`; drop-in x402 helpers: `validate_x402_network_config` (boot-time guard), `verify_x402_request` (parse + validate inbound X-Payment), `process_x402_settle` (verify-then-settle with one call), `classify_x402_settle_result` (maps the tagged settle result to a recommended HTTP status / code / next_steps so merchants get a controlled envelope without coupling to facilitator-specific error text). |
2121
| `agentscore_commerce.discovery` | `is_discovery_probe_request`, `build_discovery_probe_response` (with optional `x402_sample` for x402-aware crawlers — `awal x402 details` etc.), `sample_x402_accept_for_network` (USDC sample-accept builder for known CAIP-2 networks), `build_well_known_mpp`, `build_llms_txt` + `llms_txt_identity_section` + `llms_txt_payment_section` (compact + verbose modes), `build_skill_md` (Claude-Skill-compatible `/skill.md` agent-discovery manifest — strictly agent-facing data only, no internal posture), `agentscore_openapi_snippets`, `build_bazaar_discovery_payload`, `NoindexNonDiscoveryMiddleware` (ASGI middleware that emits `X-Robots-Tag: noindex` on every path except the agent-discovery surfaces — defaults cover `/openapi.json`, `/llms.txt`, `/skill.md`, `/.well-known/{mpp.json,agent-card.json,ucp}`, `/favicon.{png,ico}`; pure helpers `is_discovery_path` + `DEFAULT_DISCOVERY_PATHS` for non-ASGI frameworks). |
2222
| `agentscore_commerce.challenge` | `build_402_body`, `build_accepted_methods`, `build_identity_metadata`, `build_how_to_pay`, `build_agent_instructions` (auto-emits per-rail `compatible_clients` — smoke-verified CLIs the agent should use; vendor override supported), `build_pricing_block` (cents → dollar-string with optional shipping/tax), `first_encounter_agent_memory` (cross-merchant hint, returns the canonical block or `None` based on a per-merchant first-seen flag), `OrderReceipt` (dataclass for the post-settlement 200 response shape); `respond_402` — drop-in 402 emit that preserves pympp's `WWW-Authenticate` and layers x402's `PAYMENT-REQUIRED`. `build_validation_error` — structured 4xx body builder (`{error: {code, message}, required_fields?, example_body?, next_steps?, ...extra}`) so vendors compose body shapes by name instead of inlining at every validation site. |
2323
| `agentscore_commerce.stripe_multichain` | `create_multichain_payment_intent`, `get_deposit_address`, `simulate_crypto_deposit`; `create_pi_cache` (TTL'd PI / deposit-address cache, Redis-backed when `redis_url` set, in-memory otherwise), `simulate_deposit_if_test_mode` (gates on `sk_test_` and looks up the PI for you), `STRIPE_TEST_TX_HASH_SUCCESS` / `STRIPE_TEST_TX_HASH_FAILED` constants. Peer dep on `stripe`. |
@@ -247,15 +247,14 @@ from agentscore_commerce.payment import (
247247
ProcessX402SettleInput,
248248
ValidateX402NetworkConfigInput,
249249
VerifyX402RequestInput,
250+
classify_x402_settle_result,
250251
process_x402_settle,
251252
validate_x402_network_config,
252253
verify_x402_request,
253254
)
254255

255256
# Boot-time guard — raises if a configured network isn't supported.
256-
validate_x402_network_config(
257-
ValidateX402NetworkConfigInput(base_network=X402_BASE, svm_network=X402_SVM)
258-
)
257+
validate_x402_network_config(ValidateX402NetworkConfigInput(base_network=X402_BASE))
259258

260259
@app.post("/purchase")
261260
async def purchase(request: Request):
@@ -264,8 +263,7 @@ async def purchase(request: Request):
264263
verified = await verify_x402_request(VerifyX402RequestInput(
265264
headers=dict(request.headers),
266265
is_cached_address=pi_cache.has_address,
267-
accepted_base_network=X402_BASE,
268-
accepted_svm_network=X402_SVM,
266+
accepted_network=X402_BASE,
269267
))
270268
if not verified.ok:
271269
return JSONResponse(verified.body, status_code=verified.status)
@@ -276,8 +274,14 @@ async def purchase(request: Request):
276274
resource_config={"scheme": "exact", "network": verified.signed_network, "price": f"${total}", "payTo": verified.signed_pay_to, "maxTimeoutSeconds": 300},
277275
resource_meta={"url": str(request.url), "mimeType": "application/json"},
278276
))
279-
if not settle.success:
280-
return JSONResponse({"error": {"code": "payment_proof_invalid", "phase": settle.phase}}, status_code=400)
277+
classified = classify_x402_settle_result(settle)
278+
if classified is not None:
279+
# Log raw `settle` server-side; return controlled phase-based response to the agent.
280+
logger.error("x402-settle failed phase=%s raw=%r", settle.phase, settle)
281+
return JSONResponse(
282+
{"error": {"code": classified.code, "message": classified.message}, "next_steps": classified.next_steps},
283+
status_code=classified.status,
284+
)
281285

282286
headers = {"payment-response": settle.payment_response_header} if settle.payment_response_header else {}
283287
return JSONResponse({"ok": True}, headers=headers)

agentscore_commerce/challenge/__init__.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@
22

33
from agentscore_commerce.challenge.accepted_methods import (
44
BuildAcceptedMethodsInput,
5+
SolanaMppConfig,
56
StripeConfig,
67
TempoConfig,
78
X402BaseConfig,
8-
X402SolanaConfig,
99
build_accepted_methods,
1010
)
1111
from agentscore_commerce.challenge.agent_instructions import (
@@ -21,10 +21,10 @@
2121
from agentscore_commerce.challenge.how_to_pay import (
2222
BuildHowToPayInput,
2323
HowToPayRails,
24+
SolanaMppRailConfig,
2425
StripeRailConfig,
2526
TempoRailConfig,
2627
X402BaseRailConfig,
27-
X402SolanaRailConfig,
2828
build_how_to_pay,
2929
)
3030
from agentscore_commerce.challenge.identity import (
@@ -64,15 +64,15 @@
6464
"Respond402Result",
6565
"ShippingAddress",
6666
"SignerMatchResult",
67+
"SolanaMppConfig",
68+
"SolanaMppRailConfig",
6769
"StripeConfig",
6870
"StripeRailConfig",
6971
"TempoConfig",
7072
"TempoRailConfig",
7173
"X402BaseConfig",
7274
"X402BaseRailConfig",
7375
"X402PaymentRequired",
74-
"X402SolanaConfig",
75-
"X402SolanaRailConfig",
7676
"build_402_body",
7777
"build_accepted_methods",
7878
"build_agent_instructions",

agentscore_commerce/challenge/accepted_methods.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ class X402BaseConfig:
2525

2626

2727
@dataclass
28-
class X402SolanaConfig:
28+
class SolanaMppConfig:
2929
recipient: str
3030
network: str = "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp"
3131
token: str = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"
@@ -43,7 +43,7 @@ class StripeConfig:
4343
class BuildAcceptedMethodsInput:
4444
tempo: TempoConfig | None = None
4545
x402_base: X402BaseConfig | None = None
46-
x402_solana: X402SolanaConfig | None = None
46+
solana_mpp: SolanaMppConfig | None = None
4747
stripe: StripeConfig | None = None
4848

4949

@@ -74,15 +74,15 @@ def build_accepted_methods(input: BuildAcceptedMethodsInput) -> list[dict[str, A
7474
"pay_to": input.x402_base.recipient,
7575
}
7676
)
77-
if input.x402_solana:
77+
if input.solana_mpp:
7878
out.append(
7979
{
8080
"method": "x402/exact",
81-
"network": input.x402_solana.network,
82-
"token": input.x402_solana.token,
83-
"symbol": input.x402_solana.symbol,
84-
"decimals": input.x402_solana.decimals,
85-
"pay_to": input.x402_solana.recipient,
81+
"network": input.solana_mpp.network,
82+
"token": input.solana_mpp.token,
83+
"symbol": input.solana_mpp.symbol,
84+
"decimals": input.solana_mpp.decimals,
85+
"pay_to": input.solana_mpp.recipient,
8686
}
8787
)
8888
if input.stripe:

agentscore_commerce/challenge/agent_instructions.py

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,17 @@
66

77
_TEMPO_WARNING = (
88
"Do NOT use `tempo wallet transfer` to pay to the address above. That moves USDC on-chain but does not "
9-
"notify this server, leaving your order in pending_identity state. Use `tempo request` insteadit performs "
10-
"the full MPP handshake (signs, submits Authorization: Payment, waits for server confirmation)."
9+
"notify this server, so the order will not complete. Use `tempo request` instead; it performs the full MPP "
10+
"handshake (signs, submits Authorization: Payment, waits for server confirmation)."
1111
)
1212
_X402_WARNING = (
1313
"Do NOT send USDC manually to the x402 deposit addresses (e.g. via a bare wallet `transfer`). Use "
14-
"`agentscore-pay pay` so the X-Payment credential is signed and submitted; otherwise the order stays in "
15-
"pending_identity even though the deposit lands."
14+
"`agentscore-pay pay` so the X-Payment credential is signed and submitted; otherwise the order will not "
15+
"complete even though the deposit lands."
1616
)
1717
_TEMPO_TOOL = "`tempo request` for Tempo USDC (installs via `tempo add request`)"
1818
_AGENTSCORE_PAY_TOOL = (
19-
"`agentscore-pay` (npm: `@agent-score/pay`) — single CLI for x402 on Base + Solana, "
20-
"also speaks tempo MPP via `--chain tempo`"
19+
"`agentscore-pay` (npm: `@agent-score/pay`); single CLI for x402 on Base and MPP on Tempo + Solana"
2120
)
2221

2322
DEFAULT_WALLET_COMPATIBILITY = (
@@ -30,7 +29,7 @@
3029
def _default_recommended_tools(how_to_pay: dict[str, Any]) -> list[str]:
3130
tools: list[str] = []
3231
has_tempo = "tempo" in how_to_pay
33-
has_x402 = "x402_base" in how_to_pay or "x402_solana" in how_to_pay
32+
has_x402 = "x402_base" in how_to_pay or "solana_mpp" in how_to_pay
3433
if has_tempo:
3534
tools.append(_TEMPO_TOOL)
3635
if has_tempo or has_x402:
@@ -42,17 +41,17 @@ def _default_warnings(how_to_pay: dict[str, Any]) -> list[str]:
4241
w: list[str] = []
4342
if "tempo" in how_to_pay:
4443
w.append(_TEMPO_WARNING)
45-
if "x402_base" in how_to_pay or "x402_solana" in how_to_pay:
44+
if "x402_base" in how_to_pay or "solana_mpp" in how_to_pay:
4645
w.append(_X402_WARNING)
4746
return w
4847

4948

50-
RailKey = Literal["tempo_mpp", "x402_base", "x402_solana", "stripe"]
49+
RailKey = Literal["tempo_mpp", "x402_base", "solana_mpp", "stripe"]
5150

5251
_RAIL_CLIENTS: dict[str, list[str]] = {
5352
"tempo_mpp": ["agentscore-pay", "tempo request", "x402-proxy"],
5453
"x402_base": ["agentscore-pay", "x402-proxy", "purl (omit --network flag)"],
55-
"x402_solana": ["agentscore-pay"],
54+
"solana_mpp": ["agentscore-pay"],
5655
"stripe": ["link-cli"],
5756
}
5857

@@ -84,8 +83,8 @@ def _default_compatible_clients(how_to_pay: dict[str, Any]) -> dict[str, list[st
8483
rails.append("tempo_mpp")
8584
if "x402_base" in how_to_pay:
8685
rails.append("x402_base")
87-
if "x402_solana" in how_to_pay:
88-
rails.append("x402_solana")
86+
if "solana_mpp" in how_to_pay:
87+
rails.append("solana_mpp")
8988
if "stripe" in how_to_pay:
9089
rails.append("stripe")
9190
return compatible_clients_by_rails(rails)
@@ -98,6 +97,9 @@ class BuildAgentInstructionsInput:
9897
wallet_compatibility: str | None = None
9998
timeout_seconds: int = 300
10099
warnings: list[str] | None = None
100+
# Appended to the default protocol-footgun warnings. Use this to keep the SDK's
101+
# protocol warnings AND add merchant-specific notes. Ignored when ``warnings`` is set.
102+
extra_warnings: list[str] | None = None
101103
recommended: str | None = None
102104
# Per-rail list of client names the merchant has verified work end-to-end.
103105
# Vendors set this from their own smoke matrix — defaults to None, in which case
@@ -112,12 +114,16 @@ def build_agent_instructions(input: BuildAgentInstructionsInput) -> dict[str, An
112114
113115
Defaults adapt to the rails declared in ``how_to_pay``: only tempo-relevant warnings/tools
114116
appear if ``how_to_pay["tempo"]`` is set, only x402-relevant ones if ``x402_base``/
115-
``x402_solana`` are set. Vendors override ``warnings``/``recommended_tools`` for full control.
117+
``solana_mpp`` are set. Vendors override ``warnings``/``recommended_tools`` for full control.
116118
"""
117119
recommended_tools = (
118120
input.recommended_tools if input.recommended_tools is not None else _default_recommended_tools(input.how_to_pay)
119121
)
120-
warnings = input.warnings if input.warnings is not None else _default_warnings(input.how_to_pay)
122+
warnings = (
123+
input.warnings
124+
if input.warnings is not None
125+
else [*_default_warnings(input.how_to_pay), *(input.extra_warnings or [])]
126+
)
121127
compatible_clients = (
122128
input.compatible_clients
123129
if input.compatible_clients is not None

agentscore_commerce/challenge/how_to_pay.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ class X402BaseRailConfig:
2020

2121

2222
@dataclass
23-
class X402SolanaRailConfig:
23+
class SolanaMppRailConfig:
2424
recipient: str
2525
network: str = "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp"
2626

@@ -35,7 +35,7 @@ class StripeRailConfig:
3535
class HowToPayRails:
3636
tempo: TempoRailConfig | None = None
3737
x402_base: X402BaseRailConfig | None = None
38-
x402_solana: X402SolanaRailConfig | None = None
38+
solana_mpp: SolanaMppRailConfig | None = None
3939
stripe: StripeRailConfig | None = None
4040

4141

@@ -125,9 +125,9 @@ def build_how_to_pay(input: BuildHowToPayInput) -> dict[str, Any]:
125125
),
126126
}
127127

128-
if input.rails.x402_solana:
129-
s = input.rails.x402_solana
130-
block["x402_solana"] = {
128+
if input.rails.solana_mpp:
129+
s = input.rails.solana_mpp
130+
block["solana_mpp"] = {
131131
"setup": PAY_SETUP_SOLANA,
132132
"prerequisite": (
133133
f"Run `agentscore-pay balance --chain solana` and confirm USDC balance on Solana ({s.network}) "

agentscore_commerce/challenge/order_receipt.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
"""Canonical order-receipt shape returned to agents on the 200 after settlement.
22
3-
Merchants own their order schema, but converging on this shape across every AgentScore-gated
4-
merchant (Martin Estate today; Commerce7 / WooCommerce / Shopify plugins tomorrow) means
5-
agents can render and post-process orders consistently. Lift this type, fill the fields you
6-
care about, and ignore (or extend via ``extras``) what you don't.
3+
Merchants own their order schema, but converging on this shape across AgentScore-gated
4+
merchants means agents can render and post-process orders consistently. Lift this type,
5+
fill the fields you care about, and ignore (or extend via ``extras``) what you don't.
76
87
All money fields are dollar-strings. Use :func:`build_pricing_block` from
98
:mod:`agentscore_commerce.challenge` to compose the pricing fields from cents.

0 commit comments

Comments
 (0)