Designing a Subscription and Billing System for a Software-As-A-Service (SAAS) Product

Prelude

When I was hired into my first real job as a software developer (and the one in which I am still employed at this time of writing - July 2022), I was the sole developer (after a one-month overlap with the previous dev) for a small SAAS company.

The company had existed for nearly ten years, and had a (somewhat) stable product, with a relatively small but (somewhat) stable customer base of a few-hundred paying clients. I inherited a lot of tech debt, the most significant of which was the code which actually handled customer's subscriptions.

I began work on a re-write of our subscriptions and billing code last September, and am only now - almost 10 months later - "finishing" the work. It has been a very involved, very instructive effort. Although I had the original code to work from, I consider my final product to be more or less created "from scratch", as I completely re-designed the data models, re-wrote almost every single line of relevant code, and wrote significant documentation, specs, and test cases - none of which had previously existed.

In this blog post, I will attempt to re-live this effort, in the hope that it will be educational for myself and any future readers. I will attempt to walk through the process of designing the system from beginning to end in a (somewhat) realistic way. That is - I will start simply and build in complexity, as one would when designing the system from scratch. I may even intentionally take us down some faulty paths, but I will attempt to do so with the benefit of a year's worth of experience, and lay things out in an unrealistically clear way.

Introduction to the Product and the Problem

Suppose you are responsible for an office space, a manufacturing facility, or a small business. You want to know who is coming and going, and when. You would like to track this information automatically, and be able to download reports of visitor traffic. This may be for health or legal compliance, or simply to gather data. You would like people in your organization to be automatically notified if someone has come to see them. You may have other requirements for visitors as well, such as questionnaires, safety videos to watch, or agreements to sign. With an iPad and a subscription to our product (ZapIn), you can do all of this. You install our app on an iPad, configure your specific set-up using our customer website, and have your visitors sign-in using the iPad. This is the business of Visitor Management.

To use our product (hereafter simply called "the app") on your iPad, you must first make an account on our customer website ("the dash"). This is free to do, but you must verify your email address and provide detailed information, such as your address. Now you may use the product, with unlimited permissions, for the duration of a 15-day trial period. Alternatively, you may use our "5-for-free" product, which allows free use forever, but only to a limit of 5 visitors per day. Assuming you continue with the main app, you may buy a subscription at any time through the dash. You must buy one subscription for each iPad you want to use. That is, if your space has three entrances and you would like an iPad mounted at each one, you will need to buy three subscriptions. Or if your business works out of multiple buildings, you will similarly need multiple iPads with a subscription for each one. You may also select from two subscription tiers "standard" and "full". Naturally, full subscriptions are more expensive and grant access to more features. You may choose to pay monthly, or annually for a 10% discount. Furthermore, you may choose to pay a separate monthly fee for SMS service, which allows you to do things like have SMS messages automatically sent to people if a visitor has arrived to see them. You may pay for your subscription by automatic credit-card payment, but if you select annual payment you also have the option of paying manually, e.g. by wire transfer. However, the SMS addon is only available with credit card. If you purchase your subscription during the 15-day trial period, you will still retain the benefit of the trial period and only be billed at the end. Of course, if your trial has expired, then any subscription you purchase will begin (and be billed) immediately.

Confused yet? You should be! There is a lot going on, and we have not even considered questions like

Breaking it Down

As developers responsible for this system, there are three things we really want to be clear on:
  1. What states can users be in? E.g. "trial period", "expired", "subscribed", etc.
  2. What underlying data models will capture the full range of possible states, in a way that is [insert fancy words here]
  3. When our code is running, what kinds of things will it want to ask about billing/subscriptions?
Unfortunately, this is a lot to wrangle. Let's go through this one at a time.

User states:

Based on the description above, it sounds like we would have: Later we will see that this list is incomplete, but it is a good starting point which covers the major categories.

Data model:

Our software uses Mongo, which is no-sql. If you were hoping to read about sql, sorry, this article will disappoint you.

We could imagine having two collections:

User: Subscription: In this scheme, a User knows whether they are verified or not, and they know when their trial period expires (if any), and they know where to look up their subscription info (if any).

A Subscription knows its quantity, plan type, payment interval, expire, payment type, whether or not SMS was added, and when the SMS expires.

This might seem pretty straightforward, but there's actually a lot to unpack here.

Linking:

First of all, it's worth noting that this kind of singly-linked connection where the User model knows about its subscription, but the Subscription doesn't know it's User, is totally great. I agonized about this a lot when I started this project, and I eventually decided on this, and I never looked back. Could other ideas work? I'm sure they could. Would I ever bother trying them? Probably not. This is actually a very simple but very critical idea - everything starts at the User. We aren't really ever going to ask questions like "does the owner of this subscription have more ipad sessions than they are paying for?", we are going to ask questions like "Does this user have more sessions than they are paying for?". We really aren't even going to ask something like "is this subscription expired?". We are going to ask "is this user's subscription expired?".

It's a subtle distinction but it makes a world of difference. Everything starts at the user. Remember that.

Multiple Subscriptions:

I set a rule right at the beginning - you cannot have more than one Subscription. Be careful - don't confuse this with a subscription's quantity! You can have a Subscription for seventeen ipads, but that would still just be one Subscription - with a quantity of 17. Although this case study is actually fairly simple, it is my fervent belief that this is the correct approach in general. Decide what different types of product you want to offer, and allow users to have 0-1 Subscriptions for each. Those Subscription objects can internally have a quantity, but they should fundamentally represent different things, and the rules about what a user is allowed to have should be very clear.

SMS "Add-on":

In this data model what we've actually done is chosen to have two "products" and one rule about how the users are allowed to buy them:
This is enforced by the data model proposed above - information about whether or not the user has SMS is attached to the Subscription data, which means that the user couldn't have an SMS subscription without a Subscription for the app. The data model also only allows for one type of Subscription: e.g. "full monthly for 4 iPads, expiring on Dec 21, 2023, paid by card". The user could not have this subscription and also hold a different subscription with different properties at the same time. However, the SMS subscription is not truly an "add-on" to the main subscription, because it can have its own expiry date.

The SMS addon being its own subscription is not some kind of recommendation - it is a choice we made about how our subscription model works. I don't know or necessarily think that it was the best choice, but it was what we went with. We decided that the SMS subscription would only be payable by credit card on a monthly basis, and as a result of that decision, it must be separate from the main subscription. Otherwise, if we treated it as a true "addon" that firmly attached to the subscription, users with a yearly or manual-payment subscription would be unable to get the SMS addon. If we didn't decide "SMS must be paid monthly by credit card", then we could treat it as a true "addon" to the main app subscription. The really important thing is to be very clear about what products are being offered, and what the rules are about how users are allowed to buy them. These rules should be as simple as possible, or just nonexistent.

What do we want to be able to ask?

The goal here is to create a sort of API for querying the subscription information of the users. Theoretically, other developers working on the codebase should be able to call functions that you write, to return information that they want to know, without ever having to worry about the data model or anything else. So in this section, lets forget almost everything we've talked about so far and just start asking questions about a user: This is pretty much it. Again, the emphasis here is on abstracting away the data model and asking useful questions. For instance, you almost never actually care when a subscription's expiry date is, unless you are displaying this information somewhere. What you're really interested in knowing is "is this user's subscription expired?".

Recap

Okay. We've talked about a lot. Let's summarize what's happened so far:

- We've had a discussion in plain English about how the subscriptions work.
- We've translated this discussion into three things we care about as designers of the system:
    - User States
    - Data Model
    - API functions
- We've created a few rules:
    - The SMS subscription is considered a whole separate subscription, not a simple add-on.
    - Users can only buy an SMS subscription if they have an app subscription.
    - Users cannot mix and match subscriptions. They are allowed to have 0-1 app subscriptions and 0-1 SMS subscriptions.

Payments

We haven't touched on this at all, and you've probably been wondering this whole time - its fine to have all these data models and code, but how do we actually collect and process payments?

In our case, there are two ways, depending on the payment method. For credit card payments, we use Stripe, and for manual payments, our finance staff have to send invoices and collect wire transfer payments from the customers.
Stripe:

back