1. Client situation
The client is an island tour operator in Krabi province, in business for 12 years, with a fleet of speedboats and longtails. Each boat holds 30 seats and runs 2 trips/day covering Phi Phi, Hong Islands, James Bond, 4 Islands, and private charter.
Pre-project, the booking system was a half-manual, half-digital patchwork — paper booking for walk-ins at the front counter, WhatsApp for foreign customers, LINE for Thai customers, and a legacy WordPress + WooCommerce website. The single biggest issue: direct-booking checkout dropoff was 78% because the only payment option was bank transfer, which foreign customers almost universally refuse.
Customer mix: Chinese 45%, Eastern European (predominantly Russian) 25%, Thai 15%, other 15%. The old site was English-only with self-rolled Google Translate copy that caused frequent misunderstandings about package inclusions.
At project start, 70% of revenue came from OTA platforms (Klook, GetYourGuide, Viator) at 18-25% commission, steadily eroding margin. The project goal: grow direct bookings 400%+ within 6 months and cut OTA dependency to no more than 40% of revenue.
2. Why generic solutions don’t work
We started by evaluating off-the-shelf options — the client preferred not to invest in custom development if SaaS would do.
WooCommerce + multilingual plugins (WPML, Polylang) — In practice plugins routed languages incorrectly often, and checkout flow broke when currency switched mid-session. Even the enterprise plan didn’t address WooCommerce’s core architecture being built for retail, not booking.
FareHarbor, Rezdy, Bokun — Solid for tour bookings, multi-currency is fine, but none support PromptPay QR — the main channel for Thai customers. Thai customers would have to transfer and forward a slip — not instant.
Multi-boat scheduling SaaS — None supported the “boat shuffle” business logic the client uses regularly: when Boat B has a problem, customers shift automatically to Boat A with notifications to captain and every affected customer.
Conclusion: custom build.
3. Our approach
Stack: Next.js 14 (App Router) + Stripe + custom PromptPay QR generator + next-intl (i18n) + Prisma + PostgreSQL + Vercel Edge for regional tour-catalog caching.
Multi-currency: Stripe handling THB/USD/EUR/CNY/RUB natively, with locked exchange rate at checkout start (to prevent rate fluctuation while customers pick add-ons).
Custom vs SaaS — why the client chose custom:
| Item | SaaS (Bokun) | Custom |
|---|---|---|
| Year 1 | USD 24,000 | USD 14,000 one-time |
| 5-year TCO | USD 120,000 | ~USD 20,000 |
| PromptPay | No | Yes |
| Boat shuffle logic | No | Yes |
| OTA webhook customization | No | Yes |
Custom 5-year TCO is 60%+ lower (custom = USD 14K one-time + USD 1.2K/year hosting × 5 = ~USD 20K).
The 4 architecture pillars:
- i18n done right — 4 languages (TH/EN/CN/RU) lazy-loaded by route and user locale, every line of copy through a native translator (no Google Translate).
- Real-time fleet sync — Postgres triggers + WebSocket subscription pushing availability updates to every client in <500ms.
- Multi-payment — Stripe (international cards + Apple/Google Pay), PromptPay QR (instant THB), bank transfer (fallback).
- Boat captain mobile PWA — Offline-first for use on boats with no signal, syncs when back at the pier.
4. Week-by-week
Week 1-2: Discovery
- Interviewed owner, GM, 2 boat captains, 1 admin to map actual workflow
- Designed a 22-table database schema covering boat schedule, captain shift, tour package, customer, payment, refund, OTA sync log
Week 3-5: Backend API
- Booking flow API with seat-lock (5-minute hold during checkout)
- Fleet availability engine: real-time conflicts across the entire fleet, supports boat shuffle
- Payment processors: Stripe integration + custom PromptPay QR generator + bank transfer reconciliation worker
Week 6-8: Customer frontend
- Mobile-first booking UI (70% of traffic is mobile)
- 4 languages lazy-loaded — initial single-language bundle = 38KB
- Checkout flow: select tour → date → pax → add-ons → payment → confirmation
- Currency converter widget shows real-time but locks rate at checkout
Week 9-10: Admin console
- Thai-language reservations console UI (admin team is entirely Thai)
- Refund flow supports partial refund + weather cancellation
- OTA partner webhooks: Klook, GetYourGuide, Viator — 2-way inventory sync
Week 11-12: Captain mobile PWA
- Offline-first via Service Worker + IndexedDB queue
- QR scan check-in (customers show booking QR at the pier)
- Weather-based cancellation flow: captain flags cancel → admin approves → system refunds automatically + notifies customer in 4 languages
Week 13: Polish
- Native CN + RU translator review every line of copy
- Accessibility audit (WCAG AA)
- Security review (OWASP Top 10 + PCI scope review)
Week 14: Soft launch
- Gradual rollout: 10% → 50% → 100% over 5 days
- Training: 2 admin sessions, 1 captain session
- 2 weeks of post-launch monitoring
5. Problems encountered
Stripe in Thailand: At planning time Stripe wasn’t directly available for TH-registered entities. The client chose Stripe Atlas to incorporate in Delaware for international payments + PromptPay for THB payments. Tax setup was more involved than expected — coordinating a Thai accountant + a US CPA for form 5472 added 2 weeks of compliance review.
Russian translation glossary: Russian customers expect marine-specific vocabulary like “Skiff” (small boat), “Catamaran” — Google Translate produced industry-foreign terms. Fixed by building a glossary of 80+ terms for the native translator to review before any translation.
Captain app hangs on boats: First week of soft launch saw reports of the captain app freezing mid-sea with no signal. Fixed with service worker + offline-first sync queue triggering when network returns + pre-cache of the day’s full booking data when the boat leaves the pier.
6. Post-launch results + ongoing
Month 3:
- Direct booking +420% vs baseline
- OTA dependency dropped from 70% to 35% of revenue
- Average order value +25% — multi-pax booking easier, and add-ons (lunch upgrade, photographer) drove upsell
Month 6:
- Return customer rate 18% (previously 4%) — driven by email retargeting using first-party booking data
- Expanded to private charter booking flow (a post-launch customer request)
Lessons learned:
- i18n requires investing in native translators — no shortcut. Google Translate erodes credibility 100%, especially with paying Chinese and Russian customers.
- Boat shuffle logic is something SaaS can’t cover — Thai tour-operator operational reality is more complex than Western tour-ops standards.
Ongoing:
- System maintenance + quarterly feature releases under a quarterly retainer
- Roadmap: AI-driven dynamic pricing, group booking widget for corporate clients, multi-property expansion (client is planning a resort)