
LiveBooking a badminton court here meant a phone call. So I built the app I wanted.
TapNGame is a sports booking app for Metro Vancouver. Players find a court or a pickup game and book it in a few taps, and venues run their calendar from a dashboard. I started it in 2022 and I'm the only engineer.
I answer the support inbox tooI wanted to play at 7, not spend 6 o’clock finding a court.
I play badminton. Getting a court meant calling during business hours, or scrolling a WhatsApp group for a screenshot of someone's spreadsheet. The venues weren't much better off: a paper diary at the front desk, and booking software made for salons that couldn't describe a court selling eight separate spots.
- Phone calls during business hours
- Spreadsheet screenshots in WhatsApp
- A paper diary at the front desk
- Walk-ins priced from memory
- Live availability, booked in a few taps
- Pickup games with waitlists and a group chat
- A calendar where every slot is one click
- Walk-ins priced by the same rules as the app
Every sport books a court differently.
A tennis court is rented whole. A drop-in badminton session sells single spots, and players need to see how many are left. A class has an instructor and a cap. A pickup game has a host, a waitlist and a group chat. And a walk-in at the front desk has to be priced by exactly the same rules as the app.
All of it runs on one availability engine and one pricing engine, in the venue's own timezone, with the database deciding who gets a slot when two people want it.
Tap a slot, it’s yours while you pay.
Pick a court, a day and a length, and the grid shows live prices. Tapping a slot holds it for two minutes, and everyone else watching sees it go. Pay with Apple Pay, Google Pay or a saved card.
If you swipe the app away mid-booking, the hold is released straight away instead of blocking the slot for everyone else until it times out.
Find four people, not just a court.
Anyone can host a game: open to all, or with host approval. Players join or go on a waitlist, and the game chat opens with everyone who's in.
Each host shows a reliability score built from who actually turns up, and hosts can correct a no-show after the game. Players can report or block anyone, which Apple requires and which matters when strangers meet at a court.
A front desk that runs itself.
A venue signs up through a wizard that shows a live phone preview of the listing as players will see it, connects Stripe, and can take bookings the same day.
From there: a calendar where any slot can be blocked, repriced or booked for a walk-in; pricing rules; classes; coupons; QR check-in and a kiosk mode for the front desk; a morning email with the day ahead; and staff roles, so the person at the desk sees bookings but not revenue.
An admin console, for the days something goes wrong.
I approve each venue before it goes live, handle reports and refunds, and work through a queue of any payment that ends up without a booking. There's also a switch that turns payments off across the whole app, so a bad release can't take anyone's money.
Six bugs that taught me the most.
One support email, ten broken functions
“I can’t delete my account.” A single support email, August 2026.
WhyThe delete function still pointed at a table I had dropped three months earlier. Postgres only checks those names when a function actually runs, so nothing had flagged it.
The fixI ran a checker across all 311 database functions and 147 triggers. It found nine more that failed on every call, including booking cancellation and venue go-live. Sixteen migrations later, 156 test deletions went through cleanly.
A $112 payment with no booking
Stripe showed a captured $112 payment. The app had no booking to match it.
WhyThe app created the payment first and the booking second. When something failed in between, the money arrived and the booking didn’t.
The fixThe booking is now created first, the payment carries its ID with an idempotency key, and only a verified Stripe webhook can confirm it. Duplicate webhooks are ignored, and anything that still slips through lands in an admin queue.
A spot that never came back
The calendar said “3/4 spots” for a drop-in session with no bookings in it.
WhyDrop-in bookings don’t point at a single slot, and every cancel path only gave capacity back when they did.
The fixOne database trigger now fires whenever a booking becomes cancelled or a no-show, whichever path got it there, and returns the spot. It’s capped, so a session can never overfill.
Evening prices in the morning
A 10 a.m. court was charging the evening peak rate, and the evening slots were cheap.
WhyPricing worked out the day and hour in UTC, not Vancouver time. The app had a matching bug that showed slots in the phone’s own timezone.
The fixPrices and slots are now calculated in the venue’s timezone, checked against a real court with a $20 base rate and a $28 peak rate.
Checkout that only failed at the office
A colleague trying the app on a corporate network got “Something went wrong” on every single checkout.
WhyThree things stacked up. A 20-second timeout was too tight for a first booking behind a slow proxy, an error filter hid the real “connection timed out” message, and the app refreshed its login token five times in 14 seconds when it came back to the foreground.
The fixA longer timeout for the first booking, the real error shown to the user, and a token refresh only when it’s actually about to expire. I checked the payment side too: no path could have charged twice.
The fraud check that stopped real players
New players were being turned away on their very first booking.
WhyTo block card-testing bots, brand-new accounts couldn’t pay for five minutes. But the new sign-up flow took well under five minutes, so real people hit the wall too.
The fixCut the window to 60 seconds, which still breaks the sign-up-then-pay loop bots use, and kept the other guards: three declines locks card entry for an hour.
Apple and Google both said no first.
Apple rejected an early build because the Apple Pay sheet showed the venue's name instead of the business actually taking the money. It now always says “Pay TapNGame”. Google Play turned down the first build numbers, so builds now start at 100 to leave room.
- 3
- apps on one backend
- 6
- sports live in Vancouver
- 360+
- database migrations
- 1,600+
- commits across the apps
Live, and growing one venue at a time.
TapNGame is on the App Store, Google Play and the web, with six sports in Metro Vancouver. Leagues, tournaments and memberships exist in the database but aren't switched on yet.
The next step is more venues. I've been visiting badminton centres in Richmond, most of which still take bookings by phone.
I run this end to end: product, code, payments, support.
If that’s the kind of ownership you’re hiring for, the about page has the longer story.
How I work

