-
-
MeetSpot running as a macOS app (Electron). The Pro badge in the header is read from RevenueCat CustomerInfo.
-
Real commute-time check: the geometric center is rejected (two people at 38 min vs 30/35 min budgets); candidate 3 fits everyone.
-
Results: the accepted meeting point on the map, with coffee shops ranked around it.
-
Second search of the day: the free quota is used up, so the app opens the RevenueCat Paywall configured in the dashboard.
-
Lifetime purchase through RevenueCat Test Store. When it succeeds, the app reruns the search by itself.
-
The server asks RevenueCat REST whether this app user id has meetspot_pro. A failed lookup never unlocks.
-
The same customer in the RevenueCat dashboard: Lifetime purchase, meetspot_pro entitlement active.
-
App icon, 1024x1024.
TL;DR for judges
- Problem: a meeting point that looks fair on a map often isn't. The geometric midpoint can mean a 38 minute subway ride for one friend and 17 for another.
- What I built for Shipaton: MeetSpot as a macOS app with a RevenueCat business model. One free search a day. After that, a RevenueCat Paywall configured in the dashboard, a Test Store purchase, and a server-side
meetspot_proentitlement check that lifts the limit. - What the video shows: three friends with commute limits, the midpoint rejected on real transit times, the paywall, the purchase, the automatic rerun, and the Pro badge read from CustomerInfo.
- Where the code is:
desktop/(Electron app and packaging),public/meetspot_finder.html(Web SDK, paywall, Pro badge),app/payment/revenuecat.py(REST entitlement check),tests/test_revenuecat.py(12 tests).
Inspiration
Every meetup I plan turns into the same argument: who has to travel the farthest? I started MeetSpot in 2025 as an open-source web tool that finds a midpoint and ranks venues around it. Testing it with real New York addresses showed me the flaw. A straight-line midpoint ignores rivers, bridges and subway lines, so the "middle" can still be unfair.
What it does
- Everyone enters a starting point (Google Places autocomplete) and, optionally, the longest commute they will accept. Then you pick what you want: coffee, dinner, a library.
- MeetSpot computes the center plus a few nearby candidate centers, then asks the Google Routes API (
computeRouteMatrix) for real transit times from every person to every candidate. A candidate that breaks anyone's limit is rejected, and the results page shows why. In screenshot 2 the geometric center loses because two people would ride 38 minutes against limits of 30 and 35. - Venues around the accepted center are ranked on rating, popularity, distance and fit, with a map.
- The first search each day is free. The second one opens the RevenueCat Paywall. After a Test Store purchase the app reruns the search by itself, and the header switches from "Go Pro" to "MeetSpot Pro".
How I used RevenueCat
- The paywall sits at the product's natural limit. The server already answered
need_paymentonce the daily free search was used. In the macOS app that response now openspresentPaywall()from@revenuecat/purchases-js1.67.1 instead of a dead end. - Offering, products and paywall live in the dashboard. Entitlement
meetspot_pro, three Test Store products (Lifetime, Yearly, Monthly) in the default offering, and a RevenueCat Paywall. No prices are hardcoded in the app. - The server decides, not the client. The app sends only its RevenueCat app user id (
X-RC-App-User-Id). FastAPI calls RevenueCat RESTGET /v1/subscribers/{id}with a secret key and skips the quota only ifmeetspot_prois active. A failed or timed-out lookup falls back to the normal limit, so an error never unlocks anything. Only "yes" answers are cached (60 seconds), so the retry right after a purchase sees the new entitlement. - CustomerInfo drives the UI. On launch the app calls
getCustomerInfo(). An activemeetspot_proshows a "MeetSpot Pro" badge and hides the free-search counter; otherwise a "Go Pro" button opens the same paywall. The badge is display only. The server still checks on every search. - Pricing. I set the Test Store prices before I settled on pricing, so the demo shows placeholders ($99.99 lifetime, $79.99 yearly, $9.99 monthly). For a real launch I would charge $3.99 a month, $19.99 a year and $39.99 for lifetime. People plan meetups a few times a month, not every day, so a cheap monthly plan and a lifetime option at twice the yearly price fit how the app gets used. Lifetime is the highlighted package in the demo because Test Store subscriptions expire after about 25 minutes.
How I built it
desktop/: Electron 44 shell. The preload setswindow.MEETSPOT_PLATFORM = "macos", so the shared web UI knows purchases go through RevenueCat.npm run packagebuildsMeetSpot.appwith its own icon (@electron/packager, ad-hoc signed).public/meetspot_finder.html: anonymous app user id in localStorage, lazy-loaded Web SDK, paywall, automatic retry, Pro badge.app/payment/revenuecat.py: entitlement check over httpx with a 5 second timeout; handles grace periods and lifetime purchases.api/index.py: quota gate infind_meetspot, plus/api/config/revenuecatfor the public key.app/tool/google_directions_client.pyand_verify_commute_fairness()inapp/tool/meetspot_recommender.py: onecomputeRouteMatrixcall covers every person and every candidate.tests/test_revenuecat.py: 12 tests against a mocked RevenueCat (active, expired, grace period, missing, HTTP error, timeout, no key, caching, quota bypass with and without the header). I broke each guarded behavior on purpose once and confirmed the matching test failed.
What existed before and what I built for Shipaton
- Before: MeetSpot as an open-source web app, since 2025: geocoding, midpoint, venue ranking, maps, and a web-only payment flow.
- During the Shipaton window (Aug 31): the real commute-time fairness check.
- For this submission: everything macOS and RevenueCat. That covers the Electron app and its packaging, the paywall and purchase flow, the server-side entitlement check, the CustomerInfo Pro badge, the tests, and a fix for locations added with "+" never getting autocomplete.
Challenges
- After the free quota ran out, a click on Search was still caught by the web build's credits flow, so the RevenueCat paywall never opened. My scripted demo had used
requestSubmit(), which hid the bug. Switching the script to real clicks exposed it, and I fixed it before recording. - Caching a "no" would have blocked the search that runs right after a purchase, so only positive results are cached.
- On the Google Maps path, a location added with "+" never got autocomplete, which broke three-person meetups. I hit it while recording the demo and fixed it.
What I learned
Putting the purchase where the product already hits its limit felt more natural than inventing a premium feature. Keeping the trust decision on the server let the client code stay simple.
Known limitations and what's next
- The app user id is anonymous and local to the device, so anyone who learns that id could reuse its entitlement. Next I want to tie the RevenueCat app user id to a MeetSpot account (the repo already has SMS auth in
app/auth). - Move from Test Store to RevenueCat Web Billing (Stripe) to take real payments at the prices above.
- Developer ID signing and notarization, so the
.appcan be shared.
How to run and test a purchase
pip install -r requirements.txt, then setAMAP_API_KEY/GOOGLE_MAPS_API_KEYand an LLM key as in the README Quick Start.- Set
REVENUECAT_PUBLIC_KEY(Test Store public key) andREVENUECAT_SECRET_KEY(v1 secret key), then runuvicorn api.index:app. cd desktop && npm install && npm start, ornpm run packageto buildMeetSpot.app.- Run one search (free), then a second one. The paywall opens. Pick a package and choose "Test valid purchase". The search reruns and goes through, the header shows "MeetSpot Pro", and the customer shows
meetspot_proactive in the RevenueCat dashboard (Sandbox data).
Built With
- electron
- fastapi
- google-maps
- httpx
- javascript
- python
- revenuecat
- sqlite


Log in or sign up for Devpost to join the conversation.