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

  1. A technician asks in plain words. "Line 2 needs 2 temperature-controlled soldering stations, 60-80W, under $100 each."
  2. 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.
  3. 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.
  4. 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.
  5. 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: createOrder returns the order id the agent created, onApprove triggers the capture.
  • PayPal Vault for the agent wallet: POST /v3/vault/setup-tokens for the one-time manager approval, POST /v3/vault/payment-tokens for the reusable token, then POST /v2/checkout/orders with payment_source.paypal.vault_id for merchant-initiated payment, and DELETE /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-tokens kept returning NOT_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 which vault/payment-tokens/readwrite appears 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_covered per 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/search endpoint that 404s), so the client talks to the current /v1/search API 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, with gemini-3.8-flash and gemini-3.5-flash-lite as fallbacks, via @google/genai) runs the agent's tool-use loop in src/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

Share this project:

Updates

Submission history