Building a world-class POS within a short timeframe
Vue.js
Vuex ORM
JavaScript
Jest
REST API
Capacitor.js
Swift
Appium
Cypress
SASS (BEM)
Shape Up
Cross-functional teams
Figma
TL;DR
0 weeks
From inception to release
0
Operators across 1,400 sites
£0 million
GMV processed
0 million
Orders processed
Built with speed and stability.
Responsible for setting the template internally on how to build with velocity, whilst maintaining exceptional quality, in distributed autonomous teams with Shape Up methodology; delivering major features on time for several continuous build cycles. Within a mere six weeks from project start, we had a fully functional first version of our iOS app on the App Store.
Critical team member.
Responsible for developing and managing key features across all three projects that made up Onvi's final product offering, including iOS app (Capacitor.js) and web apps (Vue.js). Integrating physical hardware, diagnosing and rectifying issues, optimising performance, and providing technical support to design and sales teams.
Close collaboration.
Collaborating with product designers from project inception to completion. Engaging in breadboarding, wireframing, prototyping, and delivering polished UI/UX designs for development. Serving as the team's cohesive force, facilitating timely delivery of key components for overall feature implementation.
Impressive growth.
Since its launch in 2020, the Onvi product suite was used to place over 4 million orders and take £66 million in GMV. We secured some big names in the hospitality industry, including Boxpark, Crêpeaffaire, Patisserie Valerie, and many others, totalling 660 operators across over 1400 sites.
Table of contents
What is Onvi?
Whether a café, local restaurant, or gourmet food truck, Onvi empowers ambitious hospitality businesses to start and expand their operations through a sleek, modern, and easy-to-use POS (Point Of Sale) system.
With Onvi's POS solution, customers can order and pay either in-person or through online ordering in just a few clicks without downloading an app, filling out a registration form or entering login information. Ultimately, Onvi delivers a seamless, uninterrupted customer journey; with the mission of becoming the single system of choice for SME operators.
I first joined Onvi in August 2020, a week after we had launched the first iteration of the base Mobile Order and Pay application. At the time, this was our only product, and during the over two years I was there, we would radically transform the product offering into a fully-fledged POS system to be used by over 1400 sites across the country, successfully process over 4 million orders, and take £66 million in GMV.
I thoroughly enjoyed being part of the team and helping the young company grow. As a crucial team member, I was a principal engineer on the three projects that made up Onvi's final product offering. These three parts, together, make up the all-in-one order and payment solution for hospitality businesses.
Onvi Order
Onvi Order is the mobile-optimised web application through which an operator's customers can order and pay for items from their table, via Apple Pay, Google Pay, or debit and credit card, without the friction of having to download an app or fill in registration details.
This web app was used by thousands of customers in bars, cafés and restaurants daily; it was an essential cash stream for our operators. Therefore, its stability, security, and speed were paramount.
To ensure stability, I enforced a rigorous testing regime. Every Vue
component and utility I wrote were accompanied by comprehensive unit and integration tests using Jest
, and all user flows were accompanied by suites of integration tests using Cypress
. This included all bugs and issues that came to light, which were also heavily tested afterwards to avoid regressions.
Speed was also a significant consideration. Not only did I have to account for internet speed, but I also ensured that JavaScript execution was smooth and fast. This ranged from deferring the loading of scripts until they were required to delaying non-critical function calls to ensure smooth CSS transitions during DOM operations.
Onvi Control
Onvi Control is the central hub used by operators for managing their business. From here, operators can:
- Create and manage their menus to be accessed by customers on Onvi Order and Onvi Serve through Menu Enrichment.
- Manage availability for menus, categories, items and modifiers.
- Monitor and update orders coming into their sites.
- Download marketing assets (including QR codes customers can use to access menus on their phones through Onvi Order).
- Deep-dive into Analytics and see what's driving their business performance.
Onvi Control was also built using Vue.js
, with Jest
and Cypress
used for testing. Several features utilised a complex data structure, such as that of Menu Enrichment. We designed the data structure to be returned from the REST API to support many-to-many relationships, which resulted in the use of Vuex ORM
(Object-Relational Mapping), which helped to normalise the data and significantly improve data storage and retrieval.
To differentiate from other POS systems, we researched how to integrate analytics into Onvi Control. This would bring operators closer to their sales data in a simple, easy-to-consume way. From viewing daily sales performance to seeing which items are selling the best, our goal was to provide greater insight into how their business was doing.
Working together with our Data Scientist and Product Designer, I worked on integrating Sisense.js
, a third-party data visualisation service. Since we had several pages for which we wanted to show charts and allow the operator to filter them by site, date range, sales channel, and more, I had to architect the implementation modularly. This would reduce the complexity of the integration, allowing for greater reliability and future scalability.
Onvi Serve
Onvi Serve is an app for iPad and iPhone that can be downloaded from the App Store. From here, operators can:
- Take orders at the till, with either cash or card payment.
- Manage live orders through the KDS (Kitchen Display System) across the table, pickup, and delivery channels. Quickly see, at a glance, all your orders and understand what action needs to be taken.
- Set up printer routing. Print jobs only where they are needed, based on menu categories. For example, you can route customer receipts and snacks to the counter, hot food to the kitchen, and drinks to the bar.
When we first moved from just a Mobile Order and Pay application (Onvi Order) to building a fully-fledged POS, there were several things to consider. We knew we had to offer card payments through a till, and we knew we had to couple this with receipt printing so that customers could receive purchase receipts and the kitchen could print tickets to manage their orders. This would require Bluetooth and USB / Ethernet connectivity to connect card readers and printers to the app.
What is Capacitor.js?
After researching and testing several technologies, we agreed to proceed with Capacitor.js
to build our native iOS app for iPhone and iPad. Limiting to iOS
only could ensure a baseline quality user experience. We tested many Android devices, from Samsung to Google tablets, and as expected, we discovered that our prototype Capacitor.js app would run slower on one device than another. You would click on a button, and the device would take a second to respond. It was an inconsistent, jarring experience, which would have made optimising for all devices a time-consuming task – time which we did not have. And more importantly, users cannot wait for the device to respond whilst taking orders in the heat of service. Imagine a customer dictating their order to you whilst you're still adding the first item to the order due to how slow the device is. Sucks, right?
Using Capacitor.js
also meant that we could use our existing architecture with Onvi Control. Components, ORM models and helper functions could all be shared across these two projects. Consequently, we created an NPM package called Onvi Core
to share the common assets between both applications. This allowed us to develop significantly faster and ultimately deliver the Onvi Serve app to the market within only a few months.
How does it work?
If you're familiar with React Native
, you'll understand the concept of bridging. Capacitor.js
operates on the same principle, allowing bidirectional and asynchronous communication between the JavaScript layer and the Native layer (Java/Kotlin for Android and Objective-C/Swift for iOS). This is especially useful when we want to utilise native functionalities not directly accessible through JavaScript, such as connecting to external devices via USB or Bluetooth.
Let's take the example of discovering and displaying a list of available printers in our Onvi Serve app. We'll break it down into three components:
StarPrinterPlugin.js
This component handles the interaction with the native code from the JavaScript layer. We register our plugin and call functions that correspond to definitions in StarPrinterPlugin.m
.
StarPrinterPlugin.m
Here, we register our native functions and inform Capacitor
about which functions are accessible from the JavaScript side. Asynchronous communication is facilitated using CAPPluginReturnPromise
to handle the back-and-forth between the two layers.
StarPrinterPlugin.swift
And finally, this component contains the native implementation. We register our StarPrinterPlugin
with Capacitor
and define the discoverPrinters
function. To pass the response back to the JavaScript layer, we use call.resolve
to resolve the promise with the desired data.
For more in-depth information on how Capacitor.js
plugins work, you can refer to their official documentation available here.
What does the user see?
From a user's POV, their experience using the Capacitor.js
app is no different to using a native app. Therefore, with Onvi Serve, we had all the functionality of a native app, minus the learning curve, heavy upfront work, and maintenance required to build a fully native iOS app.
Let's consider the example of connecting the Stripe Wisepad 3 card reader to the iPad.
Delivering at velocity
Scalability roadblocks
In the first few months of my time at Onvi, we adopted an agile two-week sprint workflow. Although this allowed us to ship new features at a reasonable velocity, we encountered problems as our team, number of projects, and complexity grew. Some of these problems included:
- Two weeks was not enough time to ship a feature, if ship anything at all. Tickets frequently overflowed into the next sprint, with no clear deliverables or delivery date.
- After a sprint, we'd quickly jump into the next one with little time to assess, learn and improve our processes.
- There was a disconnect between product and engineering. The product team would create and assign tickets just before the engineers would start to work on them. There was little collaboration between teams, with little time to scrutinise and assess ways forward. Ultimately, there was no joint ownership of the work.
To scale effectively, we needed to adopt a new approach to working. One that allowed us to re-focus on the product and customer needs and allowed us to not just guess when work would be finished but almost guarantee (with small tolerances) when it would end.
From Sprint to ShapeUp
Whilst discussing ways forward, our Head of Product suggested we give Shape Up: Stop Running in Circles and Ship Work That Matters a read.
ShapeUp highlighted several of the issues we were experiencing, and after trialling and eventually adopting full-time, our internal operations had been overhauled to a remarkable degree. We ultimately increased our efficiency and released significant new features regularly whilst maintaining high-quality standards throughout the product and service offerings.
How does it work?
Using Basecamp's ShapeUp, we worked in six-week build cycles.
Six weeks is long enough to build something significant from start to finish whilst being brief enough for the team to sense the approaching deadline from the very beginning. Almost all of our major features were built and released in a single build cycle.
A fundamental principle is that projects do not get an extension if they exceed their deadline. We ensure this in the Shaping phase, where time estimates are taken out of focus in favour of the appetite. Instead of asking how much time it will take to do a piece of work, we ask: How much time do we want to spend?.
An appetite is entirely different from an estimate. Estimates start with a design and end with a number. Appetites begin with a number and end with a design.
Vitally, the project is handed to small, self-organising, integrated teams. Each "squad" generally consists of one frontend engineer (such as myself), one backend engineer, and one product designer. As a team, we are responsible for moving the project forward and ensuring its timely delivery. We define our own tasks, make adjustments to the scope, and work together from the offset to define, design and architect the solution. We are a team, and we are all responsible for each other's work.
This completely differs from other methodologies, where managers chop up the work and programmers act like ticket-takers.
I appreciate this has been a brief overview of our workflow at Onvi. Still, I would wholly recommend giving ShapeUp a read - it does a far better job than I have done here at explaining its benefits and how to use it effectively. Additionally, my former colleague, Chris Boakes, successfully introduced ShapeUp at Zoopla and shared his experience in an amazing blog post available here.
If you've experienced any of the problems above, I would definitely give ShapeUp a try; it may transform your operations as it did for us.
Delivering 'Printer Routing'
Before this feature, the operator could connect a single printer to their iPad, where all customer receipts and kitchen tickets would be printed. This was great for food trucks, but what if your setup was slightly more elaborate? Clients wanted a way to determine where specific menu items would print to. For example, you could set "Wine" to print to the bar, whereas "Main courses" could print to the kitchen. This would eliminate the need for kitchen staff to run to-and-fro a single printer and manually distribute tickets themselves, improving kitchen workflow and efficiency.
This pitch and several others were placed on the betting table and debated. As a team, we agreed to take on the project, and the squad was assigned.
Our squad for developing this feature consisted of:
- 1x Product Designer
- 2x Software Engineers:
- 1x Frontend Engineer (me)
- 1x Backend Engineer
- 1x QA / Automation Engineer
- 1x Data Scientist