Inspiration

In the stock market, nobody watches a price all day. You place a GTT ("good till triggered") or limit order, and the broker executes when the market meets your number:

$$ \text{buy} \iff p_{\text{market}} \le p_{\text{limit}} $$

Shopping has nothing like that. I'd wishlist something, get a 2 a.m. price alert, and miss the deal. Giving an AI agent my card wasn't the answer either: models hallucinate, misread variants, or get talked into things. So I built DropCart: a GTT for shopping, where an AI agent sets up the order but never decides or moves money.

What it does

  1. Browse the live Channel3 catalogue and like products.
  2. Tell the agent your price in plain English, e.g. "Track it for 2 weeks, max $338, ask me first". It fills in a mandate \( M = (p_{\max}, v, t_{\text{exp}}, \text{mode}) \): max price, variant, expiry, and auto-buy or ask first. You review and confirm it.
  3. Approve PayPal once with PayPal Vault. DropCart stores only a token id, never card or bank details.
  4. Stay inside a budget. Each tracker reserves its limit, and a new tracker is accepted only if \( p_{\max} \le \text{available} \).
  5. Auto-buy on the drop. Prices are re-checked every 6 hours, and DropCart charges PayPal only when the guard allows it, with a receipt explaining why it bought.
  6. Undo with one tap (a full PayPal refund). Every decision is logged with the rule that made it.

How we built it

  • App: Next.js 16 + React 19 on Vercel (Singapore region), Supabase Postgres via Prisma, Supabase Auth, and Row Level Security on every table.
  • Agent: Groq (gpt-oss-120b) with a Gemini Flash fallback. Its only write tool fills in a form; it has no tool that can pay, refund or confirm.
  • The guard: a pure function with 12 ordered rules and 100% branch coverage. For a live price \( p \), stock \( s \) and resolved variant \( v' \):

$$ \text{BUY} \iff t < t_{\text{exp}} \;\wedge\; v' = v \;\wedge\; s > 0 \;\wedge\; p \le p_{\max} \;\wedge\; \text{mode} = \text{AUTO} $$

Otherwise it returns SKIP, ASK or EXPIRE. It runs twice: on the scheduled check, and again on a fresh live re-check right before payment.

  • Budget, a spending cap rather than a wallet (no money is ever held):

$$ \text{available} = B - \sum \text{spent} - \sum \text{limits of open trackers} $$

  • PayPal:
    • Vault v3 (setup token → payment token), Orders v2 (merchant-initiated charge on the saved wallet), and Payments v2 refunds.
    • Every call carries an idempotency key, PayPal-Request-Id = trackerId:priceCheckId, so a retry can never charge twice.
    • The charge is refused before PayPal is even called if \( p > p_{\max} \).
    • All of the PayPal code was written with APIMatic ContextMatic, which supplied the real SDK methods and models instead of guesses.
  • Channel3: catalogue search, plus free product lookups with selected_options to get the live price of the exact variant. Checking every tracker every 6 hours costs no search quota.
  • Scheduler: GitHub Actions calls a secret-protected /api/cron/check every 6 hours.
  • Quality: 108 unit tests (Vitest), and Zod validation on every input.

Challenges we ran into

  • Vault returned 403: PayPal Vault v3 isn't available to India-based business accounts, which PayPal developer support confirmed on Discord. A sandbox merchant account in a supported country fixed it.
  • Checking generated code: ContextMatic's SDK methods and response paths were correct, but TypeScript caught two string literals where enums were required, and a card-only field placed on a PayPal-wallet credential.
  • Substituted variants: if Channel3 resolves \( v' \ne v \) (say the black one sold out), the guard skips instead of buying the wrong colour.
  • A late security catch: Supabase's public key could read every table until I enabled Row Level Security. Because the app only uses server-side Prisma, nothing broke.
  • A bug found while recording the demo: the PayPal return page could load twice and wrongly show "couldn't connect" for a connection that had worked. It's now idempotent.

Accomplishments that we're proud of

  • A real end-to-end money flow in the PayPal sandbox: vault consent, an unattended charge and a refund, all visible in the buyer's own PayPal activity.
  • An AI agent that is genuinely useful but structurally unable to spend money.
  • Every purchase decision explained in plain English, with the exact rule that made it.
  • Deployed and live, with automated price checks, tests, and a 3-minute demo of the full flow.

What we learned

The safest place for an LLM in a payment flow is upstream of a deterministic guard: the agent improves the experience, while tested code moves the money. When software spends money unattended, idempotency isn't optional.

What's next for DropCart

  • Sign-up for new users, so every shopper gets their own account and budget.
  • Email notifications for purchases, approvals needed, and refunds.
  • More currencies and stores, starting with INR for Indian merchants.
  • Price-history charts and smarter limit suggestions from the agent, based on how a product's price has moved.

Built With

Share this project:

Updates

Submission history