Every few months a friend of a friend finds me. They run a kayak company, or a wine tour, or an escape room, and they have a budget and a dream: "We just need a simple booking system. A calendar, pick a time, pay with a card. My nephew says it's a weekend with Stripe."
I used to take these projects. I want to talk about why I stopped.
The demo is a weekend. The system is not.
The nephew is right about one thing. The happy path genuinely is a weekend of work. A date picker, a slots table, a Stripe Checkout session, a confirmation email. You'll have a demo by Sunday night and everyone will be thrilled.
The happy path is maybe 15% of a booking system. The other 85% is what happens when reality shows up, and reality has a lot of edge cases with money attached.
Double-booking is a distributed systems problem wearing a small business costume
Here's the one that gets everybody. Two people are looking at the last two seats on the 2pm tour. Both hit "book" within the same second. Your endpoint reads availability (2 seats), both requests pass the check, both charge succeed, and now four people hold two seats and none of them did anything wrong.
Congratulations: you need row locking, or an atomic decrement with a constraint, or a reservation-hold pattern with expiring holds for the folks who open checkout and wander off. Which means you now need a background job system to expire the holds. Which means you need to decide what happens when a hold expires while Stripe is mid-payment, because a webhook will absolutely arrive for a charge whose hold died two seconds earlier.
None of this is exotic engineering. It's all solvable. But your client thinks they bought a calendar, and you're in the database documentation reading about SELECT FOR UPDATE at 1am. The quote did not include this.
The greatest hits of the remaining 85%
A partial list, from scars.
Timezones. The tour runs in Lisbon time. The customer books from New York. The reminder email fires from a server set to UTC. Daylight saving time transitions happen twice a year in both places, on different dates. I promise you will send someone a reminder for a tour that already left, and they will be standing on a dock with kids, calling your client.
Refunds are a state machine. Full refund, partial refund for the kid who got sick, a chargeback three weeks later on a booking that was already partially refunded, a reschedule that crosses a price-season boundary. Every arrow in that state diagram is somebody's angry email, and Stripe's dashboard only helps if your data model agrees with it.
Capacity isn't one number. The 2pm tour needs a guide, a van with nine seats, and gear. Your client has three guides, two vans, and one guide who can't drive the big van. Booking the tour means reserving all of those at once, or the system happily sells a tour with no vehicle. You are now writing a resource scheduler. That was also not in the quote.
Recurring schedules with exceptions. "Every day at 10 and 2, except Sundays, except the winter schedule, except Christmas week when we run at 11, and next Tuesday is a private charter." Cron syntax will not save you. iCal RRULEs will slightly save you, then hurt you.
Reminders and no-shows. The client asks for SMS reminders in month two, because no-shows are eating them alive. That's a messaging provider, delivery tracking, and quiet-hours logic. Bookings without reminders leak real money, so you can't skip it.
Each item is a fun problem on its own. Together, quoted as "a simple booking system," they're how a two-weekend project becomes a six-month one that you're maintaining forever. Which brings me to the actual reason I stopped.
You're not shipping a project. You're adopting a pager.
A booking system processes money for a business that may have exactly one employee. When it breaks on a Saturday morning in July, high season, that's not a bug ticket. That's a person whose income stopped, calling the only phone number they have: yours.
For a freelance rate that made sense as a one-off build, you have become the unpaid on-call engineer for someone's livelihood. Forever. Every Stripe API version bump, every framework security patch, every "can we add gift cards?" lands on you, and there is no version of the conversation where the kayak company hires a second engineer.
That's the deal you're actually signing when you say yes to the nephew's weekend project. Price that honestly and nobody can afford you, which is the correct outcome.
What I tell them instead
Buy the boring part. Booking platforms in the tour and activity space have already made every mistake in this article at scale, and they charge either a monthly fee or a slice of each booking. For a small operator I lean toward the per-booking pricing: when I looked at options for the last client who asked, we went with tour booking software that takes 5% per booking with no monthly fee, pays the operator directly through Stripe, and, the part I cared about, exposes webhooks and a REST API. Zero revenue means zero cost, which matches how seasonal businesses actually breathe.
Then build the fun part at the edges, where the failure modes don't involve anyone's rent. The marketing site, the brand, the custom availability widget on the homepage, the internal dashboard that pulls booking events off the webhook and shows the owner their week at a glance. That's a real project with a real end date, the client gets custom work where custom work is visible, and when a payment edge case detonates at 6am it detonates inside a platform with an on-call rotation that isn't me.
When you should ignore all of this
Build it yourself if the booking engine IS the business: you're doing marketplace things, your domain has genuinely weird constraints no platform models, or there's an in-house team that will own it after you. A rock-climbing gym with tiered memberships, belay certifications, and route-setting schedules might really outgrow every off-the-shelf option. Fine. Go in with a data model on the whiteboard, a hold-expiry strategy, and a maintenance budget in the contract.
But if the requirements fit on an index card and the words "it's basically just a calendar" have been spoken out loud, they're describing a solved problem. Some problems are fun to solve and bad to own. Booking systems are both, and the second one wins.
Comments
Loading comments…