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 now carries the setup page, the operator token and the Crossmint client. Review the last commit.

#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 the budget the user approved on the setup page 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. Rewards, statement lines and disputes work as for any purchase.

Opt-in

It is off until CROSSMINT_CARD_CHECKOUT=1, and needs LINK_SANDBOX=0 and DATABASE_URL for the privacy guard. The setup keys alone only approve budgets for Agent Checkouts, so setting them up never adds a second way to pay or the database requirement. With the card path on, the setup page offers Verify card for a budget whose network rail is pending.

Guards

Payment runs through Eve's approval gate. 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 rail is active, the balance covers the amount, it names this merchant, and it has not expired. It mints only while the browser lock is held, with an idempotency key, so a retry never issues a second card. Any failure after a mint answers a fixed "do not retry".

The credential is read inside the fill and never returned. After payment entry the browser is quarantined from further model access, as with Link.

Tests

419 pass on Node 24 with typecheck, lint and the build. The card tests are mock-only: one case per refusal reason, the mint request and its idempotency key, the two 409 codes an operator must act on, the opt-in, the refusal without the browser lock, and that no credential value reaches output. The staging smoke steps are in docs/crossmint.md and have not been run.

🤖 Generated with Claude Code

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.
@aadopii
aadopii requested a review from a team as a code owner October 28, 2026 22:59
@socket-security

socket-security Bot commented Oct 28, 2026 •

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

aadopii and others added 2 commits November 2, 2026 14:46
Agent Checkouts could not pay on its own: the order intent it needs takes a client key with the
user's JWT, and only the card branch could sign one. Move the operator token, the JWKS route, the
operator-only proxy, and the Crossmint React SDK here, and add /crossmint/setup, an operator page in
Crossmint's styling. It checks the connection live and shows the JWKS endpoint and issuer to paste
when Crossmint rejects Bell's token, saves delivery details as a buyer profile, lists budgets with
what is left and their card rails, cancels them, and approves new ones in Crossmint's card form
after an explicit confirmation, with one idempotency key per draft. It registers the card with the
operator's email before creating the order intent, because Crossmint refuses an empty registration
and its card component sends one. A card without a Visa or Mastercard agent rail still pays through
Crossmint's encrypted card, and the page says so. The proxy forbids framing, and every action checks
the operator's credentials and origin again.

While a run works, crossmint_checkout returns at each new stage, a fixed phrase chosen from
Crossmint's activity kinds, so Bell tells the person what is happening instead of showing "Working"
for minutes. Crossmint's own progress text is never passed on, since it can name the buyer and is
written while reading merchant pages. Progress reads stay within the wait and stop on cancel.

The approval names the payee in plain words, "Pay 16.90 EUR at Zara (payments.zara.com) with your
saved card?", and the server cross-check now also refuses a store name that is not Crossmint's.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Add crossmint_card_checkout on top of the Agent Checkouts setup. 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 at
the merchant's host. 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 mints a one-time card only while the browser
privacy lock is held, with an idempotency key per tool call, and fills it through the hosted-page
path link__checkout uses. The card never leaves the fill, the browser is quarantined after payment
entry, and any failure after a mint answers a fixed "do not retry" so no second card is issued.

Paying in Bell's own browser is opt-in with CROSSMINT_CARD_CHECKOUT=1. Until then the tool answers
not_configured, and the setup keys alone only approve budgets for Agent Checkouts, so they never add
a second way to pay or the database requirement for every browser tool. The card module builds on
the shared operator config and request helper. With the card path on, the setup page offers Verify
card for a budget whose Visa or Mastercard rail is pending, through Crossmint's verification
component, and points to it after approving a budget.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@aadopii
aadopii force-pushed the feat/crossmint-agent-cards branch from 1b43c0b to a9305bc Compare November 2, 2026 14:08
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