Skip to content

feat: commerce 2.0.0 — Checkout orchestrator + per-framework adapters + signed UCP routes - #49

Merged
vvillait88 merged 24 commits into
mainfrom
feat/sdk-lifts-tier-1-2
May 15, 2026
Merged

feat: commerce 2.0.0 — Checkout orchestrator + per-framework adapters + signed UCP routes#49
vvillait88 merged 24 commits into
mainfrom
feat/sdk-lifts-tier-1-2

Conversation

@vvillait88

Copy link
Copy Markdown
Contributor

Summary

The 2.0.0 release of `agentscore-commerce`. Adds the `Checkout` orchestrator (one config object + hooks), 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, and a canonical `Receipt` dataclass universal across goods + API merchants.

Wave-1 lifts: `pricing_result`, `validate_shipping_against_policy`, `DiscoveryProbeConfig`, `signed_response_`, `Checkout.mount_ucp_routes_`.

Wave-2: `OrderReceipt` → `Receipt` rename, `standard_endpoint_descriptions(kind='goods'|'api')`, `build_redemption_skill_md` universal prose, `build_identity_metadata` auto-attach in `Checkout._emit_402`.

Recent additions on this branch:

  • `pricing_result` accepts `discount_cents`; `PricingBlock` surfaces `discount` as a dollar-string. Subtotal stays at list price, total = subtotal + tax + shipping - discount (floored at 0).
  • Dep refresh: `agentscore-py` 2.3.1 → 2.3.2 (transitive via uv.lock).

Test plan

  • `uv run ruff check .` (clean)
  • `uv run ty check agentscore_commerce` (clean)
  • `uv run pytest -q` (1195 passed, 4 skipped)
  • Store smoke (155 tests pass at 94% coverage)
  • CI green on PR

🤖 Generated with Claude Code

vvillait88 and others added 13 commits May 15, 2026 07:09
…1 lift B)

Collapses the universal goods-merchant boilerplate

    if not shipping_country_allowed(country, policy):
        raise CheckoutValidationError(code='unsupported_jurisdiction', ...)
    if not shipping_state_allowed(state, country, policy):
        raise CheckoutValidationError(code='unsupported_jurisdiction', ...)

into one helper call. Used by per_product_policy_merchant example +
core/store/purchase.py + martin-estate (each currently inlines the same
2-check pattern in preValidate).

Default messages are neutral (don't assume regulatory reason - could be
operational / commercial). country_message / state_message override verbatim.
error_code / error_action override the canonical denial codes.

Tests: 7 new cases (null policy, no-allowlist, country deny, state deny,
product_name in message, custom messages, custom code/action). Suite green
at 95.13 percent coverage.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Collapses the universal US-commerce boilerplate

    block = build_pricing_block(subtotal_cents=..., tax_cents=..., ...)
    return PricingResult(amount_usd=total_cents/100, block=block, ...)

into one helper call. Used by core/store, martin-estate, and the
multi_rail_merchant example (each currently inlines the same dance in
compute_pricing).

- subtotal_cents + tax_cents + shipping_cents derive amount_usd.
- tax_rate / tax_state attach to the block for the 402 body.
- Passthrough mode (amount_usd only) for API merchants with no tax.
- Explicit amount_usd overrides the cents-derived value when both passed.

Tests: 7 cases. pricing_result is also re-exported at the top level.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ft D)

When a Checkout is constructed with discovery_probe=DiscoveryProbeConfig(...),
any empty-body POST without a payment header short-circuits with a sample 402
advertising the merchant's payment shape. Saves vendors the
is_discovery_probe_request + build_discovery_probe_response dance every
crawler-friendly merchant repeats.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 wrappers (fastapi, flask, django, aiohttp, sanic) that convert the
framework-neutral SignedDiscoveryResponse / WellKnownPreflightResponse into
the framework's native Response. Saves merchants the 4-line
Response(content=..., media_type=..., headers=..., status_code=...) shim
they otherwise hand-roll on every /.well-known/{ucp,jwks.json} route.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds mount_ucp_routes_{fastapi,flask,django,aiohttp,sanic} on the Checkout
class. Each registers GET /.well-known/ucp + GET /.well-known/jwks.json + an
OPTIONS preflight for both, in one call. Saves merchants the ~40-line 3-route
registration block every UCP-publishing merchant otherwise hand-rolls.

FastAPI handlers patch __annotations__ post-hoc so the Request injection
survives `from __future__ import annotations` stringification.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- api_provider.py: Checkout(discovery_probe=...) replaces inline probe routing
- per_product_policy_merchant.py: validate_shipping_against_policy replaces shipping_country_allowed + shipping_state_allowed pair
- multi_rail_merchant.py: pricing_result() replaces build_pricing_block + body_extras dance
- signed_ucp_merchant.py: Checkout.mount_ucp_routes_fastapi(...) replaces 3-route hand-mount

Net: ~80 lines removed across examples; the underlying helpers carry the
boilerplate now.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… universal copy

- Kill handle_starlette = handle_fastapi alias (FastAPI inherits from Starlette;
  one adapter already covers both).
- agent_instructions warnings + llms_txt: "order will not complete" →
  "request will not complete" (universal across goods + API merchants).
- build_success_next_steps default: "Order complete." → "Payment complete."
- build_contact_support_next_steps default: "with your order details" →
  "with the details of your request."

No behavioural change. All helpers continue to accept caller overrides; only
defaults changed so API-merchant consumers stop having to override every call.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Five-change wave focused on universalizing surface for both goods + API
merchants and on auto-attaching body-piece helpers from the orchestrator.

1. Rename order_receipt.py → receipt.py; OrderReceipt → Receipt;
   OrderProductInfo → ProductInfo; OrderNextSteps → ReceiptNextSteps. Goods-only
   slots (shipping, tracking_number, fulfillment_status, gift_note) stay
   optional; docstrings explicitly tag goods-only vs universal fields. Zero
   consumers used the old names so no consumer migration needed.

2. standard_endpoint_descriptions takes a kind: Literal["goods", "api"]
   parameter; ships separate canonical bundles. Goods bundle stays current
   (/catalog, /purchase, /orders/{id}); API bundle is /<endpoint> + /usage.

3. build_redemption_skill_md prose rewritten to be delivery-neutral (printed
   mailers, emailed codes, in-app, API trial credits all covered). Adds
   endpoint_path, delivery_intro, body_shape, body_rules, extra_recovery_rows
   params so API merchants can pass non-goods shapes without rewriting the
   whole template.

4. Wire build_identity_metadata into Checkout._emit_402 via
   _resolve_identity_metadata: when X-Wallet-Address header is present, the
   402 body now advertises identity_mode/required_signer/signer_constraint
   (and linked_wallets when the gate populated request.assess). Agents
   self-correct at discovery instead of at the 403 retry.

5. (No-op) Keep createMppxStripe rewrap as-is per design discussion.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds two routes to the API-merchant example so the new wave-2 surface is
visible end-to-end:

- GET /: discovery root using build_merchant_index_json +
  standard_endpoint_descriptions(kind="api"). Lists the per-call endpoint,
  supported rails, pricing, and a pointer to /redemption.md.
- GET /redemption.md: build_redemption_skill_md with API-trial-credit body
  shape (query + redemption_code), delivery_intro framed for developer
  onboarding emails, body_rules dropped (no shipping for API merchants).

Demonstrates how the same single-use code pattern that powers martin's
printed mailers also covers API trial credits, promo codes, and any other
out-of-band code distribution channel.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…_rail

Two changes:

1. Cross-language parity fix: kill get_deposit_address (function +
   __init__ export + tests + example call site). Wave 1 killed the node
   equivalent but missed the python copy. Same rationale: trivial
   `result.deposit_addresses.get(network)` dict lookup; no value-add as
   a separate helper.

2. Demonstrate Receipt + ReceiptNextSteps + build_success_next_steps in
   multi_rail_merchant._on_settled so the renamed canonical type has a
   visible consumer. Also added `action` field to ReceiptNextSteps so
   build_success_next_steps output spreads cleanly into the dataclass
   (the helper emits {action: "done", user_message, order_status_url}
   and ReceiptNextSteps now matches that shape verbatim).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three doc files refreshed to match the wave-1 + wave-2 SDK changes (parity
with the node-commerce doc refresh):

- README.md: add Checkout orchestrator quickstart (the 2.0 high-level
  surface that was missing); refresh helper tables (top-level adds Checkout
  + pricing_result + validation_response_* + Receipt types; identity adds
  validate_shipping_against_policy; discovery adds build_signed_ucp_response,
  build_signed_jwks_response, signed_response_*, build_merchant_index_json,
  standard_endpoint_descriptions, build_success_next_steps,
  build_agentscore_onboarding_steps, build_redemption_skill_md,
  well_known_preflight_response, default_a2a_services,
  bootstrap_ucp_signing_key; challenge: Receipt rename + identity_metadata
  auto-attach note; stripe_multichain: drop get_deposit_address); fix stale
  stripe code example to read deposit_addresses[network] directly;
  OrderReceipt → Receipt prose update.

- CLAUDE.md: top-level row added covering Checkout + pricing_result +
  validation_response_* + Receipt types; same helper-table refresh as README;
  identity.policy row added with validate_shipping_against_policy.

- examples/README.md: per-example "What it shows" descriptions updated to
  reflect the helpers each one was migrated to (Checkout, discovery_probe,
  build_merchant_index_json, standard_endpoint_descriptions(kind="api"),
  build_redemption_skill_md, pricing_result, Receipt +
  build_success_next_steps, mount_ucp_routes_fastapi,
  validate_shipping_against_policy). Add signed_ucp_merchant.py row.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…e __init__

Wave-1 added validate_shipping_against_policy to identity.policy module but
the wave-2 audit caught that identity/__init__.py never re-exported it.
Means \`from agentscore_commerce.identity import validate_shipping_against_policy\`
fails at runtime even though the helper exists.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
PricingBlock + build_pricing_block + pricing_result now accept discount_cents.
When supplied, subtotal stays at list price, discount surfaces as a dollar-
string, total = subtotal + tax + shipping - discount (floored at 0). Agents
reading 402 challenges see the savings line instead of subtotal=0.

Bumps (transitive via uv.lock):
- agentscore-py 2.3.1 -> 2.3.2

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Comment thread tests/test_seamless_helpers.py Fixed
vvillait88 and others added 3 commits May 15, 2026 10:42
asyncio is already module-level imported (line 16); the inner import in
test_mount_ucp_routes_aiohttp_registers_three_routes shadowed it for no
reason. Caught by github-code-quality bot on PR #49.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
multi-rail/stripe-multichain/variable-cost

- identity_only.py: add create_session_on_missing + capture_wallet + public
  route to match node's teaching depth.
- multi_rail_merchant.py: replace _create_multichain_payment_intent stub with
  the real create_multichain_payment_intent helper + pi_cache writes.
- stripe_multichain_merchant.py: rename POST /buy -> POST /checkout, add the
  3-network instructions block (matches node prose).
- variable_cost_merchant.py: swap raw 402 dicts for build_402_body +
  build_accepted_methods + build_agent_instructions + build_how_to_pay +
  build_pricing_block; wire create_x402_server/create_mppx_server hooks.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Comment thread examples/variable_cost_merchant.py Fixed
Replace the linter-placation assignment with an explicit __all__ so
_boot_x402_server + create_mppx_server are documented as reference imports
for vendors wiring real servers. Caught by github-code-quality bot.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Comment thread examples/variable_cost_merchant.py Fixed
Comment thread examples/variable_cost_merchant.py Fixed
vvillait88 and others added 7 commits May 15, 2026 11:09
Brings python's package-root surface in line with node's index.ts. Vendors
who switch SDKs now find the same A2A/UCP types at agentscore_commerce.*
without having to reach into agentscore_commerce.identity.

Re-exported: A2AAgentCard{,Capabilities,Extension,Signature},
A2AAgentInterface, A2AAgentProvider, A2AAgentSkill, UCPCapabilityBinding,
UCPPaymentHandlerBinding, UCPProfile, UCPProfileBody, UCPServiceBinding,
GeneratedUCPKey. Submodule imports continue to work for power users.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds build_gate_from_policy, run_gate_with_enforcement,
shipping_country_allowed, shipping_state_allowed, validate_shipping_against_policy,
EnforcementMode, GateResult, IdentityStatus, PolicyCheck, PolicyResult to
agentscore_commerce.*

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…obals

Stub env vars for the streaming SSE handler (501-stubbed today).
Reference them in a comment instead. Caught by github-code-quality bot.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Use agentscore_commerce top-level for symbols that are now re-exported
there. Submodule imports stay only for symbols that aren't top-level
(networks, validate_x402_network_config, create_*_server, payment-protocol
helpers, framework-specific gates / discovery / stripe_multichain).
Matches node-commerce.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…_profile

Last submodule-import-with-top-level-equivalent. Matches node.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The identity package's _load_asgi_middleware fallback caused
agentscore_commerce.CreateSessionOnMissing to resolve to None at runtime.
Import directly from agentscore_commerce.identity.sessions (the canonical
home) at the top-level __init__ so the symbol is always usable.

Also flatten examples/identity_only.py to import CreateSessionOnMissing
from the top level — now possible.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Quick-start: RailSpec types + validate_shipping_against_policy via top-level
- Payment helpers section: extract_payment_signer via top-level

Remaining /payment + /identity.policy imports are submodule-only helpers
(build_payment_directive, networks, build_x402_accepts_for_402, etc.).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@vvillait88
vvillait88 merged commit 1e6cf67 into main May 15, 2026
7 checks passed
@vvillait88
vvillait88 deleted the feat/sdk-lifts-tier-1-2 branch May 15, 2026 19:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant