---
title: "SJD Taxi: one booking hub for four sites | Nathan Gamache"
description: "I turned an unfinished replacement for SJD Taxi’s 2015 booking system into one booking hub for four sites, with HubSpot sync and Stripe checkout."
url: https://www.nathangamache.com/projects/sjd-taxi/
---

# SJD Taxi booking platform

SJD Taxi runs taxi and shuttle trips out of Cabo San Lucas, Mexico. I built and launched their website in 2023, working for them directly, and since 2024 I have worked with them through Gamache Technology Solutions and Perfect Afternoon. In 2025 they hired me to take over an unfinished replacement for the booking system they had run since 2015, and I turned it into a working one, with a central booking hub, booking plugins that install on any site, and all of its data flowing into HubSpot. It now takes the bookings on four sites.

[Visit sjdtaxi.com](https://sjdtaxi.com) · [All projects](https://www.nathangamache.com/projects/)

- Role: **Scope, development and launch**
- Client: **SJD Taxi, Cabo San Lucas**
- Through: **Gamache Technology Solutions and Perfect Afternoon**
- Website: **Built 2023**
- Booking system: **2025–2026**
- Stack: **WordPress · PHP · TypeScript · React · Stripe**
- Status: **Live on four sites**

![Step one of the SJD Taxi booking flow: a six-step progress bar running from service selection to billing, above a service selection card with airport transfer chosen, pick-up set to SJD Airport, an empty destination field, the round-trip option ticked and one passenger, beside an order summary panel that is still empty](https://www.nathangamache.com/assets/img/projects/sjd-taxi.webp)

## The story

I built SJD Taxi’s website and launched it in April 2023, working for them directly, and built its zone search pages alongside it. When I started Gamache Technology Solutions in February 2024, their upkeep moved to it. The 2025 booking rebuild was split between Gamache Technology Solutions and my work at Perfect Afternoon, depending on my availability and how urgent each part was.

The 2023 site, and the optimizations I made to it from 2023 to 2025, helped their business grow substantially.

Their bookings ran through a plugin from 2015 that kept every booking as a WordPress post. By September 2022 SJD had hired an outside development team to build a replacement. They worked on it into March 2023, then stopped. What they left pointed in a good direction but was not yet a working system. It was also a direct replacement for the old booking form, so it kept the old plugin’s problems: it could not easily be installed on another site, each site kept its own bookings with order numbers that could overlap, and the plugin itself had no connection to HubSpot, where the client wanted its CRM to live.

SJD needed someone to turn that into a working system. In May and June 2025 I wrote the scope, then built it. The outside team had laid the foundation of the TypeScript and React booking form and the database structure, and I built on both: I refactored the booking form nearly completely, and rebuilt the whole back end around a REST API, because the back end and the form had lived in the same plugin. I built the REST API and most of its controllers and handlers, and refactored every class and connector. I moved the booking logic into a central booking hub, made the booking plugin something that installs on any site and connects to the hub, put all four sites on one shared booking number sequence with a prefix for each site so order numbers can no longer collide, and, with a colleague, routed the booking data into HubSpot.

A Perfect Afternoon colleague had built the first HubSpot sync. It was good, but because it sat outside the booking plugin it had limits, so in May 2026 I merged it into the hub plugin and refactored it, with better error handling, tracking and alerts, and cleaned up a lot of old code along the way. Since the rework, scheduled checks resurface any booking that has not synced, and no deal has missed HubSpot.

Having every booking in one place, and in HubSpot, is what made the rest possible: remarketing to recapture lost bookings, which now wins back roughly two thirds of them, and the booking statistics and analytics we use, alongside the site’s Microsoft Clarity heatmaps, to keep improving the booking flow’s performance and conversion rate. It went from something that just functioned to something we can keep optimizing.

It also shaped the new sjdtaxi.com. I did not build or design the site that replaced the 2023 one in January 2026, but I had a large say in its design and in how the booking functionality would fit into it. I built its location page structure and main zone search, as I had built the zone search for the 2023 site. Since 2025 my focus has been the booking system and making the whole thing run better.

A sister site went live on the new system in November 2025 as the first test, and sjdtaxi.com and the other two sister sites moved over together in January 2026. All four have taken their bookings through it since.

The brief

## What the scope asked for

I compared the old plugin’s modules with the modules of the plugin I was taking over, then finished the scope after meetings with the client and the outside team. It set out:

- **One central booking hub** that each site’s plugin connects to, so the plugin can be dropped into a site and connect itself to the hub.
- **Tables of its own** for the booking data, instead of WordPress posts and post meta.
- **A cron job** in place of the old plugin’s database cleanup function.
- **Night pricing**: a vehicle surcharge between 11pm and 5am.
- **An 8pm cutoff** the night before for next-day shuttle bookings.
- **Vehicles chosen per site**, from the hub.
- **Abandoned carts** captured and passed to HubSpot.
- **A description for each vehicle**, and better pricing import and export.

Architecture

## How it fits together

- The **booking hub** is a separate WordPress install with its own admin. It is all PHP, and it holds the bookings, the vehicles, the locations and zones, the prices and each site’s settings.
- Each site runs the **booking form plugin**. It is a PHP plugin, but mostly TypeScript and React: the multi-step booking form and the Stripe checkout.
- The two talk over a **REST API**. The sites get their prices, vehicles and locations from the hub and send it their bookings, and the hub calls an endpoint on each site when a vehicle changes, so the site’s vehicle images stay in step.
- The system is integrated with **Stripe** (with Apple Pay, Google Pay and Link), **HubSpot** and the **Google Sheets API**.

![Architecture: four WordPress booking sites, each running a booking form plugin in TypeScript and React, talk over a REST API to one booking hub, a separate PHP install that holds the bookings, vehicles, locations, prices and each site’s settings. Stripe, HubSpot and Google Sheets are integrated below them.](https://www.nathangamache.com/assets/img/sjd-architecture.svg)

Drawn from the architecture summary I wrote for the handoff in March 2026. It shows the pieces and which way they talk, not the booking flow step by step.

The work

## What I built

### Payments

- Moved checkout to Stripe’s Payment Element on the Payment Intents API, with a new TypeScript and React payment step. A payment intent is created when checkout begins and kept in step with the cart total.
- Created Stripe customers with details such as IP address and email, so Stripe Radar has fraud signals to work with.
- Got Stripe and Google Pay payments working, stopped duplicate payment intents, and reconnected the Stripe webhook so bookings pick up their payment status.
- Added multi-currency checkout: USD, CAD, EUR, MXN and AUD, exchange rates that refresh every 5 minutes, a configurable exchange fee, and the chosen currency passed through to Stripe.
- Reworked how tax is calculated, fixed a subtotal bug so cents and tax come out right, and gave each site its own Stripe live and test mode.
- Cut coupon validation from 10 network requests to 3, and fixed a fee calculation bug with coupons.
- Sent custom destinations to a request-a-quote form in HubSpot instead of through checkout.

### Booking rules

- The 8pm cutoff for next-day shuttle bookings, and same-day booking restrictions.
- Night surcharges between 11pm and 5am, vehicles chosen for each site from the hub, and a cron job in place of the old plugin’s database cleanup.
- Departure-only handling when the destination is an airport.
- One-way and round-trip display, a description for each vehicle, locations in alphabetical order with the default first, and vehicles ordered from the lowest price.
- In early 2026, the round-trip choice and passenger count moved onto the first step, next to pickup and destination.
- A sign name field, a separate country code for phone numbers, the airline and flight number fields in a new order, and a time picker in 15-minute steps to match HubSpot’s date and time property.
- The small booking forms on the home and location pages hand pickup, destination, trip type and passenger count to the main form, and each location page pulls its one-way and round-trip prices and booking links from the hub by location ID.

### One system, four sites

- Split the system into the central hub, and cleaned up the codebase and its old libraries.
- Moved the general settings into settings for each site: booking page, terms page, default location, tax rate, admin email and receipt copy.
- Added a site prefix to bookings in the admin and on their Stripe transactions.
- Made the booking form’s colors configurable for each site from a settings page.
- Added an endpoint on each site that re-syncs vehicle images when the hub updates a vehicle, plus a button to re-sync by hand.
- Finished the pricing import and export.

### Admin, HubSpot and handoff

- A quote generator in the hub’s admin, a View in Stripe button on each booking, and an action for assigning bookings, with an Assigned status.
- New bookings written to the bookings tracking spreadsheet through the Google Sheets API, with a retry when a sync fails.
- A fix for what the booking calendar (iCal) entries contain.
- A scroll-to-accept terms of service panel on the payment step.
- Merged the HubSpot sync a colleague had built into the main codebase and refactored it, with better error handling, tracking and alerts, and scheduled checks that resurface any booking that has not synced. With that colleague I had hooked the sync into creating, updating and deleting vehicles, locations and bookings. I held HubSpot updates back until the Stripe webhook returns, so details like the vehicle name arrive reliably, and made the sync fire when a booking is first paid, then follow refunds and chargebacks.
- Abandoned-cart capture, with a resumable cart link for each one, synced into its own HubSpot pipeline, building on sync code my colleague had started. The client’s remarketing flow runs from that pipeline to bring customers back to their carts.
- Ad click attribution on the old site in 2024: each booking saved the ad click it came from and its referrer. For the new system I rebuilt the bookings spreadsheet feed, with retries.
- Parts of the handoff documentation: how the plugin works, an end-to-end test (place a test booking, then check the hub record, the Stripe payment, the HubSpot contact, deal and workflows, and the error log), and the architecture summary this page draws on.

Engineering

## Three decisions

01 · The hub

### A hub the sites plug into

The plugin I took over was a direct replacement for the old booking form, so each site would still have kept its own bookings. I moved the booking logic into one central hub, so the plugin can be dropped into a site and connect to it. That is how one hub now serves four sites, each with its own settings, colors and booking prefix.

Technologies: Booking hub, REST API, PHP

02 · HubSpot

### HubSpot waits for Stripe

I held HubSpot updates back until Stripe’s webhook returns, so details like the vehicle name arrive reliably. The sync fires when a booking is first paid, then follows later payment changes such as refunds and chargebacks.

Technologies: HubSpot, Stripe, Webhooks

03 · Webhooks

### The webhook never creates a booking

Stripe’s webhook only updates bookings that already exist. If the booking is not there yet, it tries again a little later instead of creating one, which prevents duplicate bookings.

Technologies: Stripe, Payment processing, Reliability

Launch

## Going live

A sister site went live on the new system in November 2025, ahead of the sjdtaxi.com relaunch. I took it live with the colleague who built the first HubSpot sync, and removed the old booking plugin and its database tables from it.

sjdtaxi.com relaunched on the new system in January 2026, with the client and another developer. I ran the pre-launch and launch checklists, synced the latest pages, posts and media across, activated and configured Cloudflare page caching, and installed the analytics and advertising tracking. Just after launch I made the booking flow send a purchase event for GA4 and Google Ads conversions.

By March 2026 changes were shipping to all four sites at once, the first-step rework among them.

2026

## Around and after launch

The zone pages were part of my work through Gamache Technology Solutions. The rest of this section was Perfect Afternoon work in 2026.

- **Zone pages.** Just before launch I built 10 zone landing pages from the booking database, through a new zones API endpoint, ACF field groups and a page template. Later I switched them to load their locations in the browser, so they stay current behind page caching.
- **Structured data.** Location pages carry breadcrumbs through their zone, a Service with an AggregateOffer priced from the live booking API to match the Starting at price on the page, an FAQPage from the questions on the page, and LocalBusiness details. I also collapsed redirect chains into single redirects and made GA4 count each purchase once.
- **An SEO and internal linking pass**, which I led in July 2026. After a crawl of about 2,800 pages, roughly 11,000 internal link occurrences that went through redirects were pointed straight at their live addresses. I built a Top Hotel Shuttles cards block, and a Nearby Locations block that adds up to 8 links from the same zone to every hotel page. I also wrote new copy for 12 location pages and 34 meta descriptions, improved the alt text on 833 images across 166 posts, and fixed heading levels across 203 pages. SEMrush site health went from 78% to 85%.
- **The sister sites.** Schema and content work on the three other sites that share the booking hub, and Core Web Vitals work on one of them: layout shift went from 0.149 to 0.067 and blocking time from 130ms to 10ms.
- **QR codes** generated from real bookings, with tracking that attributes each reservation to its deal record. They went live in September 2026.

Outcome

## What changed

- The 2023 site and my optimizations to it from 2023 to 2025 helped their business grow substantially.
- The 2015 plugin has been replaced. Four sites take bookings through one hub.
- Checkout runs on Stripe’s Payment Element, in five currencies, with Apple Pay, Google Pay and Link.
- Paid bookings reach HubSpot and the bookings spreadsheet by themselves, and the Stripe webhook only updates bookings that already exist.
- Scheduled checks resurface any booking that has not synced to HubSpot.
- Remarketing wins back roughly two thirds of the bookings people abandon.
- SEMrush site health on sjdtaxi.com went from 78% to 85% after the July 2026 pass.

## Two more from the same stack

### [Corrigan Companies](https://www.nathangamache.com/projects/corrigan/)

WordPress at the other end of the scale: an 11-site multisite I set up environments and servers for, and launched four sites on.

Technologies: WordPress, Multisite, DNS, Performance

[View project: Corrigan Companies](https://www.nathangamache.com/projects/corrigan/)

### [Terminal Supply Co: Product Imports & AS400](https://www.nathangamache.com/projects/terminal-supply/)

A WooCommerce catalog built from AS400 exports and the client’s own spreadsheets, and both sides of a REST integration with the AS400.

Technologies: WooCommerce, AS400, PHP, Data imports

[View project: Terminal Supply Co: Product Imports & AS400](https://www.nathangamache.com/projects/terminal-supply/)

[View all 16 projects](https://www.nathangamache.com/projects/)

## Questions about this build?

I am happy to go deeper on any part of it. [nathanpaulgamache@gmail.com](mailto:nathanpaulgamache@gmail.com)

[Ask me about it](https://www.nathangamache.com/contact/)

---

Nathan Gamache · [nathanpaulgamache@gmail.com](mailto:nathanpaulgamache@gmail.com) · [GitHub](https://github.com/nathangamache) · [LinkedIn](https://www.linkedin.com/in/nathan-p-gamache/) · [nathangamache.com](https://www.nathangamache.com/)
