When we started building RideCab WP, a WooCommerce-based taxi booking plugin, we underestimated almost everything. Booking flows look simple from the outside: pick up here, drop off there, pay. Under the hood, it's a maze of edge cases.
This post is a brain dump of the technical decisions, dead ends, and lessons from building a commercial WordPress plugin for a real business niche: taxi and fleet operators who want to take bookings from their own website without paying SaaS fees or per-ride commissions forever.
Why WordPress + WooCommerce?
The taxi industry is full of expensive SaaS platforms that charge per booking and lock operators into someone else's ecosystem. Most small fleet owners already have a website — usually WordPress — and don't want a separate dashboard, separate billing, and a 15% cut on every ride.
WordPress + WooCommerce gives us:
- Existing checkout and payment gateway infrastructure
- Order management UI that fleet owners already understand
- A massive plugin and theme ecosystem
- Zero recurring fees for the operator
The tradeoff: WooCommerce's data model is built for physical and digital products, not service bookings with dynamic pricing. Bending it to fit took real work.
Custom Product Type vs. Custom Post Type
The first big architectural decision: should a "ride booking" be a WooCommerce product or a separate entity?
We tried both. A pure custom post type gave us full control but meant rebuilding cart, checkout, and payment integration from scratch. A pure custom WooCommerce product type kept us inside WooCommerce's flow but fought us on every dynamic pricing rule.
The answer was a hybrid: a custom WooCommerce product type (ridecab_booking) that delegates pricing to a separate fare engine. Each booking becomes a real WooCommerce order, but the price is calculated server-side at add-to-cart time from distance, zones, and vehicle type — not stored as a fixed product price.
\php
add_filter( 'woocommerce_product_class', function( $classname, $product_type ) {
if ( 'ridecab_booking' === $product_type ) {
$classname = 'WC_Product_RideCab_Booking';
}
return $classname;
}, 10, 2 );
\\
This let us reuse WooCommerce's tax calculation, coupon system, and email notifications for free.
The Fare Calculation Problem
This is where 70% of the complexity lives.
A real taxi fare isn't just distance × rate. You're dealing with:
- Base fare + per-km rate + per-minute rate
- Zone-based pricing (airport surcharges, city center premiums)
- Vehicle class multipliers (standard, premium, van)
- Time-of-day modifiers (night rates, weekend rates)
- Minimum fares
- Round-trip discounts
- Distance calculation between two real-world points
We tried Haversine distance (straight-line) first. It was useless — drivers don't fly. Real fares need driving distance, which means a routing API. We integrated Google Distance Matrix as the default and made the integration pluggable so operators can swap in OSRM, Mapbox, or other providers.
The fare engine itself ended up as a chain of modifiers — each rule (base, distance, time, vehicle, zone) is applied in sequence, and each can be enabled, disabled, or overridden per operator. Keeping each rule as a small, independent class made testing far easier than the giant if/else block we started with.
Handling Bookings as WooCommerce Orders
A booking has data WooCommerce doesn't natively understand: pickup location, drop-off location, scheduled time, vehicle, passenger count, flight number, special notes.
We attach all of this as order item meta:
\php
add_action( 'woocommerce_add_order_item_meta', function( $item_id, $values ) {
if ( isset( $values['ridecab_pickup'] ) ) {
wc_add_order_item_meta( $item_id, '_pickup_location', $values['ridecab_pickup'] );
wc_add_order_item_meta( $item_id, '_dropoff_location', $values['ridecab_dropoff'] );
wc_add_order_item_meta( $item_id, '_scheduled_time', $values['ridecab_time'] );
}
}, 10, 2 );
\\
Then we filter the order admin screen and the customer email to surface this data clearly. A fleet dispatcher needs to see "Pickup: 14 Avenue Habib Bourguiba at 9:30 AM" the moment they open the order, not buried under a list of meta fields.
The Licensing System
A commercial plugin needs license keys, domain locking, activation/deactivation, and update delivery. Most developers reach for Freemius. We chose to build our own, partly for control, partly to avoid the revenue share.
The architecture is two pieces:
- A server-side mu-plugin that generates and validates license keys against an admin dashboard, with domain binding.
- A client-side class inside the plugin that calls home on activation, caches the response, and re-validates quietly in the background.
The key insight: don't validate the license on every page load. That kills performance and creates downtime if your license server has a hiccup. Validate on activation, cache the result for several days, and re-validate quietly in the background.
\php
class RideCab_License {
public function is_valid() {
$cached = get_transient( 'ridecab_license_valid' );
if ( false !== $cached ) {
return (bool) $cached;
}
$valid = $this->remote_validate();
set_transient( 'ridecab_license_valid', $valid ? 1 : 0, WEEK_IN_SECONDS );
return $valid;
}
}
\\
This pattern — cache aggressively, fail gracefully, never block the admin UI — is the difference between a licensing system users tolerate and one they hate.
What We'd Do Differently
A few honest reflections:
- We over-engineered the fare engine early. A simple distance-based fare would have covered 80% of customers on day one. We built for the 20% edge cases before the 80% was solid.
- We underestimated documentation. A commercial plugin lives or dies on its docs. We treat docs as code now: versioned, reviewed, shipped with releases.
- We should have set up update delivery from day one. Pushing updates manually for the first few customers was painful.
- Test coverage on the fare engine paid for itself ten times over. Pricing bugs are the kind users notice immediately and remember forever.
The Result
RideCab WP is now in production with real fleet operators running their booking sites on it. You can see the result at https://ridecabwp.com — one-time license, no commission, no per-booking fees.
If you're building commercial WordPress plugins, the meta-lesson is this: WooCommerce is a powerful foundation, but you'll spend more time bending it than extending it. Plan for that, lean on its hooks instead of fighting its data model, and ship the 80% before you chase the 20%.
Happy to answer questions in the comments — especially on the fare engine architecture or the licensing approach. The licensing system in particular is its own rabbit hole; I'll write that one up next.
Top comments (0)