feat: 2.1.1 — typed errors, bootstrap session helper, stripe $0.50 auto-drop - #53
Merged
Merged
Conversation
Mirrors the node-commerce 2.1.1 changes for cross-language parity.
1. Typed errors in stripe-multichain + dispatch helpers
- `pay_to_address.py`: malformed Authorization: Payment, cache-miss
recipient, missing recipient field → CheckoutValidationError(401,
`invalid_credential`, action=`retry_without_credential`).
`pay_to_address` fallback → 503 `payment_provider_unavailable`.
- `payment_intent.py`: Stripe returns an empty `deposit_addresses`
map → 503 `payment_provider_unavailable`.
- `payment/dispatch.py`: unregistered EVM/Solana handler or
unrecognized network family → 503 `payment_provider_unavailable`.
2. CheckoutValidationError extraction to its own module
`agentscore_commerce/errors.py` is the new canonical home for the
class. checkout.py, checkout_compute_first.py, identity/policy.py,
stripe_multichain/{pay_to_address,payment_intent}.py,
payment/dispatch.py import directly from there. Top-level __init__.py
re-exports for the public surface. The checkout module no longer
re-exports the class — direct imports break the cycle that
previously required lazy/local imports for the
identity.policy → checkout dep edge.
3. build_verification_required_body(reason, message=?, agent_instructions=?, extra=?)
Collapses the per-merchant identity_verification_required body
mapping into one call. Same shape as the node helper.
4. (Already existing — preserved) Checkout auto-defaults
`create_session_on_missing` from gate.api_key + gate.base_url +
gate.context + gate.merchant_name when not supplied.
CLAUDE.md updated for symmetry with node. Compliance-merchant example
updated to use the helper. Tests cover the new throws + helper. ✓
1383 tests pass, 95.08% coverage.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
build_mppx_compose_rails now drops the stripe/charge intent (with a one-time logging.warning) when amount_usd < 0.50. Stripe's fixed ~$0.30 fee makes sub-50-cent charges unprofitable - a $0.11 PI nets -$0.19 after fees; many accounts also reject PI creation under the floor with amount_too_small. Callers can pass include_stripe=False explicitly to silence the warning. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The compose-time auto-drop in build_mppx_compose_rails landed last commit but the 402 body's accepted_methods + how_to_pay still came from the static build_default_checkout_rails config - so agents saw stripe offered even though there was no matching WWW-Authenticate challenge for it. Move STRIPE_MIN_CHARGE_USD into payment/constants.py and consume it from BOTH layers: - build_mppx_compose_rails (already did): drops the stripe intent from the compose list. - Checkout._emit_402 + compute_first_checkout._emit_402 (this commit): strip stripe from emit_rails before build_accepted_methods runs, so accepted_methods + how_to_pay never advertise a rail mppx won't accept. For variable-price merchants where one product is below $0.50 and others above, each cart now gets a consistent 402 - the rail appears/disappears with the cart total. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- lefthook 2.1.6 -> 2.1.8 uv sync --upgrade --all-extras --all-groups; no major bumps. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Wrap the warn-once flag in a _WarnedFlags class so the symbol is referenced at module scope (was: bare module-level bool only mutated via `global`, which the lint pass flagged as unused at module scope). - Drop the per-test `import compose_rails as mod` indirection; reset via _WarnedFlags directly. Eliminates the mixed `import` / `from import` styles bot complaint on test_compose_rails.py. No behavior change. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
When a pympp rail's verify() throws (e.g. a Tempo RPC rejection with keychain validation failed: KeyNotFound), the canonical compose hook previously swallowed str(error) and returned a bare MppxComposeOutcome(status=402), losing the recovery signal. The agent saw the generic `payment_proof_invalid: regenerate` body and had no hint to drive the WebAuthn enrollment flow. - Add `failure_reason: str | None` to MppxComposeOutcome. Custom hooks can opt in by setting it; make_mppx_compose_hook now captures `str(error)` automatically on the except branch. - New `classify_mppx_failure(reason)` mirrors the node SDK: known patterns map to typed ClassifiedMppxFailure envelopes. First entry: Tempo keychain rejection -> 401 `tempo_key_not_registered` with recovery hints (run `tempo wallet login` or switch rail). - `_handle_mppx` runs the classifier when failure_reason is set; returns the typed envelope. Falls back to the generic `payment_proof_invalid` otherwise. pympp already preserves the inner error via re-raise (unlike node's mppx which swallows + wraps), so no AsyncLocalStorage / console interception needed - the exception lands directly in the compose hook's catch block. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Python parity for the node-commerce 2.1.1 release (agentscore/node-commerce#55).
1. Typed errors in stripe-multichain + dispatch helpers
pay_to_address.py: malformedAuthorization: Payment, cache-miss recipient, missing recipient field →CheckoutValidationError(401, invalid_credential, action=retry_without_credential). Fallback when none of preferred/base/tempo recipients land → 503payment_provider_unavailable.payment_intent.py: Stripe returns an emptydeposit_addressesmap → 503payment_provider_unavailable.payment/dispatch.py: unregistered EVM/Solana handler or unrecognized network family → 503payment_provider_unavailable.Previously these all surfaced as bare
ValueError/RuntimeError→ 500 to the agent.2.
CheckoutValidationErrorextraction to its own moduleagentscore_commerce/errors.pyis the new canonical home.checkout.py,checkout_compute_first.py,identity/policy.py,stripe_multichain/{pay_to_address,payment_intent}.py,payment/dispatch.pyimport directly. Top-level__init__.pyre-exports for the public surface.This breaks the
identity.policy→checkoutcycle that previously required lazy/local imports.3.
build_verification_required_body(reason, message=?, agent_instructions=?, extra=?)helperCollapses the per-merchant
identity_verification_requiredbody mapping into one call. Same shape as the node helper.4. (Already existing — preserved)
Checkoutauto-defaultscreate_session_on_missingFrom
gate.api_key+gate.base_url+gate.context+gate.merchant_namewhen not supplied.5. Stripe
$0.50USD auto-drop (compose + discovery)Stripe's fixed ~$0.30 fee makes sub-50-cent card charges unprofitable (a $0.11 PI nets -$0.19 after fees); many accounts also reject PI creation under the floor with
amount_too_small. The SDK now drops thestripe/chargerail from BOTH layers whenamount_usd < 0.50:build_mppx_compose_railsinagentscore_commerce/payment/compose_rails.py— drops thestripe/chargeintent at mppx compose time with a one-timelogging.warning. Warn-once state lives on a_WarnedFlagsclass (module-level class attribute, lint-clean).Checkout._emit_402inagentscore_commerce/checkout.pyANDcompute_first_checkout._emit_402inagentscore_commerce/checkout_compute_first.py— strip thestripeslot from the rails dict beforebuild_accepted_methods/build_how_to_payrun, so the 402 body'saccepted_methods+agent_instructions.how_to_paystay consistent with what pympp will actually accept.Pass
include_stripe=Falseto suppress the warning when the merchant knows their pricing tier is permanently sub-50-cent. Shared constant:agentscore_commerce/payment/constants.py:STRIPE_MIN_CHARGE_USD.6. In-range deps bumps
lefthook2.1.6 → 2.1.8.uv sync --upgrade --all-extras --all-groups; no major bumps.Version
2.1.0→2.1.1Test plan
uv run ruff check .uv run ty check agentscore_commerce/uv run pytest tests/— 1385 pass + 4 skipped, 95.05% coverage (clears 95% bar)🤖 Generated with Claude Code