A post went viral on X: "there should be an app where you upload your bills and bored billionaires swipe right to pay them."
Everyone joked. I wanted to see how fast I could ship a real one. So here's Pay My Mortgage ๐ , running on Helpeasant, an open-source, one-page "joke-but-real" payoff site.
Yes, it's a joke. Yes, the button works.
๐ Live: helpeasant.com
๐ Repo: github.com/mariache/helpeasant
What it does
- Live progress counter (% only, 4 decimals, so even $5 visibly moves it)
- Quick buttons: โ $5 ยท ๐ $20 ยท ๐ $100, plus any amount
- A "Pay it all off ๐คฏ" button that politely explains card limits
- A Wall of Weirdos for supporters, with emoji tiers instead of amounts
- Milestones with confetti: "Paid for a doorknob" โ "The garage is basically mine"
- "Can't chip in? Send it to Elon ๐" share button
- Installable PWA, dark mode
Stack
- Next.js 15 (App Router), one page and three API routes
- Stripe Checkout + a signed webhook
- Supabase (Postgres), server-side only
- Vercel for hosting, Cloudflare for DNS
Design decisions I liked
1. The counter only trusts webhooks
The UI never updates the total on a button click. The flow is:
-
POST /api/checkoutcreates a Stripe Checkout Session with inlineprice_data. No products are needed in the dashboard. - Stripe hosts the payment page.
-
POST /api/webhookverifies the signature and inserts a row only ifpayment_status === "paid".
Inserts are idempotent on the Stripe session ID, so webhook retries never double-count:
await supabase.from("donations").upsert(
{ stripe_session_id: session.id, amount_cents: session.amount_total, name, message },
{ onConflict: "stripe_session_id", ignoreDuplicates: true }
);
2. Privacy by design
The real balance lives in a server-only env var. The browser only ever gets a rounded percentage, and the wall shows tiers (โ ๐ ๐๏ธ ๐ ) instead of amounts. Otherwise total รท % would reveal the balance.
3. A locked-down database
- Row-level security on, "auto-expose new tables" off
- Explicit grants to
service_roleonly - The client never talks to Supabase directly; everything goes through API routes
4. Config over code
Title, emoji, tagline, owner name and city are all NEXT_PUBLIC_* env vars. A fork is a new campaign with zero code changes: Pay My Car Loan, Pay My Student Debt, you name it.
5. Native share with a fallback
navigator.share on mobile opens WhatsApp, Messages and so on. On desktop it falls back to an X intent with @elonmusk pre-filled.
What actually took the time
The code took maybe an hour. Most of the evening went into setup:
- Domain + Cloudflare DNS (tip: set records to DNS only, grey cloud, or Vercel complains about the proxy)
- Stripe verification (it needs a live, reachable site that matches your business name)
- Test-mode end-to-end run with
stripe listenand the4242card before switching to live keys
Fork it
git clone https://github.com/mariache/helpeasant
cd helpeasant && npm install
cp .env.example .env.local
npm run dev
The README walks through Supabase, Stripe and Vercel setup in about 15 minutes.
Feedback welcome, especially on abuse and fraud edge cases I haven't covered. And if you can't chip in... you know who to send it to. ๐

Top comments (0)