Skip to content

feat(checkout): seamless merchant helpers (adapters, hooks, discovery) - #46

Merged
vvillait88 merged 6 commits into
mainfrom
seamless-checkout-helpers
May 15, 2026
Merged

feat(checkout): seamless merchant helpers (adapters, hooks, discovery)#46
vvillait88 merged 6 commits into
mainfrom
seamless-checkout-helpers

Conversation

@vvillait88

@vvillait88 vvillait88 commented May 15, 2026

Copy link
Copy Markdown
Contributor

Summary

Collapses ~190 lines of per-merchant boilerplate into spec-rooted SDK helpers. After this PR a goods-merchant /purchase route can be a one-liner that calls Checkout.handle_fastapi(request), and a UCP-publishing merchant's /.well-known/{ucp,jwks.json} handlers are ~10 lines instead of ~150.

Seamless helpers (commit 1):

  • Checkout framework adapters: handle_aiohttp / handle_sanic / handle_flask / handle_django (parity with the FastAPI adapter; sync frameworks bridge via asgiref.async_to_sync).
  • validation_envelope + per-framework validation_response_* wrappers for the canonical 4xx body.
  • Lazy server helpers: lazy_x402_server / lazy_mppx_server return memoized asyncio.Lock-guarded async getters; Checkout auto-derives both from flat cdp_api_key_id / cdp_api_key_secret / mppx_secret_key kwargs.
  • make_mppx_compose_hook factory: the canonical compose_mppx hook for pympp, including DID-based signer extraction.
  • pre_validate hook + ctx.state pattern so a module-level Checkout supports per-request validation without surrendering encapsulation.
  • CheckoutGateConfig wires the AgentScore gate into Checkout; per-request policy is resolved via per_request_policy(ctx).
  • SettleOutcome normalized (rail_key, signer_address, signer_network, tx_hash). CheckoutContext.identity_status property.
  • zero_settle_carve_out handles the EIP-3009 value=0 and pympp proof carve-outs.
  • extract_signer_for_precheck reads x402 OR MPP credentials.
  • Checkout.accepted_rails + accepted_method_names accessors.
  • Signed discovery surfaces: build_signed_ucp_response, build_signed_jwks_response, well_known_cors_preflight_headers, bootstrap_ucp_signing_key, default_a2a_services. UCP section 6 cache, CORS, and X-Request-ID handling are folded in.
  • agentscore_content (PURCHASE_MODE_NOTES, build_agentscore_onboarding_steps, standard_endpoint_descriptions, build_success_next_steps) plus build_redemption_skill_md template for canonical agent-facing copy.
  • format_usd_cents helper.
  • RailSpec auto-derivation (X402BaseRailSpec network -> chain_id/token, TempoRailSpec testnet -> network/chain_id/token).

Cleanup (commit 2): Drops three internal-only re-export shims (__all_sdk_timeout__, _ = asdict, _ = networks) and inlines the lone-caller TokenDeniedError.next_steps accessor.

Tier 1+2+3 lifts + integration fixes (commit 3):

  • Gate-hook extensions on Checkout (per_request_policy, on_denied, run_gate) and ctx.capture_wallet closure for clean settle-time wallet capture without framework-bound helpers.
  • Checkout.discovery_extensions plumbed through build_402_body so per-route bazaar (and other x402 body extensions) merge into the challenge body.
  • well_known_preflight_response / WellKnownPreflightResponse for the 204 CORS preflight every UCP-publishing merchant otherwise hand-rolls.
  • build_merchant_index_json canonical / discovery body builder.
  • x_service_info_extension + x_payment_info_from_checkout openapi helpers.
  • x_payment_info_extension emits authMode: "payment" + description.
  • load_solana_fee_payer accepting base58 (64-byte or 32-byte seed) and 128-char hex.
  • Auto-CDP-toggle in create_x402_server: when CDP_API_KEY_ID + CDP_API_KEY_SECRET env vars are set, defaults to facilitator="coinbase".
  • body.extensions passthrough on X402PaymentRequired.
  • Top-level agentscore_commerce re-exports of frequently-used SDK symbols.

Smoke-tested live against the migrated AgentScore Store (core/store) and martin-estate: real orders end-to-end on tempo, x402-base, and Solana, plus signed UCP + JWKS, GET /orders/{id} gate, and every 4xx error envelope.

Verification

  • uv run ruff check, uv run ty check, uv run vulture all pass.
  • uv run pytest 1132 passed, 4 skipped.
  • CI coverage threshold (95%) still failing on pre-existing low-coverage files (pi_cache.py, parts of checkout.py gate-hook paths, well_known.py signed-profile branches). Follow-up.

Test plan

  • Local pytest green
  • Two consumers (core/store + martin-estate) migrated and smoke-clean against dev

🤖 Generated with Claude Code

vvillait88 and others added 2 commits May 14, 2026 17:54
Collapses ~190 lines of per-merchant boilerplate into spec-rooted SDK helpers:

* Checkout framework adapters: handle_aiohttp / handle_sanic / handle_flask /
  handle_django (parity with the FastAPI adapter; sync frameworks bridge via
  asgiref.async_to_sync). validation_envelope + per-framework
  validation_response_* wrappers for the canonical 4xx body.
* Lazy server helpers: lazy_x402_server / lazy_mppx_server return memoized
  asyncio.Lock-guarded async getters; Checkout auto-derives both from flat
  cdp_api_key_id / cdp_api_key_secret / mppx_secret_key kwargs.
* make_mppx_compose_hook factory: the canonical compose_mppx hook for pympp,
  including DID-based signer extraction.
* pre_validate hook + ctx.state pattern so a module-level Checkout supports
  per-request validation without surrendering encapsulation.
* CheckoutGateConfig wires the AgentScore gate into Checkout; per-request
  policy is resolved via per_request_policy(ctx).
* SettleOutcome normalized (rail_key, signer_address, signer_network,
  tx_hash). CheckoutContext.identity_status property.
* zero_settle_carve_out handles the EIP-3009 value=0 and pympp 'proof'
  carve-outs.
* extract_signer_for_precheck reads x402 OR MPP credentials.
* Checkout.accepted_rails + accepted_method_names accessors.
* Signed discovery surfaces: build_signed_ucp_response,
  build_signed_jwks_response, well_known_cors_preflight_headers,
  bootstrap_ucp_signing_key, default_a2a_services. UCP section 6 cache, CORS,
  and X-Request-ID handling are folded in.
* agentscore_content (PURCHASE_MODE_NOTES, build_agentscore_onboarding_steps,
  standard_endpoint_descriptions, build_order_success_next_steps) plus
  build_redemption_skill_md template for the canonical agent-facing copy.
* format_usd_cents helper.
* RailSpec auto-derivation (X402BaseRailSpec network -> chain_id/token,
  TempoRailSpec testnet -> network/chain_id/token).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three internal-only references with zero callers, kept alive only by
keep-import-name tricks:

* ``__all_sdk_timeout__ = SdkTimeoutError`` in identity/core.py: never imported.
* ``_ = asdict`` in identity/_denial.py: marked as "re-exported for vendors"
  but not in __all__ and not assigned to a public name.
* ``_ = networks`` in payment/rail_spec.py: import was only kept alive by this
  line; the module's actual values come from USDC constants.

Plus:

* Inline ``TokenDeniedError.next_steps`` at its sole internal caller; the
  attribute was labelled "Legacy accessor" with no external use.
* Rename the misleading "legacy stubs" test in test_lifted_helpers.py and
  reword the matching comment in payment/x402_settle.py.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Comment thread tests/test_seamless_helpers.py Fixed
Adds the work shipped alongside the consumer migrations:

- `Checkout` gate hook extensions (`per_request_policy`, `on_denied`, `run_gate`)
  and `ctx.capture_wallet` closure for clean settle-time wallet capture without
  framework-bound helpers.
- `Checkout.discovery_extensions` plumbed through `build_402_body` so per-route
  bazaar (and other x402 body extensions) merge into the challenge body.
- `well_known_preflight_response` / `WellKnownPreflightResponse` for the 204
  CORS preflight every UCP-publishing merchant otherwise hand-rolls.
- `build_merchant_index_json` canonical `/` discovery body builder.
- `x_service_info_extension` + `x_payment_info_from_checkout` openapi helpers.
- `x_payment_info_extension` emits `authMode: "payment"` + `description`.
- `load_solana_fee_payer` accepting base58 (64-byte or 32-byte seed) and 128-char
  hex; merchants drop the per-merchant Solana keypair parsing boilerplate.
- Auto-CDP-toggle in `create_x402_server`: when `CDP_API_KEY_ID` +
  `CDP_API_KEY_SECRET` env vars are set, defaults to `facilitator="coinbase"`.
- `body.extensions` passthrough on `X402PaymentRequired`.
- Top-level `agentscore_commerce` re-exports of frequently-used SDK symbols
  (`AgentScoreCore`, denial helpers, rail-spec types, signer helpers, ...) for
  flat imports.

Tests: 1132 passed, 4 skipped. Includes coverage for `solana.load_solana_fee_payer`,
the new `x402_server` paths (auto-CDP toggle, custom schemes, upto rails, dict
requirements, JWT minting via cdp-sdk), and the new openapi/discovery helpers.

Tightens the test_seamless_helpers.py:400 assertion so CodeQL no longer matches
`py/incomplete-url-substring-sanitization` on a markdown content check.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Comment thread tests/test_payment_servers.py Fixed
vvillait88 and others added 3 commits May 14, 2026 22:29
… openapi rails)

- pi_cache.py Redis-backed paths via sys.modules['redis.asyncio'] stub + eviction
  loop via asyncio.sleep monkeypatch. pi_cache.py 71 to 100 percent.
- build_signed_ucp_response happy path + multi-rail (solana/stripe/tempo_session)
  + 503 misconfigured branch.
- build_signed_jwks_response with X-Request-ID echo.
- bootstrap_ucp_signing_key malformed-env throw + valid-env success.
- default_a2a_services + well_known_cors_preflight_headers.
- x_payment_info_from_checkout across all four rail types (stripe, base, solana
  with currency, tempo). openapi.py 86 to 100 percent.

Drops an unused _FacilitatorStub ellipsis-body class from the bazaar test
(code-quality bot flagged the no-op statement); facilitator is now opaque object().

Tests: 1146 passed, 4 skipped. Total coverage: 91.19 to 93.02 percent.

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

Adds tests covering the previously-uncovered Checkout flows:

- gate run_gate escape hatch (allow / deny / invalid-shape branches)
- gate per_request_policy returning None (skip-gate path)
- gate on_denied callback reshaping the canonical denial body
- gate happy path attaches ctx.capture_wallet closure; the closure itself
  is exercised end-to-end with AgentScoreCore.acapture_wallet mocked
- Checkout.accepted_rails dedupes tempo + tempo_session to a single slug
- Checkout.accepted_method_names emits each protocol method
- zero-settle MPP carve-out at zero (compose_mppx skipped, signer lifted)

Also tightens the _spec_rail_key / _spec_method_name return types from
RailKey | None / str | None to non-nullable (every code path returns a
value; the | None was dead and the key is None checks in the
accepted_rails / accepted_method_names accessors were unreachable).
Mirrors the same fix landed in node-commerce.

Tests: 1156 passed, 4 skipped. Total coverage: 93.02 to 95.00 percent.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@vvillait88
vvillait88 merged commit 1ee7d0b into main May 15, 2026
7 checks passed
@vvillait88
vvillait88 deleted the seamless-checkout-helpers branch May 15, 2026 13:17
vvillait88 added a commit to agentscore/node-commerce that referenced this pull request May 15, 2026
## Summary

Cross-language port of python-commerce #46 ([seamless merchant
helpers](agentscore/python-commerce#46)).

Collapses ~190 lines of per-merchant boilerplate into spec-rooted SDK
helpers. After this PR a goods-merchant `/purchase` route can be a
one-liner that calls `Checkout.handleHono(c)`, and a UCP-publishing
merchant's `/.well-known/{ucp,jwks.json}` handlers are ~10 lines instead
of ~150.

**Seamless helpers:**

- Checkout framework adapters: `handleHono` / `handleExpress` /
`handleFastify` / `handleNextjs` / `handleWeb`.
- `validationEnvelope` + per-framework `validationResponse*` wrappers
for the canonical 4xx body.
- Lazy server helpers: `lazyX402Server` / `lazyMppxServer` return
memoized async getters; `Checkout` auto-derives both from flat
`cdpApiKeyId` / `cdpApiKeySecret` / `mppxSecretKey` kwargs.
- `makeMppxComposeHook` factory: the canonical `composeMppx` hook for
mppx.
- `preValidate` hook + `ctx.state` pattern so a module-level Checkout
supports per-request validation without surrendering encapsulation.
- `CheckoutGateConfig` wires the AgentScore gate into Checkout;
per-request policy via `perRequestPolicy(ctx)`.
- `SettleOutcome` normalized (`railKey`, `signerAddress`,
`signerNetwork`, `txHash`). `CheckoutContext.identityStatus`.
- `zeroSettleCarveOut` handles the EIP-3009 `value=0` and mppx `proof`
carve-outs.
- `extractSignerForPrecheck` reads x402 OR MPP credentials.
- `Checkout.acceptedRails` + `acceptedMethodNames` accessors.
- Signed discovery surfaces: `buildSignedUcpResponse`,
`buildSignedJwksResponse`, `wellKnownCorsPreflightHeaders`,
`wellKnownPreflightResponse`, `bootstrapUcpSigningKey`,
`defaultA2aServices`. UCP section 6 cache, CORS, and `X-Request-ID`
handling folded in.
- `agentscore_content`: `PURCHASE_MODE_NOTES`,
`buildAgentscoreOnboardingSteps`, `standardEndpointDescriptions`,
`buildSuccessNextSteps`, `buildMerchantIndexJson`,
`buildRedemptionSkillMd`.
- `formatUsdCents` helper.

**Tier 1+2+3 lifts + integration fixes:**

- Gate-hook extensions on `Checkout` (`perRequestPolicy`, `onDenied`,
`runGate`) and `ctx.captureWallet` closure for clean settle-time wallet
capture without framework-bound helpers.
- `Checkout.discoveryExtensions` plumbed through `build402Body` so
per-route bazaar merges into the challenge body.
- `xServiceInfoExtension` + `xPaymentInfoFromCheckout` openapi helpers.
- `xPaymentInfoExtension` emits `authMode: "payment"` + `description`.
- `loadSolanaFeePayer` accepting base58 (64-byte or 32-byte seed) and
128-char hex.
- Auto-CDP-toggle in `createX402Server`: when `CDP_API_KEY_ID` +
`CDP_API_KEY_SECRET` env vars are set, defaults to
`facilitator="coinbase"`.
- `body.extensions` passthrough on x402 PaymentRequired body.
- `processX402Settle` calls `verifyPayment` (was a non-existent method;
both verify + settle phases now exercise the right x402/core v2 method).

## Verification

- `bun run lint`, `bun run typecheck` clean.
- `bun run test` 1055 passed, 4 skipped.
- Smoke-tested live against the migrated martin-estate and AgentScore
Store: real orders end-to-end on tempo + x402-base + Solana MPP.
- Coverage threshold (95/90/95/95) still failing on some pre-existing
files and the `buildSignedUcpResponse` happy path (~50 uncovered lines
in `well_known.ts`); follow-up.

## Test plan

- [x] Local vitest + typecheck green
- [x] Two consumers (`martin-estate` + `core/store`) already migrated
and smoke-clean

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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.

2 participants