An AI shopping agent that does the legwork and leaves the payment to you.
You type what you want ("cheapest frozen food bundle under $30, no pork"). A Gemini agent searches the catalog, compares options and builds a draft cart. You review it and tap Confirm, then approve the payment in PayPal. The agent never touches your money.
Built for the Build What's Next with PayPal and AI hackathon. PayPal runs in the sandbox only: no real money moves, and the code has no path to the live API.
| Step | Who acts | What is enforced |
|---|---|---|
| 1. Request | You | Message length and session validated |
| 2. Propose a cart | Gemini, via three tools | The agent can call only search_products, compare_options and propose_cart. There is no payment tool. The server prices every line from the catalog, rejects bad ids and out-of-stock items, and blocks carts above your spending limit. |
| 3. Confirm | You | Only POST /api/carts/:id/confirm can create a PayPal order. It re-prices the cart, checks ownership of the session, and checks the limit again. |
| 4. Approve | You, inside PayPal | PayPal's own checkout page |
| 5. Capture | Backend | Runs only when PayPal redirects back for an order the backend created, only once, and the captured amount must equal the cart total |
Two human checkpoints sit between a request and a charge. A wrong or manipulated price from the model cannot reach checkout, because totals never come from model output.
Flutter app ──► Node/Express backend (Render) ──► Gemini (function calling)
│
└──► PayPal sandbox (Orders v2: create, capture, webhook)
backend/Express server, Gemini agent loop, PayPal client, testsapp/Flutter chat app (cart card, Confirm button, PayPal approval, live status)render.yamlone-click backend deploy
cd backend
npm install
cp .env.example .env # then fill it in (see below)
export $(grep -v '^#' .env | xargs) # or set the variables however you prefer
npm startThe server reads real environment variables; it does not load .env by itself. Without GEMINI_API_KEY it runs a small offline demo agent (rule-based, same tools) so you can try the flow, and without PayPal credentials chat works but checkout returns 503. For the real project, set both.
PayPal sandbox credentials
- Log in to the PayPal Developer Dashboard and open Apps & Credentials in Sandbox mode.
- Create an app and copy the Client ID and Secret into
PAYPAL_CLIENT_IDandPAYPAL_CLIENT_SECRET. - Under Testing Tools > Sandbox Accounts, use the default Personal (buyer) account to approve payments. Its email and password are shown there.
Gemini key: create one at Google AI Studio and set GEMINI_API_KEY. GEMINI_MODEL defaults to gemini-2.5-flash.
PUBLIC_BASE_URL must be reachable by your browser, because PayPal redirects the buyer back to /paypal/return on it. Locally that is http://localhost:3000.
The repo contains the Dart sources, not the generated platform folders. Generate them once:
cd app
flutter create . --project-name agentcart --org tech.monam
flutter pub get
flutter run # pick a device, or: flutter run -d chromePlatform notes:
- Android emulator reaches your machine at
http://10.0.2.2:3000(the default). Inandroid/app/src/main/AndroidManifest.xmladdandroid:usesCleartextTraffic="true"to<application>for local http, and add this inside<manifest>so links can open:For release builds also add<queries> <intent><action android:name="android.intent.action.VIEW"/><data android:scheme="https"/></intent> </queries>
<uses-permission android:name="android.permission.INTERNET"/>. - Web / desktop / iOS simulator use
http://localhost:3000. - Physical phone: open the in-app settings (sliders icon) and set the backend URL to your deployed Render URL or your computer's LAN address.
- Push this repo to GitHub and create a new Blueprint on Render pointing at it (it reads
render.yaml). - Set
PAYPAL_CLIENT_ID,PAYPAL_CLIENT_SECRET,GEMINI_API_KEYandPUBLIC_BASE_URL(yourhttps://<service>.onrender.comURL). - Free instances sleep when idle; open
/healtha minute before a demo.
Optional webhook: add a PayPal webhook for PAYMENT.CAPTURE.COMPLETED pointing to <PUBLIC_BASE_URL>/api/paypal/webhook and set PAYPAL_WEBHOOK_ID. Events are accepted only after PayPal's verify-webhook-signature API returns SUCCESS.
cd backend && npm test32 tests cover pricing and shipping, the tools, the Gemini loop (with a stubbed Gemini API), PayPal request bodies (with a stubbed PayPal), and the full HTTP flow including the failure paths: unconfirmed capture, another session's cart, cancel, double capture, amount mismatch, spending limit and missing credentials. The Flutter model tests are in app/test.
| Method | Path | Purpose |
|---|---|---|
| GET | /health |
Status, which agent is active, whether PayPal is configured |
| GET | /api/catalog |
Demo catalog |
| POST | /api/chat |
{sessionId, message, spendingLimit?} returns {reply, cart, trace} |
| GET | /api/carts/:id?sessionId= |
Cart status |
| POST | /api/carts/:id/confirm |
{sessionId} creates the PayPal order, returns approvalUrl |
| GET | /paypal/return, /paypal/cancel |
PayPal redirect targets (capture happens on return) |
| POST | /api/paypal/webhook |
Verified PayPal webhook |
- Gemini (
gemini-2.5-flashby default) with function calling: interprets the request and decides which catalog tool to call. - PayPal Orders v2 API (sandbox): create order with item breakdown, buyer approval via redirect, capture, and webhook verification.
- Render: hosts the backend.
- Flutter: mobile and web client.
- State is in memory, so carts are lost on restart. Swap
backend/src/store.jsfor Firestore or Postgres to persist. - The demo catalog is fixed (
backend/src/catalog.js), including a deliberately out-of-stock item. - Sessions are random IDs created by the app. That is enough to keep one user's carts from another's in a demo, but it is not real authentication. Add Firebase Auth or similar before anything real.
- The offline agent exists for development and tests; it is not the AI feature.
MIT, see LICENSE.