When it comes to recurring billing there is a bewilderment of services available: Recurly.com, Chargify.com, Spreedly.com, CheddarGetter.com, and so on. Each has pros and cons, but our billing needs were a bit involved, and since we couldn’t find a service that seemed to be a perfect match we decided to create our own system, using Braintree as our credit card payment processor.
Full disclosure, it did end up being quite complicated, and for those with simpler needs it is certainly worth investigating any of the above - Braintree even has its own simple recurring billing system built in. However, we’ve now got a system we’re confident in, which is super easy to customize as our needs mature, over which we have full control, and which will never go out of business on us or suffer downtime or start charging us more or…you get the idea.
This isn’t so much a guide as a collection of stuff that we figured out along the way. It may be useful for you if you are considering taking the plunge yourself.
Overview
We needed a system that would allow the following:
- Credit card as well as invoice-based billing
- Usage-based payment packages, with a minimum monthly fee
- Trial period, customizable per customer
- Millicents-based (usage is based on attributions, which cost e.g. €0.000125 each)
- Customer or account manager can upgrade/downgrade package any time
- VAT based on customer location and VAT status
Our customers’ payment details are stored in an Account
model, the billing-relevant parts of which look like this:
braintree_customer_id: string, # braintree's 'vault' id for stored credit cards
billing_start: date, # prior to this date a customer is in their trial period
billing_day: integer, # the day this customer will be charged each month
invoice: boolean # customers can pay via invoice or credit card
vat_number: string # don't charge VAT to EU customers with a valid VAT number
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
|
As you can see, when an account is created we set up a few things. PaymentPlanTemplate
refers to our three ‘default’ payment plans - Basic, Business and Enterprise. Each PaymentPlan
has a different attribution cost and minimum fee. Since each account has its own collection of PaymentPlans
, our account managers can easily edit a customer’s available payment plans as needed.
You may wonder why billing_day
is limited to 28 - well, that’s our cheeky way of bypassing the complexity involved in end-of-month calculations. If billing_day
can be anything up to and including 31 then things get pretty gnarly in shorter months (god forbid you should ever experience a leap year). We had a stab at it (here’s a representative sample), recoiled in horror, and decided it wouldn’t kill anybody who signs up on the 29th to pay on the 28th of each month.
Saving credit cards
Braintree takes all of the complication out of storing customers’ payment details. Using their braintree_ruby gem’s awesome transparent redirect feature we can easily set up a form in our user’s profile to save a card without any sensitive data ever touching our system:
The controller:
1 2 3 4 5 6 7 8 9 |
|
The view:
1 2 3 |
|
…yep, that’s it! The form gets submitted directly to Braintree, never touching your server, and braintree responds with success or failure allowing you to store the customer’s braintree_customer_id
for use at billing time. The Braintree docs are a great place to go looking for more info if you can’t quite believe it’s that simple.
The recurring part
As you would expect, at the heart of our billing system is a recurring cron job running a rake task that checks who should be billed today and does the necessary leg-work. Simple scopes on the Account model provide us with our victims targets:
1 2 3 4 5 6 7 8 9 |
|
process_async!
comes from the excellent Sidekiq gem which handles all of our background jobs.
Most of the complexity in our Receipt.generate
method comes from figuring out what period we are billing the customer for. Everything else (how many attributions the account had, which payment plan is active etc.) can easily be figured out from there and is much more dependent on how you calculate cost, so I won’t go into detail.
Remember billing_day
? That’s all you need to work out which billing period an account is/was/will be in on any given date:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
|
Hopefully this is fairly self explanatory - if the date’s day-of-the-month is smaller than (ie. before) the account’s billing day, then the period must have started the month before and will end this month; if bigger (after) then it will start this month and end the month after.
Happily, ActiveSupport takes care of a lot of the complexity here, as we can use crazy magic talk like date - 1.month
to cover a variety of ills.
Once we know what period we’re talking about, it’s trivial to work out how many attributions the account had and multiply them by the active payment plan’s attribution cost.
We end up with a receipt that looks something like this:
1 2 3 4 5 |
|
The billing part
So we now have a receipt all calculated and ready to go. What happens next depends on what sort of customer we are dealing with:
Credit card customers
This is dead simple, thanks to Braintree. We submit a transaction like so:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
|
The CalculatorService
stuff is there to calculate VAT etc, which is based on the customer’s location and whether or not they have submitted a valid VAT number. We use composed_of
in the Receipt
model to enable us to call methods like to_euros
on a value:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
|
Invoice customers
This is even simpler, as we simply generate a PDF invoice from the receipt’s show action, using our wonderful shrimp gem, and email it to the customer, who cheerfully pays us. Job done!
User experience
Obviously when money is involved you want to be as transparent as possible. One big advantage of everything being in our own system is that it’s simple to show our customers all the information they need about their billing status, such as their previous bills and current status:
or which cards they have saved with us:
Not having to rely on external APIs for anything except credit cards also makes the entire dashboard a lot snappier, which is a big plus!
Conclusion
The hardest part of building a recurring billing system is designing it in the first place. We were lucky enough to be able to take it at our own pace and the design grew out of the implementation process quite naturally. No one part of it is particularly complicated, but taken all together there is indeed a great deal of complexity involved, which is why so many people advise against ‘reinventing the wheel’ and making your own. I haven’t even touched on how we deal with trial periods and payment-based authorization - this will come in a later post - but I hope this gives some idea of what is required and what you should think about if you decide to roll your own. Above all - ENJOY RESPONSIBLY.
NOTE: ‘attribution’ refers to a click or an install that we have tracked for a customer.