One of my first tasks at InterConn was to set up a billing system. We did not have many requirements, so we evaluated a lot of SaaS options. Now, there’s no shortage of billing SaaS — unfortunately, though, none of them could handle our particular rated billing process, except for JBilling, which was awesome but definitely out of our budget’s reach (in retrospect, Stripe might have been a good solution — I just didn’t know about it at the time).
So I set out to develop an in-house solution that could handle this task. A couple of months later, we had our MVP. The UI was based on ActiveAdmin — it was solid and it worked, but it was not very pretty or flexible (no modals for creating parent resources on the fly?!). As this was one of my first Ruby (on Rails) jobs, the codebase was a mess: I had stuffed everything into models and background jobs and there were no tests, because it’s much more fun to test things with your browser, right? Right?!
Those two months were hell. Almost literally.
If you have never worked on a billing system before, you’re completely unprepared to the ridiculous amount of requirements that kindly come as a byproduct of having to handle people’s money. Don’t worry, though: you’ll learn about all of them. The hard way, most likely.
You’re equally unprepared to the pressure that comes with building such an application: it’s not a regular web app, where the worst-case scenario is that your users lose access to their collection of cat GIFs. If this one doesn’t work, people will be paying more than they owe you and they might not like it when they find out (and they will, trust me). Or they will be paying less than they owe you, which is even worse: you can reimburse a customer, but it’s much harder to go back to them and say “Hey, apparently we screwed up and it turns out you owe us €2k! This is our bank account.” You just can’t do that. Not while retaining their trust.
All of this was five years ago, so I put it behind me (it took some counselling, though). In these five years, I built countless billing and payment systems. I don’t know if it’s because I took this field of work to heart or I’ve just come across a lot of projects where the billing component was so strong.
This is the article I wish was around when I started.
Data Integrity Is Paramount
If you only take one thing away from this article, here it is: you don’t fuck with data when you’re building a billing system. Here’s what happens otherwise:
- you create an invoice with an item referring to a product that costs $50 (at this point, the total is $50);
- you bill your clients for two years;
- you edit the product’s price to be $65;
- you end up with hundreds of partially unpaid invoices which your system sends hundreds of notifications for.
(This is a real story, by the way. It happened to… a friend.)
To avoid this, you need to make sure that invoices cannot be mutated after they have been sent to their recipient. This includes freezing all records associated with the invoice: the customer, the items and so on. Modifying an associated record should never impact an invoice, unless that invoice is still open (i.e. it was never sent). There are two ways to achieve this:
- you can simply create copies of the associated records for each invoice, or
- you can version your associated records, and link the invoice to a specific version of the records.
The second solution is preferrable, as it uses less storage space: generating 100 invoices for the same customer only requires a single customer version record (provided, of course, that the customer has not changed in the meantime). Versioning also allows you to implement a web UI to see the state of a record at any point in time and restore it. There are many versioning solutions for Rails, but the most popular is probably PaperTrail (this is what I ended up using in my apps and I swear by it).
Of course, you can also use a mix of the two methods if you prefer (versioning for important data and copying for small bits of information).
You should also make sure that no record is ever really deleted in a billing system: soft-delete everything and allow users to easily restore it through the web UI (or, at the very least, make sure that you can restore it). Again, there are many solutions for soft-deletion in Rails, but I recommend Paranoia.
Oh, and you should still keep daily backups. But that’s not specfically about billing systems, it’s just common sense.
Caching Is Okay, Really
I am writing a series of articles on how powerful relational databases are and how little of their power we harness in day-to-day development. Yet here I am, telling you to cache calculated values. And I have my reasons.
When you’re starting out, it’s simple to retrieve an invoice’s total without caching anything. After all, it’s just a matter of summing the amounts of the invoice items, right? Want to get all the invoices with a total greater than $100? Easy-peasy.
Then you introduce shipping costs. And quantities. And fixed-amount discounts. And percentage discounts. And late fees. Oh, and what about per-item discounts? Pretty soon, you’re the only person who can understand that SQL query. In a few weeks, you won’t be able to do it either.
This query is not only hard to read: it performs badly.
What if, instead, you could write it like this?
All you need to do is add a total_cents column to your invoices table and make sure to update it whenever the total of the invoice changes (e.g. an item is added, edited or removed). The advantages are alluring:
- no complex SQL queries as the one above;
- no performance bottlenecks;
- no app crashes because data has been deleted that was needed to calculate the value.
What are the drawbacks, really? That someone edits the field manually? You own the application and are responsible for ensuring that does not happen. Storage space? A few more bytes per record aren’t going to kill you.
Calculate and store everything. Discounts, fees, totals. One day, you’ll be grateful you did this.
Currency Is Not Your Friend
Saying that working with currency is a pain in the ass would be a huge understatement. However, the same can be said for regular expressions and a lot of other stuff. If you take a few precautions when designing your app, you can pretty much avoid any currency problems.
There’s a lot of material out there about how to work with currency, so I’m just repeating widely known stuff. But what would an article about billing systems be without a section on currency?
First of all, store money amounts as integers, not decimals. $100.50 should be represented as 10050 cents, not 100.5. Decimal columns are imprecise and lead to rounding errors: you’re practically losing precision with every operation you make on a decimal. For more information, see this StackOverflow answer. Language types have been introduced that deal with these issues (e.g. BigDecimal in Ruby, the Money API in Java), but cents are still the best and most language-agnostic way of storing an amount of money.
Secondly, always store the currency along with the amount, even if you only work with one currency. If you don’t, and the client suddenly decides to use EUR instead of USD, you’ll have a lot of refactoring to do before the app is ready. And God-forbid you forget you’re not storing currency and simply start using EUR from that point on. Now you have half your amounts stored in USD and half in EUR and no idea which is which.
You should use a library to abstract all of this stuff for you. I don’t know about other languages, but Ruby has Money. It takes care of storing, manipulating and displaying currency. It also supports currency conversion and automatic download of exchange rates.
As I said, I’m not the first one to write something on this topic. Have a look at these resources too:
One mistake people often make is tying their billing logic with their business logic. Invoices, invoice items, discounts etc. should be as general as possible in their design and implementation.
For instance, an invoice has invoice items, not products. Not all items are necessarily products that you sell. Some day, you might decide to start selling another services, and have separate billing logic for services and products (e.g. services don’t require shipping). If your models are structured like this:
You will have to do some (or a lot, depending on how complex your invoicing logic is) refactoring work.
If, on the other hand, you keep things simple and generic:
You will be able to do whatever you want with your invoices, like adding another kind of item as we said:
Even if invoice items can refer to both products and services, you will only ever have to deal with one type (InvoiceItem) when listing items, because all the details have been abstracted for you.
The recommendations I presented in this article are not enough for building a solid billing system: that requires a lot of thought as well as trial-and-error (possibly not with live data). If at all possible, you should avoid developing such a system yourself: there are a lot of off-the-shelf solutions and one of them is probably perfect for you — it’s just a matter of finding it.
If nothing fits your requirements, though, these tips can help you design an application that is easy to maintain and stays that way as the requirements of your business evolve.