MeterDemo: usage-based billing for an AI product, built on Stripe
Live: https://meterdemo.vercel.app · Sample customers · Pricing lab · Solution brief
AI products have a pricing problem: every reply costs money to generate, so on a flat plan the heaviest users can cost more than they pay. MeterDemo shows the fix on Stripe. It meters every AI reply, prices input and output tokens separately (output costs the AI provider about 5x more), and makes sure no usage goes unbilled.
TEST MODE ONLY. Use card 4242 4242 4242 4242, any future date, any CVC. No real money moves, and the app refuses to start with a live Stripe key. Customers in the samples are fictional.
Problem → solution → result
| |
|---|
| Problem | On a flat $49/month plan, the sample power user costs $90.03 a month in AI usage: a −88% gross margin. Light users pay $49 for $0.34 of usage and leave. |
| Analysis | The pricing lab runs real metered usage from three customer types through four pricing models and shows revenue, AI cost, Stripe fees and gross margin for each. |
| Solution | Two Stripe Billing Meters (input, output), each with a graduated price and a free allowance, found by lookup key. Checkout, invoice preview, Customer Portal, signed webhooks. |
| Safeguard | Usage reports retry with the same ID (never double-billed); failures are recorded and a reconcile job compares our records with Stripe's meters. |
| Result | Every paying customer at 47–49% gross margin; the lab's v2 bills match Stripe's real invoices to the cent. |
The full write-up: docs/SOLUTION_BRIEF.md. How to present it: docs/TALK_TRACK.md. Terms explained: docs/GLOSSARY.md.
How a reply becomes an invoice
Customer chats
│
▼
/api/chat ── AI model ──► reply + input & output token counts
│
▼ lib/usage-reporting.js: retry, idempotent IDs, record anything unreported
Stripe Billing Meters: ai_input_tokens ai_output_tokens (sum per month)
│
▼
Graduated prices: input: 2,000 free, then $0.25/1K output: 500 free, then $1.00/1K
│
▼
Subscription (from Checkout) ──► month end: invoice ──► card charged
│ │
│ /api/usage: meter totals + Stripe invoice preview ▼
└──────────────────────────────── /api/webhook ◄── invoice.paid / payment_failed
npm run reconcile: our reported totals vs. Stripe's meters; re-sends anything missing
- Meters count usage. Each AI reply sends two meter events (input tokens, output tokens) for that customer.
- Prices turn meter totals into money. Both are graduated: a free first tier, then a per-token rate. Output is priced higher because it costs more.
- Lookup keys (
meterdemo_input_tokens_v2, meterdemo_output_tokens_v2) let the app find the current prices, so pricing changes in Stripe need no deploy.
- Subscription from Stripe Checkout holds both prices, billed monthly in arrears.
- Invoice preview gives the customer Stripe's own estimate; the app never does its own billing math.
- Invoice at month end; Stripe charges the card and sends a webhook.
There's no database. Stripe holds the customers, subscriptions, usage and invoices; the browser keeps a signed, HTTP-only cookie with its Stripe customer ID.
Scale note: real AI prices are fractions of a cent, so every price and every cost is scaled up 1,000x to make test invoices visible. Both sides use the same scale, so margins are realistic.
Pages
| Page | What it shows |
|---|
/ | The problem, the solution and the safeguard; start the live demo |
/chat.html | Live demo: chat, input/output usage, Stripe's estimate, Manage billing (Customer Portal) |
/sample.html | Three fictional customers (light, growing, power) with paid invoices, daily usage and per-invoice margin: revenue, AI cost, the card fee Stripe actually charged, gross profit |
/pricing.html | The pricing lab: four pricing models on each customer's real usage, with findings |
This is the beginning of the README. Read the rest at the original. Read the full README ↗