Case study
Why I license Wander Yak out instead of rebuilding it per client.
Tourism businesses kept asking for the same thing with different logos: packages, bookings, payments, a CMS. Building it once and licensing it out turned out to be the harder, better decision.
The problem
Every tourism client wanted the same system with a different coat of paint.
Tourism business owners don't actually want a "website" — they want fewer WhatsApp messages asking "is this still available." The pattern across client after client was identical: package listings, a way to take a deposit or full payment online, some way to run a cause-driven or volunteer campaign alongside regular packages, and a CMS so someone non-technical could update prices and photos without calling a developer.
Building that from scratch for each client meant re-solving the same booking-and-payment problem repeatedly, at full price each time, while the client got a system with no more depth than the last one. That's a services business, not a product business — and it doesn't get better with each build, it just gets repeated.
The approach
Build it once, properly, then license it — don't rebuild the same booking engine per client.
Wander Yak was built as a real product, not a template: a full booking engine (packages, itineraries, per-package SEO, Razorpay checkout, PDF itinerary generation), a CMS-driven admin covering everything from destinations to homepage sliders, and a proper roles-and-permissions system so a client's team can have multiple admins without sharing one login.
The business decision was to license it under a custom "no resale" terms — a client can run it for their own business, modify it or pay for modifications, and gets three months of free bug fixes on unmodified code, but can't redistribute or resell the source. That's the difference between selling a product and selling custom development: the platform gets better every time it's used, instead of forking into a dozen unmaintained copies.
The architecture
What's actually running under it.
- Backend
PHP, with Guzzle for outbound HTTP and Nyholm PSR-7 for request/response handling — a lean stack, not a full framework.- Payments
Razorpayfor checkout, with dedicated success/failure pages and a generated PDF itinerary tied to a confirmed booking.- Multi-provider by design —
PHPMailer,SendGrid, andMailgunare all wired in, so a client isn't locked to one provider's uptime or pricing. - Admin / CMS
- Packages, tours, hotels, attractions, destinations, sliders, blogs, reviews, and a role-and-permission system (
add_role,configure_role) for multi-admin teams. - SEO & delivery
- Sitemap, robots.txt, and RSS feed generated per site; installable as a
PWAvia a web manifest, so it can sit on a customer's home screen like an app.
The interesting problem
A registration isn't "done" just because someone submitted the form.
Campaigns are cause- and volunteer-driven trips — a registration form with a media consent checkbox, a declaration, and sometimes an entry fee an admin can toggle on or off per campaign. The obvious approach is to save the form and call it registered. That breaks the moment a campaign has a fee: someone can submit the form, abandon the Razorpay checkout, and now there's a "registration" for a spot they never actually paid for.
The fix was treating payment status as part of the registration's state, not a side effect of it. A free campaign confirms a registration immediately (payment_status = not_required). A paid one saves the registration as pending first, creates the Razorpay order, and only flips it to confirmed once the payment actually verifies — with a duplicate check up front so the same person can't end up with two pending registrations for one campaign while they retry a failed payment.
The outcome
A product that gets sold, not a project that gets closed.
Wander Yak runs as a licensed product today — the license terms themselves (free bug fixes for three months on unmodified code, chargeable support after modification, no resale) are part of the product, not an afterthought bolted on after the build. That structure is what makes it a repeatable sale instead of a one-off contract.
One honest gap: a paid registration that starts checkout and never completes it stays pending indefinitely — there's a duplicate-registration check, but no scheduled job yet to expire a stale pending registration and free that up. It's the kind of edge case that doesn't matter until a campaign is popular enough for it to matter, which is exactly when I'd rather have already built it.
Running a tourism, events, or booking-driven business and want to see it live?
Get in touch