Rethinking price plans to support a fast growing and changing customer base

Steef Janssen
The Qonto Way
Published in
9 min readMar 23, 2022

How we prepared for Qonto’s next one million business customers

During the summer of 2021 in just 2 months we transitioned all our apps, as well as our website to support our brand new price plans.

We revamped our price plans with the aim to scale our customers with us. First, we empowered them with more freedom, more possibilities, while offering them twice as much plans to chose from than before. We also made our pricing more transparent, especially what’s included in each of our plans. For anyone who is interested in seeing our brand new price plans in action, you can learn more on our beautiful website.

As engineers laying the foundations of a subscription-based application, we do not always foresee how software will evolve and scale over the years. One thing is certain: transitioning to a new pricing model is always challenging.

As a frontend team, we think it’s interesting to share how we made our Web app more predictable, scalable and a lot easier to test!

Situation: How did we handle Pricing before?

Before July 2021, we managed 3 price plans: ‘Solo’, ‘Standard’ and ‘Premium’ in our web app (built with EmberJS).

Our pricing plans before July 2021

We originally developed all of our feature restrictions logic based on the name of the price plan. For example, when a user was on a ‘solo’ plan, they could not access the part of our app where employees can make transfer requests.

The same principle applied for non-user limitations, like only having access to one bank account for example.

Our app was riddled with conditions to exclude access for the lower plans to certain parts of the app.

Our bank accounts page, when you cannot create multiple accounts

First Step: From a ‘Solo’ plan to a ‘solo’ line-up

With the introduction of the new price plans, we split up the single ‘Solo’ plan aimed at single user organisations to a solo line-up of three plans.

Our three new plans in the ‘solo’ line-up

At the same time we also needed to support our existing customers on our ‘legacy’ plans.

It did not take long for us to realize that the existing way to limit access was not scalable enough. Rewriting our whole app to implement all the new conditions to exclude the plans try and exclude plan access was going to be a lot of work.

Also, what if we needed to introduce a new plan or wanted to change a feature on an existing plan?

The anatomy of a price plan, features and limits

Building out feature restrictions based on ‘what plan the customers is on’ was not the correct approach. But what if we decided this one step further by talking about the limitation itself?

Take the difference between two solo plans, for example Solo and Basic. We present the Smart plan as a bundle of more advanced ‘features’ such as multiple accounts or bulk transfers.

A price plan comparison of features and limits

Next to that, we present a Smart plan including the ‘Accountant Pack’. With this plan, the customer can invite an accountant and provide access to features that meet their respective needs.

At Qonto, we believe information about features and user limitations is the way a customer selects the best plan for their business. Defining our customers by the features we offer them is far closer to the reality than defining them according to the price plan they subscribed to.

We actually defined these features on a price plan, so if we access the price plan associated to a given organisation, it should look something like this:

Not only did this apply to our new ‘solo’ line-up, but also to our legacy plans ‘Solo’(I know, confusing right), ‘Standard’ and ‘Premium’.

This is pretty straightforward to convert in our app:

Next to features, we also have to deal with limits. One good example would be the number of users allowed. All of our new ‘solo’ plans would be targeted towards businesses with only 1 user.

So parts of our app like allowing users to make transfer requests or inviting new team members should not be available to customers on solo plans.

Like features, we could implement checking for the user limit property on the price plans.

Configuration over code

Early on, we decided to keep all of this information centralized on our backend so all our apps have one source of truth. The main reasons, maintainability and predictability!

This configuration does not only apply to our web app, but also our mobile apps. Defining this in every codebase increases the chance they will not be in sync.

As we were rewriting our apps to move away from the many conditional statements involving different price plans, we actually found inconsistencies that had crept in over the years. We expected from our teams to not only know what a specific price plan was, but also how it relates to the other plans and make a decision on what to show to our users.

Another way to look at it is in terms of complexity theory; that same policy can be implemented on each front-end, but having it on a single system reduces overall complexity.

Our main goal from the beginning was to support our existing customers on a legacy plan as well as our new customers. Moving to this approach, we support these plans out of the box as well, for free!

Half the way through: Why we rely on teamwork

None of this would’ve been possible without a strong focus on solving this as a team. Although the Frontend team focuses mainly on our web app, rethinking our price plans also involved our a lot of colleagues from Backend, iOS, Android, Product or Website teams. Not to mention all the incredible teams involved outside of Tech & Product…

At Qonto, we activate teamwork as early as possible, through technical workshops and writing technical proposals requiring reviews from a larger part of the team. By doing so, we clearly mapped out all the implications and spotted issued early on. We rather take this cost upfront than deal with it at a later stage, especially when dealing with such a huge undertaking. If you want to learn more about this process, I can recommend this excellent article by our CPO Marc-Antoine.

After three months of planning, writing specs, rewriting the app, reworking conditions and updating our visuals, we rolled out our new solo line-up to new and existing customers in July 2021. Although being slightly nervous to see if it all worked out, we had a super smooth rollout with no users impacted by our changes. Mission successful!

Second Step: Introducing a new teams line-up

But we didn’t stop here: our next ambitious goal was to release Teams line-up, our dedicated plans for SMEs and Startups, in September 2021. This was our first test to see if our approach was as scalable and predictable as we designed it to be.

All the new plans within this line-up were built on the same set of features and limits. The main exception being — of course — the amount of users an organization could invite!

As soon as we turned on the new Teams line-up in our staging environment we carefully provisioned a full week to make sure that every new price plan featured the ‘correct limitations’. In the end, we didn’t need more than two days: it just worked as expected!

Writing tests with our customer’s reality in mind

Although predictability and scalability are some of our main drivers, writing tests is another significant part of our job.

We have a high level of confidence in our test scenarios, thanks to high test coverage (all apps are beyond 90% coverage) and to our implementation of Mirage, that allows for advanced mocking of our API. This approach allows us to easily create dummy data for our test scenarios.

Since we use Ember.js, we also rely on the QUnit testing framework to run all unit, integration and acceptance tests. QUnit’s universal setup is perfectly matching the way we write test scenarios.

We typically use custom prepare functions to setup the scenarios. These can be small when it comes to Unit tests, but tend to grow larger when it comes to integration or acceptance tests. As an example, an acceptance test could follow this logic:

  • Setup the application
  • Create a fake organization
  • Create a user and authenticate them
  • Make sure the user is a member of the organization
  • Generate a bank account for the organization (we are a financial institution after all)
  • Create the fake data set needed (transactions, transfers, cards, etc…) to test the scenario.

Whenever we tested a part of the app which relied or was impacted by a price plan, we had to set up our tests with that in mind:

Under the hood, we would rely on a ‘fixture’ for each price plan, returning all properties:

Now the easy thing would have been to simply update these fixtures with the new price plans and we would be done, right? But there are two issues with this approach:

  • We need to keep these fixtures in sync not only with Backend, but also with our other apps to make sure we all test against the same data.
  • We get more information than we need in the end. When testing if we want to show a user a button to create a new account, we do not really care about how many users the plan includes.

Since we’re already not considering the plan itself, but rather the feature or limits in our app, we can apply this approach directly to our test scenarios as well.

This approach set us up for more scalable testing. We only prepare the scenario with the information we need. If the available features on a plan happen to change, if we introduce new plans, or introduce new features, the result still remains the same : our test scenarios are still valid and work.

Conclusion: We are always learning

When you start writing a new subscription-based software, it might be hard to have a clear mind about how it will evolve and grow. In hindsight, we wish we started writing our software from day one with the hyper-growth reality of today’s Qonto in mind.

It is one thing to design your software based on existing conditions, but it is a very different one to support its evolution and scalability over time. Still, based on our experience, it doesn’t have to be painful or complex.

Instead of identifying users based on what plan they have, consider grouping them based on the features they have access to.

“The source of truth should be the source of policy.” In other words, the decision making on whether a feature is supported in a plan should be done by the same system that is authoritative on it.

If at some point you have to introduce more features, or make your software evolve, the logic we presented may help you scale and grow at the same time as your customers, and so will the value you bring them.

--

--