Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
dec3e19
feat(identity/policy): validate_shipping_against_policy helper (Tier …
vvillait88 May 15, 2026
6e75da7
feat(checkout): pricing_result factory (Tier 1 lift C)
vvillait88 May 15, 2026
f68970c
feat(checkout): Checkout(discovery_probe=...) auto-routing (Tier 2 li…
vvillait88 May 15, 2026
33ae4d8
feat(discovery): signed_response_<framework> wrappers (Tier 2 lift A)
vvillait88 May 15, 2026
0a15eb7
feat(checkout): mount_ucp_routes_<framework> methods (Tier 2 lift E)
vvillait88 May 15, 2026
4ead6e4
docs(examples): wire 4 examples to Tier 1+2 helpers (B, C, D, E)
vvillait88 May 15, 2026
2e08b8d
chore: SDK cleanup wave 1: kill handle_starlette alias + de-goods-ify…
vvillait88 May 15, 2026
a75509a
chore: SDK cleanup wave 2: redesigns + identity_metadata wire-in
vvillait88 May 15, 2026
a9cab13
docs(examples): api_provider demos kind="api" + trial-credit redemption
vvillait88 May 15, 2026
8b798f6
chore: kill python get_deposit_address + wire Receipt demo into multi…
vvillait88 May 15, 2026
9054df7
docs: refresh README + CLAUDE + examples README for 2.0 surface
vvillait88 May 15, 2026
f1edaa8
fix(identity): re-export validate_shipping_against_policy from packag…
vvillait88 May 15, 2026
439daab
feat(pricing): discount field + agentscore-py 2.3.2 refresh
vvillait88 May 15, 2026
e6d8ed3
test: drop redundant local asyncio import
vvillait88 May 15, 2026
7832a7b
docs: README discount mention + strip internal tier/lift annotations
vvillait88 May 15, 2026
cea1cbe
examples: align with node — full identity-only + real SDK helpers in
vvillait88 May 15, 2026
2a56ddf
examples(variable_cost): drop unused _X402_SERVER_BOOT alias
vvillait88 May 15, 2026
8376efb
refactor: top-level re-export A2A + UCP types + GeneratedUCPKey
vvillait88 May 15, 2026
3eac488
refactor: top-level re-export policy + shipping helpers
vvillait88 May 15, 2026
b41c464
examples(variable_cost): drop unused MPP_SECRET_KEY + TEMPO_ESCROW gl…
vvillait88 May 15, 2026
d9dd42b
examples: promote payment/identity imports to top-level
vvillait88 May 15, 2026
b42f7ed
examples(signed_ucp): use top-level UCPVerificationError + verify_ucp…
vvillait88 May 15, 2026
19a59c5
fix: top-level CreateSessionOnMissing returns the real dataclass
vvillait88 May 15, 2026
4699bb3
docs(README): promote example imports to top-level where re-exported
vvillait88 May 15, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,13 @@ Every helper is extracted from a real consumer, not speculated.

| Submodule | What it is |
|---|---|
| `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 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) |
| `agentscore_commerce.identity.{fastapi,flask,django,aiohttp,sanic,middleware}` | Trust gate middleware (KYC, age, sanctions on both account name and signer wallet, jurisdiction) |
| `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) |
| `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 |
| `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 |
| `agentscore_commerce.challenge` | 402-body builders: accepted_methods, identity_metadata, how_to_pay, agent_instructions, build_402_body, `build_validation_error` (4xx body builder) |
| `agentscore_commerce.stripe_multichain` | Multichain PaymentIntent helper, deposit-address lookup, testnet simulator, mppx Stripe wrapper |
| `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}` |
| `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) |
| `agentscore_commerce.stripe_multichain` | Multichain PaymentIntent helper (`create_multichain_payment_intent` returns `MultichainPaymentIntentResult`; read `result.deposit_addresses[network]` directly), testnet simulator (`simulate_crypto_deposit`, `simulate_deposit_if_test_mode`), `create_pi_cache`, `create_mppx_stripe` |
| `agentscore_commerce.api` | Re-exports `AgentScore` from `agentscore` SDK |

## Architecture
Expand Down
100 changes: 91 additions & 9 deletions README.md

Large diffs are not rendered by default.

51 changes: 50 additions & 1 deletion agentscore_commerce/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,12 @@
CheckoutRequest,
CheckoutResult,
CheckoutValidationError,
DiscoveryProbeConfig,
MppxComposeOutcome,
PricingResult,
SettleOutcome,
format_pydantic_errors,
pricing_result,
validation_envelope,
validation_response_aiohttp,
validation_response_django,
Expand All @@ -41,22 +43,38 @@
AGENTSCORE_UCP_CAPABILITY,
FIXABLE_DENIAL_REASONS,
UCP_A2A_EXTENSION_URI,
A2AAgentCard,
A2AAgentCardCapabilities,
A2AAgentCardExtension,
A2AAgentCardSignature,
A2AAgentInterface,
A2AAgentProvider,
A2AAgentSkill,
AgentIdentity,
AgentMemoryHint,
AgentScoreCore,
AgentScoreGatePolicy,
AssessResult,
CreateSessionOnMissing,
DenialCode,
DenialReason,
EnforcementMode,
GateResult,
GeneratedUCPKey,
IdentityStatus,
PolicyBlock,
SignerVerdict,
UCPCapabilityBinding,
UCPPaymentHandlerBinding,
UCPProfile,
UCPProfileBody,
UCPServiceBinding,
UCPSigningKey,
UCPVerificationError,
VerifyWalletSignerResult,
build_a2a_agent_card,
build_agent_memory_hint,
build_contact_support_next_steps,
build_gate_from_policy,
build_jwks_response,
build_signer_mismatch_body,
build_ucp_profile,
Expand All @@ -67,13 +85,19 @@
is_fixable_denial,
load_ucp_signing_key_from_env,
mpp_payment_handler,
run_gate_with_enforcement,
shipping_country_allowed,
shipping_state_allowed,
sign_ucp_profile,
stripe_spt_payment_handler,
ucp_a2a_extension,
validate_shipping_against_policy,
verification_agent_instructions,
verify_ucp_profile,
x402_payment_handler,
)
from agentscore_commerce.identity.sessions import CreateSessionOnMissing
from agentscore_commerce.identity.types import PolicyCheck, PolicyResult
from agentscore_commerce.payment import (
PaymentSigner,
SignerNetwork,
Expand Down Expand Up @@ -101,6 +125,13 @@
"AGENTSCORE_UCP_CAPABILITY",
"FIXABLE_DENIAL_REASONS",
"UCP_A2A_EXTENSION_URI",
"A2AAgentCard",
"A2AAgentCardCapabilities",
"A2AAgentCardExtension",
"A2AAgentCardSignature",
"A2AAgentInterface",
"A2AAgentProvider",
"A2AAgentSkill",
"AgentIdentity",
"AgentMemoryHint",
"AgentScoreCore",
Expand All @@ -116,9 +147,16 @@
"CreateSessionOnMissing",
"DenialCode",
"DenialReason",
"DiscoveryProbeConfig",
"EnforcementMode",
"GateResult",
"GeneratedUCPKey",
"IdentityStatus",
"MppxComposeOutcome",
"PaymentSigner",
"PolicyBlock",
"PolicyCheck",
"PolicyResult",
"PricingResult",
"SettleOutcome",
"SignerNetwork",
Expand All @@ -127,6 +165,11 @@
"StripeRailSpec",
"TempoRailSpec",
"TempoSessionRailSpec",
"UCPCapabilityBinding",
"UCPPaymentHandlerBinding",
"UCPProfile",
"UCPProfileBody",
"UCPServiceBinding",
"UCPSigningKey",
"UCPVerificationError",
"VerifyWalletSignerResult",
Expand All @@ -135,6 +178,7 @@
"build_a2a_agent_card",
"build_agent_memory_hint",
"build_contact_support_next_steps",
"build_gate_from_policy",
"build_jwks_response",
"build_signer_mismatch_body",
"build_ucp_profile",
Expand All @@ -151,10 +195,15 @@
"load_ucp_signing_key_from_env",
"make_mppx_compose_hook",
"mpp_payment_handler",
"pricing_result",
"read_x402_payment_header",
"run_gate_with_enforcement",
"shipping_country_allowed",
"shipping_state_allowed",
"sign_ucp_profile",
"stripe_spt_payment_handler",
"ucp_a2a_extension",
"validate_shipping_against_policy",
"validation_envelope",
"validation_response_aiohttp",
"validation_response_django",
Expand Down
16 changes: 8 additions & 8 deletions agentscore_commerce/challenge/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,23 +10,23 @@
from agentscore_commerce.challenge.body import X402PaymentRequired, build_402_body
from agentscore_commerce.challenge.how_to_pay import build_how_to_pay
from agentscore_commerce.challenge.identity import IdentityMode, SignerMatchResult, build_identity_metadata
from agentscore_commerce.challenge.order_receipt import (
OrderNextSteps,
OrderProductInfo,
OrderReceipt,
from agentscore_commerce.challenge.pricing import PricingBlock, build_pricing_block
from agentscore_commerce.challenge.receipt import (
ProductInfo,
Receipt,
ReceiptNextSteps,
ShippingAddress,
)
from agentscore_commerce.challenge.pricing import PricingBlock, build_pricing_block
from agentscore_commerce.challenge.respond_402 import Respond402Result, respond_402
from agentscore_commerce.challenge.validation_error import build_validation_error

__all__ = [
"AgentMemoryHint",
"IdentityMode",
"OrderNextSteps",
"OrderProductInfo",
"OrderReceipt",
"PricingBlock",
"ProductInfo",
"Receipt",
"ReceiptNextSteps",
"Respond402Result",
"ShippingAddress",
"SignerMatchResult",
Expand Down
4 changes: 2 additions & 2 deletions agentscore_commerce/challenge/agent_instructions.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@

_TEMPO_WARNING = (
"Do NOT use `tempo wallet transfer` to pay to the address above. That moves USDC on-chain but does not "
"notify this server, so the order will not complete. Use `tempo request` instead; it performs the full MPP "
"notify this server, so the request will not complete. Use `tempo request` instead; it performs the full MPP "
"handshake (signs, submits Authorization: Payment, waits for server confirmation)."
)
_X402_WARNING = (
"Do NOT send USDC manually to the x402 deposit addresses (e.g. via a bare wallet `transfer`). Use "
"`agentscore-pay pay` so the X-Payment credential is signed and submitted; otherwise the order will not "
"`agentscore-pay pay` so the X-Payment credential is signed and submitted; otherwise the request will not "
"complete even though the deposit lands."
)
_TEMPO_TOOL = "`tempo request` for Tempo USDC (installs via `tempo add request`)"
Expand Down
86 changes: 0 additions & 86 deletions agentscore_commerce/challenge/order_receipt.py

This file was deleted.

32 changes: 27 additions & 5 deletions agentscore_commerce/challenge/pricing.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,17 +25,22 @@ class PricingBlock:
"""

subtotal: str
"""Pre-tax, pre-shipping subtotal."""
"""List-price subtotal, pre-tax, pre-shipping, pre-discount."""

tax: str
"""Tax amount. Always present even if ``"0.00"``."""

total: str
"""Final total = subtotal + tax + shipping."""
"""Final total = subtotal + tax + shipping - discount. Floored at 0."""

shipping: str | None = None
"""Shipping cost. Omit for digital goods / services."""

discount: str | None = None
"""Discount deducted from subtotal (redemption code, coupon, promo). Omit when
no discount applied; agents reading the 402 see ``subtotal``/``discount``/``total``
and can render the savings line."""

tax_rate: float | None = None
"""Tax rate as a decimal fraction (e.g. ``0.0775`` for 7.75%). Omit for tax-free merchants."""

Expand All @@ -54,6 +59,8 @@ def to_dict(self) -> dict[str, Any]:
}
if self.shipping is not None:
out["shipping"] = self.shipping
if self.discount is not None:
out["discount"] = self.discount
if self.tax_rate is not None:
out["tax_rate"] = self.tax_rate
if self.tax_state is not None:
Expand All @@ -67,6 +74,7 @@ def build_pricing_block(
subtotal_cents: int,
tax_cents: int = 0,
shipping_cents: int | None = None,
discount_cents: int | None = None,
total_cents: int | None = None,
tax_rate: float | None = None,
tax_state: str | None = None,
Expand All @@ -75,7 +83,8 @@ def build_pricing_block(
"""Compose a :class:`PricingBlock` from cents-denominated inputs.

Handles the cents → dollar-string conversion (always 2 decimals) and computes the total
when not explicitly provided.
when not explicitly provided. ``subtotal_cents`` is the list price, pre-discount;
``discount_cents`` is the deduction applied (redemption code, coupon).

Example::

Expand All @@ -88,18 +97,31 @@ def build_pricing_block(
)
# → PricingBlock(subtotal="250.00", tax="18.75", shipping="9.99", total="278.74", ...)

# Redemption-code applied:
pricing = build_pricing_block(
subtotal_cents=7500,
discount_cents=7500,
)
# → PricingBlock(subtotal="75.00", discount="75.00", tax="0.00", total="0.00")

Pass ``shipping_cents=0`` for digital goods if you want the field present (it's then
``"0.00"``); pass ``None`` (or omit) if you don't want shipping in the response shape
at all.
at all. Total floors at 0 when discount exceeds subtotal + tax + shipping.
"""
shipping = shipping_cents if shipping_cents is not None else 0
total = total_cents if total_cents is not None else subtotal_cents + tax_cents + shipping
discount = discount_cents if discount_cents is not None else 0
if total_cents is None:
gross = subtotal_cents + tax_cents + shipping - discount
total = max(0, gross)
else:
total = total_cents

return PricingBlock(
subtotal=_format_cents(subtotal_cents),
tax=_format_cents(tax_cents),
total=_format_cents(total),
shipping=_format_cents(shipping) if shipping_cents is not None else None,
discount=_format_cents(discount) if discount_cents is not None else None,
tax_rate=tax_rate,
tax_state=tax_state,
currency=currency,
Expand Down
Loading