Intro
This post documents the design and development of a custom WordPress plugin for Libertad Travel. We’ll look at how Libertad Travel is different from your typical travel agency, some of Libertad’s business goals, the design and development that went into creating the custom plugin, and wrap-up with what the future plans are for Libertad. So, without further preamble…
The Business
Marc Schwarz founded Libertad Travel to provide a unique way to travel through Mexico. An avid traveler himself, Marc knew there was a better way to provide supremely flexible, fun travel options for people who wanted to travel with a “twist.”
Here’s why Libertad is a better way to travel: you buy one ticket that gives you freedom to get on or off the bus route as you choose. The buses run frequently on a published schedule. Once you have a ticket, you can choose which day you’d like to depart from your current location and where you’d like to stop along the way. Unlike tour companies that sell experiences that last mere weeks, a Libertad Travel ticket gives you unrestricted access to the tours for a period of months, and you can make changes as frequently as you’d like.
The entire system works in real time, so you can make a change to your itinerary at (literally) the last minute. As long as you’re there to check in on your reserved bus when it arrives, you can select a bus for a given departure date and location, and then you’re off.
Here’s a quick example. One of the tours allows you to board a bus in Cancun that is bound for Playa Del Carmen. Once you get to Playa though, you can choose to head on to Tulum on the next available bus (or not!). When you’re ready, you can reserve a seat on the bus and head from Playa to Tulum, or pick up the bus anywhere else on the route such as Merida or Palenque (and many other places in this example).
The beauty of this, for the traveler, is the flexibility. The flexibility is where software comes in to help manage the complexity, keep all of the customers’ reservations straight, and present a simple way for the customers to set their itineraries.
Marc looked but couldn’t find any WordPress plugins that could do this, so he started searching for a consultant to build the system for him.
Marc and I met one another through an online job posting. We exchanged a few emails and had a call to agree to the scope of the project and the features he needed for the first iteration of software. We decided on a fixed price for the first phase, and I started designing the plugin that would bring Marc’s great idea of flexible bus tours to life.
The Need
As I mentioned before, Libertad Travel’s system needed to provide its users with supreme flexibility. The primary interface would be a reservation screen, which we quickly started calling the “Trip Planner,” that would allow any paying customer to log-in and make changes to his or her bus reservations in real time.
Here is a very early concept of the Trip Planner interface, drawn on my whiteboard.
The Trip Planner for every customer would be consolidated on a mostly read-only page called the “Drivers Area” where the bus drivers on the tour could check-in customers as they boarded the buses.
Finally, Libertad Travel’s staff needed a way to create and manage tours, including which cities they would stop in and on what dates.
Design and Development
One of my goals from the start was to design the entire system so that it integrated into WordPress and WooCommerce. I didn’t want to create a ton of extra screens and write logic to tie orders to trips. It seemed like too much extra complexity. We decided to simply build the travel system onto (mostly) existing WooCommerce features.
So, after some deliberation, I decided that the best architecture of the plugin would be:
- Tours would be built and managed by creating a WooCommerce Product; the Tours would be input as form entries into a meta box on the Add/Edit Product screen
- A customer would buy a Tour, which would generate an Order (this is all part of the standard WooCommerce flow)
- On checkout, the user would choose his/her starting location, as well as an available date
- After that, the user could go to the Trip Planner (a separate page on the front end, requiring login) to add or remove dates from a given Trip.
The Trip Planner, shown below, would be the most important screen, since it would be the primary UI for customers to manage their tours. Here’s a screenshot of the final Trip Planner that we’ve settled on after a few iterations:
Tour Manager
I started with building the Admin’s Tour Manager. It was the part of the plugin that would allow an administrator to create a new tour, add the locations, and add the dates for each location. It would also need a way for the administrator to set the number of seats on the bus.
The number of available bus seats would be critical, since we would never want to allow the buses to get overbooked under any circumstance. We wouldn’t want to leave customers stranded somewhere in an unfamiliar city, now would we?
After some initial wireframes (pictured below), we went right in to building the plugin on the backend. We realized pretty quickly that we’d need a way to dynamically manage the number of stops on a trip.
For example, in the early wireframe shown here, it’s easy to see that the UI could become crowded and difficult to manage if we had a tour with a large number of locations. My initial attempt, as shown in the wireframe, would be to have each tour support up to twenty locations. However, Marc and I weren’t very happy with this and sought out another solution.
Luckily, I utilized a library called CMB2, for building the custom meta boxes, and CMB2 has the option of using a repeater field. This way, the administrator can dynamically add stops and remove them without having a bunch of empty form fields cluttering up the user interface.
I knew that, for a project of this size, an object-oriented approach would be best. I wrote the following classes:
- Bus – used to represent people sitting in seats; primarily to make sure we don’t overbook the buses
- Tour – keeps information about the tours that the admin enters into the database
- Reservation – manages the process of reserving seats on buses for customers
- WooCommerce Utilities – various functions to interface with WooCommerce
Also, for a great user experience, I used AJAX wherever possible. Basically, this means that the page doesn’t have to refresh in between server requests because some JavaScript that I wrote (with a lot of help from jQuery) can exchange data with the server asynchronously.
Here is a current screenshot of the Tour Manager:
Trip Planner
The Trip Planner is the screen of the app where the customer would spend the most time modifying his or her tour. Since it would be such a heavily used part of the software, we spent a bit of time getting it just right.
As you can see in the mobile screenshot, you can edit existing reservations, or create new reservations, from any device. This is a necessity for travelers who need to make changes while on the go.
Checkout
The user is presented with a lightweight version of the Trip Planner during the checkout process. We opted to do this so that they could secure seats on a tour right from the first purchase of the ticket. This would prevent a customer from buying a ticket and skipping the reservation process to some extent. We didn’t want the users to come back later only to find that all of the tour dates during their travel time were taken.
Printer-friendly Version
It was a small feature, but we used a pure CSS solution to create a Print Itinerary button on the Trip Planner. It’s pretty self-explanatory, but added a nice touch to the site’s user experience.
Lessons Learned
This is a somewhat scattered list of things I learned while working on the project.
I had very hard time doing a refactor at one point. I struggled with refactoring my code from a one-customer-equals-one-ticket system to a system where one customer could buy multiple tickets (simply by increasing the number of items in his/her cart before checkout) and bring along his or her friends. It would have been helpful if I had kept my implementation of the Tours, Reservation, and Bus classes a bit more flexible/generalized so that this refactoring would have been easier.
Troubleshooting and testing my AJAX calls proved to be difficult. I typically use a combination of server logging and JavaScript console output while developing to aid with debugging, but this wasn’t exactly straightforward with my AJAX requests. I ended up using a tool called xdebug a lot so I could step through my code one line at a time if needed and following what was happening during the AJAX calls.
Also, I had a tough time handling dates. I had to pass dates back and forth between JavaScript and PHP. It was manageable with careful coding, but I did make the mistake of using a United States date format (MM/DD/YYYY) when almost everyone else in the world uses DD/MM/YYYY. As a compromise, we went back into the code and changed everything to be DD-MMM-YYYY, such as 23 Jul 2015 so as to clear up any confusion for our international users.
I should have written a test suite early on. This application ended up being quite complex, so introducing new features would often break features I had already tested. Having a set of regression tests would have given peace of mind and made adding new features less stress-inducing. I would still love to write tests at some point, but it’s much harder to fit into the development process now that we’re getting close to launch.
Moving Forward
It looks like the business will launch officially within the next couple of months. Marc is starting with tours available in Mexico, and he also has plans to expand operations throughout Central America. Marc and I also have plans to extend the plugin to add new features and make it better as the business picks up and we get feedback from customers.
It has been great working with Marc to help launch his company, and hopefully this is the beginning of a long business relationship!
Consider this
I’ve really enjoyed working on this project, and I’m always looking for more exciting opportunities like this one.
[gravityform id=”1″ title=”true” description=”true”]