-
-
The model never holds a tool that spends money — payment happens in PayPal, or under limits a manager authorised once.
-
A technician writes what the line needs; the agent turns it into must-have specs and searches live retail listings.
-
Every line is checked against the spend policy in code — 6/6 passed, so the requisition is routed to the Plant Manager.
-
The approver pays inside PayPal Checkout. The agent never holds a payment tool.
-
Under $50 and clean on every check, so the agent pays from the PayPal Vault wallet a manager authorised once — no approval step.
-
$2,685 of cordless drills: over the per-order limit and the monthly budget, so the agent declines and recommends a formal RFQ.
-
Every PO carries its PayPal capture id, and the cost center's budget moves with it.
The problem
In a small plant, the small purchases are the messy ones. A soldering station for rework, ESD mats for the QA bench, ear muffs for new operators. Nobody runs a tender for a $70 tool, so these buys happen through whoever has a card: screenshots in a group chat for approval, no comparison between listings, no record against the department's budget until month end. It is slow for the technician and invisible to the manager, at the same time.
I run a team that lives on this paperwork — purchase orders, quotations, work instruction sheets — so this is the job I wanted to hand to an agent. But handing purchasing to an agent is exactly where "the AI did something clever" becomes "the AI spent money it should not have".
What it does
- A technician asks in plain words. "Line 2 needs 2 temperature-controlled soldering stations, 60-80W, under $100 each."
- The agent shops. It turns the request into must-have specs, then searches live retail listings through Channel3 and compares them on spec, price, stock and retailer.
- The policy decides, in code. Order limit, unit-price cap, approved retailers, restricted items, the cost center's remaining monthly budget, and a duplicate-purchase check. The agent can dry-run the policy, but it cannot talk its way past it.
- A person pays in PayPal. The agent creates a PayPal order and routes it to the approver for that amount (Line Supervisor up to $150, Plant Manager up to $600). The approver sees the request, the chosen listing, what else was compared, the must-haves it was matched against, and every policy check — then pays with PayPal Checkout, or rejects. For small buys, a manager can authorize an agent wallet once in PayPal: after that, orders up to $50 that pass every check with no warnings (at most $150 a day) are paid by the agent straight away. The manager can revoke it at PayPal at any time.
- A purchase order is recorded. The capture is confirmed with PayPal, a PO number is issued, and the cost center's budget moves.
And when it should not buy, it does not: ask for "15 cordless drills, the best ones" and it declines, naming the two rules that fail, and suggests raising an RFQ instead.
The design idea: the model never holds a "pay" tool
The agent's tools are search_catalog, check_policy, submit_requisition and decline_request.
There is no tool that moves money. submit_requisition re-runs the whole policy in server code and
only then creates the PayPal order. Payment happens in one of two places, and the model chooses
neither of them: a human approves it inside PayPal Checkout, or — under the wallet limits a manager
authorized at PayPal beforehand — server code pays with the vaulted token.
That is what makes a fast agent safe here. The LLM is good at the judgement that is genuinely hard (is this listing actually the right thing for an indoor operator?) and is kept away from the one decision a company can never delegate to a probability distribution.
The same principle shows up in the duplicate check. If the same cost center already bought a similar item in the last 14 days, the order only goes through when the request itself says why another one is needed. Early on the agent simply invented the reason ("Budi confirmed the old one failed"), so now the server requires the stated reason to be quoted word for word from the technician's own text, and checks it. The approver sees the warning and the quote.
How I built it
- Node.js, no framework: a plain HTTP server with server-sent events, so the approver watches the agent's searches and policy checks happen live.
- PayPal Agent Toolkit (
@paypal/agent-toolkit) for every server-side PayPal call — the same tool handlers an LLM would be given:create_order,pay_order,get_order. - PayPal JavaScript SDK on the approver's screen:
createOrderreturns the order id the agent created,onApprovetriggers the capture. - PayPal Vault for the agent wallet:
POST /v3/vault/setup-tokensfor the one-time manager approval,POST /v3/vault/payment-tokensfor the reusable token, thenPOST /v2/checkout/orderswithpayment_source.paypal.vault_idfor merchant-initiated payment, andDELETE /v3/vault/payment-tokens/{id}to revoke. - Google Gemini runs the tool-use loop.
- Channel3 provides live product data across 25,000+ retailers.
- Google Cloud Run hosts it, with the ledger on a Cloud Storage volume and the keys in Secret Manager.
Everything runs against the PayPal sandbox, so no real money moves.
Challenges
- The vault scope was not granted.
POST /v3/vault/setup-tokenskept returningNOT_AUTHORIZED; the app's token only had the v1 credit-card and billing-agreement scopes. The fix was not in code at all — "Save payment methods" has to be enabled on the sandbox app's features, after whichvault/payment-tokens/readwriteappears in the token. - The agent bought the wrong right thing. Asked for safety glasses for indoor operators, it picked dark safety sunglasses — cheap, in stock, from an approved retailer, and useless. So the agent now derives must-haves from the stated use (indoor means clear lenses, a bench tool should be ESD-safe), rules out listings that contradict them, and shows the approver the must-haves it matched.
- Pack sizes. "10 pairs" is not 10 boxes of 12. The agent now has to state
units_coveredper line, and the policy checks the arithmetic. - A vague request could burn the whole search budget, so searches are capped at 4 per request and restricted to approved retailers via Channel3 website ids.
- The published Channel3 SDK is stale (it calls a
/v0/searchendpoint that 404s), so the client talks to the current/v1/searchAPI directly. - The sandbox login trap, which cost me an hour: PayPal Checkout in the demo must be approved with a sandbox Personal account. Logging in with the sandbox Business account (the seller) just says the information is incorrect.
What I learned
That the interesting engineering in agentic commerce is not the payment call — the Agent Toolkit makes that a one-liner. It is deciding which decisions the model is allowed to make, and then enforcing that boundary somewhere the model cannot reach. Every guardrail in this project started as the agent doing something plausible and wrong.
What's next
- Pay the supplier, not a sandbox merchant: PayPal Payouts to vendors who accept PayPal, so a PO becomes a real settlement.
- Post-purchase: shipment tracking and delivery confirmation against the PO, via the toolkit's shipment tools.
- Real roles: the approver tier tied to the signed-in company account instead of a tab.
- Quotation mode for the buys that are too big for a card: the agent assembles the RFQ it currently only recommends.
AI tools used, and how
AI inside the product
- Google Gemini (
gemini-3.5-flash, withgemini-3.8-flashandgemini-3.5-flash-liteas fallbacks, via@google/genai) runs the agent's tool-use loop insrc/agent.mjs: it turns a plain request into must-have specs, chooses search terms, rules out listings that contradict the stated use, works out pack sizes, adapts when a policy check fails, and writes the justification and the "also compared" notes the approver reads. - Channel3 AI product search supplies the live listings (titles, prices, stock, retailer, buy links) the agent compares.
- The app also ships a non-AI fallback playbook, so the PayPal flow can be tested without a Gemini key.
AI used to build it
- Claude (Anthropic), via Claude Code was my pair programmer for the implementation: scaffolding the server and UI, writing the PayPal and Channel3 clients, and debugging the sandbox issues above. The idea, the product decisions, the guardrail design, the policy rules and all the testing are mine.
Built With
- channel3
- google-cloud
- google-cloud-run
- google-gemini
- html
- javascript
- node.js
- paypal
- paypal-agent-toolkit
- paypal-javascript-sdk
- paypal-rest-api
- paypal-vault
- server-sent-events
Log in or sign up for Devpost to join the conversation.