Skip to content

feat(checkout): add Checkout orchestrator for one-call agent-commerce flow - #45

Merged
vvillait88 merged 2 commits into
mainfrom
feat/305-checkout-orchestrator-v2
May 14, 2026
Merged

feat(checkout): add Checkout orchestrator for one-call agent-commerce flow#45
vvillait88 merged 2 commits into
mainfrom
feat/305-checkout-orchestrator-v2

Conversation

@vvillait88

Copy link
Copy Markdown
Contributor

Summary

High-level orchestrator that collapses 402-emit + verify+settle into a single await checkout.handle() call. Services every merchant shape via the same primitive:

Axis Options
Rail mix x402-only / MPP-only / both
Custody self-custody (chain) / Stripe (custodial) / mixed
Identity gated (AgentScoreCore upstream) / ungated
Use case goods seller (per-order) / API seller (per-call)

Common usage

Goods seller (full agent-commerce):

checkout = Checkout(
    rails={
        "tempo": TempoRailSpec(recipient=...),
        "x402_base": X402BaseRailSpec(recipient=...),
        "stripe": StripeRailSpec(profile_id=...),
    },
    url=APP_URL,
    compute_pricing=lambda ctx: PricingResult(amount_usd=cart_total(ctx.body)),
    mint_recipients=lambda ctx: stripe_multichain_addresses_for(ctx),
    on_settled=lambda ctx, outcome: persist_order(ctx.reference_id, ctx.body, outcome),
    compose_mppx=lambda ctx: mppx_compose(mppx, ctx.request),
    x402_server=x402,
)

API seller (per-call x402):

checkout = Checkout(
    rails={"x402_base": X402BaseRailSpec(recipient=TREASURY)},
    url=APP_URL,
    compute_pricing=lambda ctx: PricingResult(amount_usd=0.01),
    on_settled=lambda ctx, _: {"data": await run_api(ctx.body)},
    x402_server=x402,
)

Design notes

  • Framework-neutral: handle() takes CheckoutRequest, returns CheckoutResult (body + headers + status + reference_id + settled). Merchant wraps in their framework's response.
  • Domain-neutral: reference_id is a UUID; goods merchants persist as order id, API merchants treat as request id.
  • x402 base network is auto-derived from rails["x402_base"].network — no duplicate kwarg.
  • CheckoutRequest.raw is an escape hatch for compose_mppx hooks that need the framework's native request object.
  • Flattening rule respected: __init__ is flat-kwargs with * keyword-only marker; all dataclasses are value types (data shapes for inputs/outputs), not parameter-bundle wrappers.

Test plan

  • pytest green (1076 tests pass; coverage 95.17%)
  • ty check clean
  • ruff check clean
  • vulture clean
  • 12-test matrix in tests/test_checkout.py covers every flexibility axis

… flow

Composes 402-emit + verify+settle into a single 'await checkout.handle()' call.
Services every merchant shape via the same primitive:

- Goods sellers: wire on_settled for order persistence + mint_recipients for
  Stripe-multichain per-order addresses.
- API sellers: wire compute_pricing for per-call billing; on_settled can
  return the inline API response body.
- Self-custody / Stripe / mixed: rails dict is the single source of truth.
- x402 only / MPP only / both: each handler is independently optional.
- Gated / ungated identity: CheckoutRequest.assess is optional.

Framework-neutral: handle() takes CheckoutRequest, returns CheckoutResult
(body + headers + status + reference_id + settled).

Domain-neutral: reference_id is a UUID; goods merchants persist as order id,
API merchants treat as request id.

x402 base network is auto-derived from rails['x402_base'].network. No
duplicate kwarg. CheckoutRequest.raw is an escape hatch for compose_mppx
hooks that need the framework's native request object.
Comment thread agentscore_commerce/checkout.py Fixed
CodeQL flagged Awaitable as unused because it only appeared inside string
forward refs. All referenced types (CheckoutContext, SettleOutcome, etc.)
are defined earlier in the file, so the strings aren't needed — drop them
and the linter sees Awaitable as a direct reference again.
@vvillait88
vvillait88 merged commit a1590df into main May 14, 2026
7 checks passed
@vvillait88
vvillait88 deleted the feat/305-checkout-orchestrator-v2 branch May 14, 2026 21:56
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