Skip to content

feat: 2.1.1 — typed errors, bootstrap session helper, stripe $0.50 auto-drop - #53

Merged
vvillait88 merged 6 commits into
mainfrom
feat/2.1.1-typed-errors-bootstrap-helper
May 20, 2026
Merged

feat: 2.1.1 — typed errors, bootstrap session helper, stripe $0.50 auto-drop#53
vvillait88 merged 6 commits into
mainfrom
feat/2.1.1-typed-errors-bootstrap-helper

Conversation

@vvillait88

@vvillait88 vvillait88 commented May 19, 2026

Copy link
Copy Markdown
Contributor

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: malformed Authorization: Payment, cache-miss recipient, missing recipient field → CheckoutValidationError(401, invalid_credential, action=retry_without_credential). Fallback when none of preferred/base/tempo recipients land → 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.

Previously these all surfaced as bare ValueError / RuntimeError → 500 to the agent.

2. CheckoutValidationError extraction to its own module

agentscore_commerce/errors.py is the new canonical home. checkout.py, checkout_compute_first.py, identity/policy.py, stripe_multichain/{pay_to_address,payment_intent}.py, payment/dispatch.py import directly. Top-level __init__.py re-exports for the public surface.

This breaks the identity.policycheckout cycle that previously required lazy/local imports.

3. build_verification_required_body(reason, message=?, agent_instructions=?, extra=?) helper

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.

5. Stripe $0.50 USD 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 the stripe/charge rail from BOTH layers when amount_usd < 0.50:

  • build_mppx_compose_rails in agentscore_commerce/payment/compose_rails.py — drops the stripe/charge intent at mppx compose time with a one-time logging.warning. Warn-once state lives on a _WarnedFlags class (module-level class attribute, lint-clean).
  • Checkout._emit_402 in agentscore_commerce/checkout.py AND compute_first_checkout._emit_402 in agentscore_commerce/checkout_compute_first.py — strip the stripe slot from the rails dict before build_accepted_methods / build_how_to_pay run, so the 402 body's accepted_methods + agent_instructions.how_to_pay stay consistent with what pympp will actually accept.

Pass include_stripe=False to 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

lefthook 2.1.6 → 2.1.8. uv sync --upgrade --all-extras --all-groups; no major bumps.

Version

  • 2.1.02.1.1

Test 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)
  • Cross-language parity verified against node-commerce 2.1.1 (same auto-drop behavior at $0.50 boundary, same envelope shape)

🤖 Generated with Claude Code

vvillait88 and others added 2 commits May 18, 2026 21:39
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>
Comment thread agentscore_commerce/payment/compose_rails.py Fixed
Comment thread tests/test_compose_rails.py Fixed
vvillait88 and others added 3 commits May 19, 2026 09:01
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>
@vvillait88 vvillait88 changed the title feat: 2.1.1 — typed errors + bootstrap session-mint helper feat: 2.1.1 — typed errors, bootstrap session helper, stripe $0.50 auto-drop May 19, 2026
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>
@vvillait88
vvillait88 merged commit eb2bc01 into main May 20, 2026
7 checks passed
@vvillait88
vvillait88 deleted the feat/2.1.1-typed-errors-bootstrap-helper branch May 20, 2026 00:39
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