Skip to content

Pay in Bell's browser with a Crossmint agent card - #35

Open
aadopii wants to merge 4 commits into
browserbase:mainfrom
aadopii:feat/crossmint-agent-cards
Open

aadopii wants to merge 4 commits into
browserbase:mainfrom
aadopii:feat/crossmint-agent-cards

Conversation

@aadopii

@aadopii aadopii commented Oct 28, 2026

Copy link
Copy Markdown

Stacked on #34, which adds the Crossmint client and config. Review the last two commits.

#34 hands the whole checkout to Crossmint. This PR keeps the checkout in Bell's own browser and pays with a Crossmint agent card. The model drives the merchant page with Bell's existing tools, maps the payment fields with exact CSS selectors, and calls crossmint_card_checkout. The tool mints a one-time card from an order intent the user approved beforehand and fills it server-side through the same hosted-page path link__checkout uses. Nothing under server/link changes; server/browser/privacy-lock.ts gains one condition so the browser quarantine also engages for this path.

The card is the user's own, enrolled in Visa Intelligent Commerce or Mastercard Agent Pay: Mastercard cards from any country, and U.S.-issued Visa cards. The order intent carries the amount, the merchant scope and the expiry. Rewards, statement lines and disputes work as for any purchase.

Setup

The setup page pulls in Crossmint's React SDK, which carries wallet and chain dependencies the card page does not use. It lives in the second commit, which nothing else imports. If you would rather keep the dependency tree as it is, we drop that commit, and the operator does card setup in Crossmint's quickstart app, signed in with a token from Bell's issuer instead of its own sign-in.

Order intents accept a client key with the user's JWT, not a server key. Bell serves one operator, so it acts as the Crossmint project's custom JWT issuer: it signs a ten-minute RS256 token for CROSSMINT_USER_ID and serves the public key at /api/crossmint/jwks. pnpm crossmint:operator-jwt prints one for setup. /crossmint/cards, behind Bell's operator credentials, renders Crossmint's CrossmintAgentCardAuthorization component to save the card, create the order intent and verify the rail, then shows the id to paste into .env.local. This path needs DATABASE_URL for the privacy guard; #34 still needs none.

Guards

Payment runs through Eve's approval gate, approval: always(). The person reads "Pay 12.49 EUR at shop.example with your Crossmint agent card?" and only the authenticated sender who asked can approve. Before minting, the server reads the order intent and refuses unless it is active, the chosen rail is active, the available balance covers the amount, it names this merchant, and it has not expired. Field mappings take card inputs only, because the one-time card carries no billing address. CROSSMINT_ALLOWED_MERCHANTS and CROSSMINT_MAX_COST from #34 apply. The mint carries an idempotency key, so a retry never issues a second card.

The credential is read inside the fill operation and never returned. Tool output is the same enumerated status set Link's checkout returns. After payment entry the browser is quarantined from further model access, as with Link. Network tokens and the encrypted-card rail are out of scope here, in the same way Shared Payment Tokens are out of scope for link__checkout.

Tests

403 pass on Node 24 with typecheck, lint and the Eve build (60 opt-in provider tests skipped). The 28 new tests are mock-only. They cover the order intent checks with one case per refusal reason, the mint request and its idempotency key, the projection into Bell's field mapping, the two 409 codes an operator must act on, the operator token against the JWKS, the setup page's operator check, and that no credential value reaches output or errors. The staging smoke steps are in docs/crossmint.md under Verify and have not been run.

First commit, 16 files: one tool, two server modules, the JWKS route, the operator-token script, one test file, one condition in the privacy lock, the shared approval policy, and edits to the Crossmint doc, the instructions, .env.example, the README and the repository map. Second commit, 7 files: the setup page and its client component, the proxy, one test file, the dependency in package.json and the lockfile, and one paragraph in the Crossmint doc.

Two tools let Bell buy through Crossmint Agent Checkouts: crossmint_checkout
starts a run from a product URL, a plain-language task and a spending cap,
answers form questions and declines or cancels; crossmint_pay settles the
payment request with the operator's order intent.

Payment goes through Eve's tool approval rather than a confirmation in chat.
crossmint_pay is declared with approval: always(), the prompt shows the
amount, currency and merchant the model put in the call, and after approval
the server compares those with the live Crossmint request and with the cap
the run was created under before paying. Payments need an authenticated
sender, and only the requesting principal can approve.

Operator policy is checked before a run is created, so a refused request is
never billed: an optional per-checkout ceiling (CROSSMINT_MAX_COST) and an
optional merchant allowlist (CROSSMINT_ALLOWED_MERCHANTS). A saved buyer
profile (CROSSMINT_BUYER_PROFILE_ID) lets checkouts skip shipping and
contact questions.

The API key, buyer id, buyer profile and order intent id stay on the server.
Each snapshot is projected to a metadata allowlist before Eve can persist it,
and a conversation can only act on runs it started in this process. Link,
the browser runtime and the hooks are unchanged; with the Crossmint variables
unset both tools return not_configured.
One tool starts a checkout at any store with a product URL, a sentence and a
spending cap, then answers the forms. A second tool pays after the person
approves the exact total in Eve, and the server checks that total, the
currency, the merchant and the cap against Crossmint's live request before
paying. The card number never reaches Bell or the model. Nothing changes
unless the Crossmint variables are set.
Add crossmint_card_checkout. At the merchant's payment step the model maps the card inputs with
exact CSS selectors and Eve asks the person to approve the amount and merchant. The server reads
the operator's order intent and refuses unless it is active, scoped to this merchant, in this
currency, with enough left, unexpired, and backed by an active Visa Intelligent Commerce or
Mastercard Agent Pay rail. It then mints a one-time card with an idempotency key per tool call and
fills it through the hosted-page path link__checkout uses. The card never leaves the fill.

Order intents take a client key with the user's JWT, so Bell signs a ten-minute operator JWT and
serves its public key at /api/crossmint/jwks. The browser privacy lock also engages when agent
cards are configured, so the browser is quarantined after payment entry.
CROSSMINT_ALLOWED_MERCHANTS and CROSSMINT_MAX_COST apply.
/crossmint/cards renders Crossmint's CrossmintAgentCardAuthorization component behind Bell's
operator credentials. It saves or selects the card, registers it, creates an order intent for one
merchant, verifies the card rail, and shows the order intent id to paste into .env.local. A root
proxy asks for the credentials. This adds @crossmint/client-sdk-react-ui 4.12.1, which carries
wallet and chain dependencies the page does not use. Drop this commit to keep the dependency tree
as it is.
@aadopii
aadopii requested a review from a team as a code owner October 28, 2026 22:59
@socket-security

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Added@​crossmint/​client-sdk-react-ui@​4.12.18810088100100

View full report

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