Inspiration

At a busy food stall, "payment" often means a customer holding up a phone that says You sent $21.00 · Completed, then walking off with the food. A screenshot like that takes a minute to fake, and the seller only finds out at closing time when the cash doesn't add up.

Real online payments would fix that, but for a stall that runs on a chalkboard, setting them up means merchant forms, product catalogs and typed invoices. So most small sellers (market stalls, food trucks, tutors, small workshops) stay on cash and screenshots. I wanted the paper they already have to be the setup.

What it does

Paperstall turns a photo of paper into a verified PayPal checkout.

  1. Snap it. The seller photographs a chalkboard menu, a handwritten bill or a lessons notice (or draws one).
  2. The Read. Gemini reads every item, price and position on the paper, translates it (a Spanish menu becomes an English storefront), and decides which PayPal rail the paper needs:
    • a price list → a storefront with PayPal Checkout (Orders v2)
    • a bill for one customer → a PayPal invoice with a QR code (Invoicing v2)
    • "$120 a month" → a PayPal subscription plan (Catalog + Subscriptions)
  3. Sell. Customers scan the stall's QR poster, can just type what they want ("two al pastor and a horchata, no onions") to an AI concierge that builds the cart from the stall's own items, and pay with PayPal.
  4. Verify-to-record. A sale only reaches the seller's live counter after Paperstall asks PayPal itself: is this order paid, for the right amount, to this seller, and not counted before? The pickup code on the buyer's receipt matches the seller's screen. A forged screenshot's transaction ID simply doesn't exist at PayPal, so it's rejected.
  5. Penny, the AI clerk. The seller can ask "who hasn't paid?" or "refund one horchata from the last order". Penny uses Gemini function calling over PayPal tools, but anything that moves money becomes an Approval Card showing the exact PayPal endpoint. Nothing runs until the seller presses Approve.

Every PayPal call is logged with its debug_id in an in-app Receipts drawer. Everything runs in the PayPal sandbox.

How we built it

  • Next.js 16 + React 19 + TypeScript, Tailwind and shadcn/ui, deployed on Vercel.
  • PayPal: Orders v2 (create, capture, get, track), Payments v2 refunds, Invoicing v2 (create, send, generate QR, remind, cancel), Catalog Products + Subscriptions, and the JS SDK v6 buttons. Every POST sends a PayPal-Request-Id; capture and refund use deterministic IDs so retries are idempotent.
  • Gemini via @google/genai: a vision model for the Read with a zod schema sent as structured output (items, 0–1000 bounding boxes, language, currency, tax, intent, clarifying questions), and a fast model for the concierge, an acceptable-use check, and Penny's function-calling loop.
  • AI is checked by code. Money is integer cents. The math check flags a bill when $$\sum_i q_i \, p_i \neq T_{\text{written}}$$ and the invoice uses the correct sum. Penny's money-moving tools have no executor at all; they can only return proposals.
  • No database. A published stall is a signed, compressed URL payload (edit a price in the link and checkout refuses it); the ledger lives on the seller's device. The live counter hears new orders through a sealed ntfy.sh topic and re-checks each one with PayPal.
  • Channel3 supplies market prices for branded items, so a typo like "$4.90" for a $49 speaker gets caught.
  • An OpenAPI spec, a docs page and a generated Postman collection make the API easy to integrate.

Challenges we ran into

  • Trusting "paid". The hard part wasn't taking a payment; it was proving one. Verify-to-record had to reject unpaid, wrong-amount, wrong-payee and replayed orders, and work across devices without counting a sale twice.
  • PayPal details: a newly enabled Invoicing feature only appeared after terminating the cached access token; the invoice QR comes back in a multipart body that I had to parse carefully; the default sandbox accounts were in India, so I created a US business and personal pair for USD checkout.
  • Gemini details: structured output rejected some JSON Schema keywords, Gemini 3 function calling needs the model's own turn echoed back between tool steps, and the free tier has a small daily cap, so the app falls back to a second model and says so plainly.
  • Agent safety: keeping Penny useful while making sure prompt text, uploaded images or a confused model can never move money without the seller.

Accomplishments that we're proud of

  • The AI doesn't just read text; it chooses the PayPal product a piece of paper needs.
  • A screenshot is not a payment in Paperstall: every counted sale is confirmed by PayPal first, and the demo shows a forged ID being rejected.
  • A Spanish chalkboard becomes an English storefront taking PayPal payments in under a minute.
  • An agent that can refund, remind and ship, with an approval gate that's enforced in code, not just in the prompt.
  • Nothing is faked: every PayPal object in the demo is real, with debug IDs to prove it.

What we learned

  • How Orders, Invoicing and Subscriptions fit different kinds of selling, and why verifying an order server-side matters more than any client-side "success" screen.
  • Structured output plus a strict schema makes vision models usable for money, as long as code re-checks the numbers.
  • For agents that touch payments, the safest design is one where the model can't execute: it proposes, and a human approves.

What's next for PaperStall

  • Real sellers: PayPal partner onboarding so a seller can connect their own account in a few taps.
  • Sharing the stall link on WhatsApp, and offline queueing for markets with bad signal.
  • Multi-device ledger sync and a self-hosted doorbell for busy stalls.
  • More paper types: event tickets, deposits, and split bills.

Built With

  • channel3
  • claude
  • gemini-api
  • google-gemini
  • jsqr
  • next.js
  • node.js
  • ntfy
  • openapi
  • paypal
  • paypal-invoicing-api
  • paypal-js-sdk
  • paypal-orders-api
  • paypal-subscriptions-api
  • postman
  • qrcode
  • react
  • shadcn-ui
  • tailwind-css
  • tanstack-table
  • typescript
  • vercel
  • vitest
  • zod
  • zustand
Share this project:

Updates

Submission history