Inspiration

Every Monday, independent café owners face the same problem. Run out of espresso beans or barista oat milk during the 7:30 AM rush and service stops. Reordering means hours of comparing supplier sites. And if you hand an AI agent a credit card, it can hallucinate quantities, mistake an $8.99 scoop for a 5 lb wholesale bag, or reorder $838.50 of oat milk that is already on the delivery truck.

We built RestockPilot around a strict separation of powers:

AI proposes, deterministic code decides, the owner approves, and PayPal verifies.

What it does

RestockPilot automates café replenishment without letting the AI touch the money.

  • Net-inventory math: Instead of a naive "on hand below reorder point" trigger, it uses periodic-review (s, S) logic: $$\text{Position} = \text{onHand} + \text{onOrder} - \text{committed}$$ Quantities round up to supplier case packs and minimum order quantities. In our seeded run, Oatly Barista Milk has 12 cases in transit, net position 24 beats the reorder point of 19.5, so it orders 0 cases and prevents $838.50 of duplicate spend.
  • Live supplier discovery (Channel3): Searches real merchant catalogs for café SKUs and compares offers across merchants.
  • Anti-poisoning peer median: Raw search returns scoops and 12 oz retail bags. We filter to sameProduct && inStock before computing the median. This moves the median from a poisoned $49.00 to the true $63.25, so a suspicious $36 clearance listing is flagged for review.
  • 9-rule policy gate: Every proposal is checked against the owner's mandate. The gate never short-circuits, so a refurbished-item warning can't hide a budget breach. Verdict order is BLOCK > ASK > ALLOW.
  • Owner approval card: Shows savings versus the verified peer median (+$17.50) and the last price paid (+$28.00).
  • Two-phase idempotent PayPal Orders v2:
    • Phase A creates the order (intent: CAPTURE) with a deterministic SHA-256 PayPal-Request-Id and a custom_id contract hash.
    • Phase B does a GET /v2/checkout/orders/{id} to confirm APPROVED before capture, which avoids ORDER_NOT_APPROVED 422 errors.
  • Raw-body webhook verification: PAYMENT.CAPTURE.COMPLETED events are verified from the raw body (CRC32 plus an RSA-SHA256 fallback for the Webhook Simulator), then re-read with a fresh GET.
  • AG Grid ledger: Quick filters, status badges, idempotency keys, and a pinned totals row for approved spend, savings, and remaining budget.
  • Read-only MCP server (/api/mcp): External agents can read inventory, mandates and quotes. It has zero money-moving tools.
  • Verifiable receipts: Ed25519-signed dockets that anyone can verify, with a live tamper test.
  • Grounded, privacy-safe AI: Owner PII is masked, merchant text is treated as untrusted data, and AI rankings must trace to real sourced offers or fall back to deterministic ranking.

How we built it

  • App: Next.js 16 (App Router), React 19, strict TypeScript, Tailwind CSS 4
  • Payments: PayPal Orders v2, Webhooks, Payments v2 refunds, PayPal Server SDK, with a hard guard that only allows api-m.sandbox.paypal.com
  • Catalog: Channel3 API
  • Ledger UI: AG Grid Community
  • AI: Google Gemini, with Groq and deterministic parsing as fallbacks, plus prompt-injection fences around merchant text
  • Hosting: Render
  • Quality: 160+ Vitest tests and a 24-case adversarial benchmark (npm run eval)

Challenges we ran into

  1. Webhook signatures in Next.js: Parsing the body and calling JSON.stringify changes key order and number formatting (12.50 becomes 12.5), which breaks PayPal's CRC32 check. We read the raw body once with req.text() and splice it into the verification payload.
  2. Catalog poisoning: Cheap accessories dragged the peer median down to $49.00 and let a $36 trap pass as ALLOW. Filtering to the same product, in stock, fixed it.
  3. Short-circuit masking: An early ASK hid a later budget BLOCK. We replaced the if/else chain with a rule lattice that evaluates all 9 rules every time.
  4. Premature capture: Capturing before buyer approval returns 422. A mandatory pre-capture GET check solves it.

Accomplishments we're proud of

  • 24/24 on our adversarial benchmark (Wilson 95% lower bound 86.2%) with $0.00 unsafe spend. A naive agent would have spent $7,010.49 unsafely.
  • $838.50 duplicate order prevented on the oat milk.
  • The AI and MCP server have no payment tools. Only the owner can approve a payment.
  • 160+ passing tests and strict TypeScript.

What we learned

  • Language models are great at parsing "keep a $500 buffer for milk," but money logic must be deterministic code.
  • Idempotency keys are essential when network retries and delayed webhooks are normal.
  • Read-backs from PayPal's own API build more trust than anything the app says about itself.

Honest limits

  • Sandbox only. The UI labels SIMULATED mode when credentials are missing.
  • The /evidence order IDs are seeded demo data, not live captures.
  • Refunds and the scheduled morning run are tested against a mocked PayPal.
  • The data store is in memory, and the benchmark uses our own fixtures.

What's next

  • Multi-location café groups with regional budgets
  • Real supplier checkout and EDI after PayPal capture
  • Demand-aware safety stock using weather and foot traffic

Built With

  • ag-grid
  • channel3
  • ed25519
  • google-gemini
  • groq
  • model-context-protocol
  • next.js
  • node.js
  • paypal-orders-v2
  • paypal-sandbox
  • paypal-server-sdk
  • paypal-webhooks
  • python
  • react
  • render
  • tailwind-css
  • typescript
  • vitest
Share this project:

Updates

Submission history