Inspiration

Ordering medicine still means someone reads a handwritten or printed prescription, looks up each item, checks stock and takes payment. AI can do most of that busywork, and PayPal's Agent Toolkit lets an agent create the payment. But in healthcare a professional has to stay in control, and an agent must never be able to change what is charged. I wanted to show a pattern for agentic commerce with a hard human-approval gate: the AI does the work, a pharmacist approves, and PayPal only charges for exactly what was approved.

What it does

Rx Cart Agent is an open-source prototype built entirely on synthetic data: fictional medicines, prices, pharmacy and prescriptions. The three bundled sample prescriptions are watermarked "SYNTHETIC – NOT A REAL PRESCRIPTION".

  1. Upload: the patient uploads a prescription image or picks a bundled sample.
  2. AI extraction: Google Gemini (default gemini-3.5-flash, through the Vercel AI SDK and @ai-sdk/google) transcribes medicine names and quantities into a typed zod schema. The prompt treats the image as untrusted, ignores non-medicine text and forbids inference, correction, substitution and clinical advice. Names and quantities are sanitized to cart limits.
  3. Review: the patient can edit anything the AI misread. Any edit marks the cart stale until it is rebuilt.
  4. Exact-match cart: items are matched to the pharmacy catalog by exact normalized name. Unknown and out-of-stock items are flagged and never substituted. Totals are always recomputed on the server, and the server returns a hash of the reviewed cart.
  5. Pharmacist gate: a pharmacist confirms the cart with a PIN. The server checks it before any PayPal order can be created. Each order is bound to the reviewed cart's hash; a different cart is refused (HTTP 409).
  6. Pay with PayPal, two ways:
    • In-page PayPal JS SDK v5 Buttons (popup). The SDK is loaded at runtime with the sandbox client ID served by /api/config (never the secret). createOrder calls our server (PIN and cart-hash checks); onApprove captures. A redirect approve flow (PayPal → /return → Capture) is kept as a fallback.
    • Agent mode: an AI SDK agent calls two tools, build_cart and then create_paypal_order. The second wraps the PayPal Agent Toolkit create_order (configured for orders.create only, at most one order per run) behind the pharmacist gate. The tool inputs carry no amounts and no approval flag, so the model cannot set prices or skip the gate. The UI shows the agent's tool-call trace and the PayPal approve link.
  7. Capture and receipt: capture requires a server-signed approval token bound to that order ID and cart hash (15-minute expiry, single use). The receipt shows the PayPal status (COMPLETED).

Verified end to end in the PayPal sandbox (sandbox order IDs):

  • 2XT9160042656350H: $7.00
  • 5S6597813N105671X: $11.75, redirect approve flow
  • 3J7034079R5590630: $11.75, in-page Buttons popup, with live Gemini extraction

A mock mode (no keys at all) runs the whole flow locally, so judges can try it in minutes. Real Gemini and the PayPal sandbox are switched on with environment variables (see the README).

How I built it

  • Next.js (App Router), React, TypeScript, server-side route handlers: /api/extract, /api/cart, /api/pharmacist/verify, /api/paypal/create-order, /api/paypal/capture-order, /api/agent, /api/config.
  • AI: Vercel AI SDK v7. Extraction uses generateText + Output.object with a zod schema and the image as a file part. Agent mode uses generateText with tools.
  • PayPal: Orders v2 REST (create/capture in the sandbox, with experience_context return/cancel URLs, PAY_NOW, no shipping), the JavaScript SDK v5 Buttons, and Agent Toolkit 1.11.0 (@paypal/agent-toolkit/ai-sdk).
  • Validation and errors: every input is validated with zod (quantities are positive integers ≤ 999). Errors are structured JSON: 400 for bad input, 409 for cart mismatch, 429 for PIN lockout, 502 for PayPal or AI provider failures, and 503 with a reason and Retry-After when no AI model is available.
  • Tests: 171 vitest tests in 18 files cover matching, cart validation, the PIN gate and rate limit, approval tokens, PayPal errors, the SDK loader, Gemini fallback and timeouts, the agent gate and the routes. scripts/security-demo.sh demonstrates PIN lockout, the cart-hash 409 and a forged approval token being rejected (403) against a running server.

Challenges I ran into

  • Keeping the human in control of an agent that can pay: the gate is enforced on the server, not in the prompt, and the agent tool cannot take an amount or an approval as input. The agent creates at most one order per run.
  • Charging exactly what was reviewed: orders are tied to a cart hash, and capture needs an HMAC-signed, single-use token for that order and that cart. MOCK- order IDs are only accepted in mock mode.
  • Brute-forcing a short PIN: the PIN is compared as hashes with constant-time comparison. A lockout applies after 5 wrong tries per client, with a 50-failure global cap per 10 minutes (HTTP 429).
  • Free-tier AI reliability: Gemini's free tier can return 429 or 503. Each attempt has a hard 20s timeout, and one extraction has a 35s total budget. The app retries a 503 once, then falls back to other Gemini models (gemini-3-flash-preview, gemini-3.5-flash-lite) and reports which model answered. If every model fails, the UI offers a clearly labeled demo extraction for the bundled samples (no AI call).
  • PayPal approval loops: without a return_url, sandbox approval could loop back to the payment screen. Every order now sets experience_context return/cancel URLs.
  • Dependency hygiene: the Agent Toolkit pulls in old transitive dependencies. These are pinned with npm overrides; npm audit reports 0 vulnerabilities, and the toolkit still loads and creates orders.

Accomplishments that I'm proud of

  • Real sandbox payments completed through both the in-page Buttons popup and the redirect flow, including one run with live Gemini extraction.
  • An agent that uses the PayPal Agent Toolkit but cannot set prices, skip the pharmacist or create a second order.
  • The full flow runs with zero keys in mock mode, and 171 tests pass.

What I learned

How to combine the PayPal Agent Toolkit with the Vercel AI SDK; that safety controls for agentic payments belong in server code and data binding (cart hashes, signed tokens), not in the prompt; and how to design around free-tier model limits with timeouts, fallbacks and an honest demo fallback.

What's next for Rx Cart Agent

Persistent storage for rate limits and capture records (they are in memory in this prototype), real pharmacist accounts instead of a demo PIN, and order lookup and refunds through more Agent Toolkit tools behind the same gate. Any use with real patients would first need clinical, legal and regulatory review.

Scope and disclaimers

  • Synthetic data only. No real patients, prescriptions, medicines, prices or payments (PayPal sandbox only).
  • The app gives no clinical, dosing or substitution advice. Unmatched or out-of-stock items are flagged, never swapped.
  • This is an independent hackathon prototype. It is not production pharmacy software and contains no code from any other product.

Built With

Share this project:

Updates

Submission history