While building saas products, especially one like AllBox, people pay to use your software. A lot of our software is billed pay-as-you-go where people only pay for what they have used, not a blanket cover-all cost. This post is about how one would model such a system in a very minimal / simple way and it invites discussion about how to best handle this situation.
If you're not familiar with the basics of accounting Beancount and Plain text accounting are good places to start
First let's describe the problem we are out to solve.
application
is what we are building. It could be anything at all and for the purposes of this post we assume a simple web application.gateway
is something that accepts payments on your behalf from customers via multiple methods of payment and then finally send the money to you. For this post we assume Razoypay.customer
is someone who pays you money in order to use your application
.There are a few lines of thought we went through:
customer
accounting and so should remain separate. Our goal is to make sure that customers should only pay for what they consume and vice versa. Nothing more.We create 3 tables in our database.
Account {
id integer [primary key]
name string
user_id integer
}
Transaction {
id integer [primary key]
refno varchar
metadata JSON
}
Posting {
id integer [primary key]
refno varchar
amount integer
metadata JSON
// -------FOREIGN KEYS
txid Transaction(id)
accid Account(id)
}
Note that the Account
table is not your SAAS account. It represents "accounting" accounts.
With this we create a few entries in the Account
table to represent "accounting" accounts. Each account has a name and is potentially linked to a user_id indicating that it belongs to that user. Accounts that don't belong to any particluar user don't have user_id associated with them. Accounts with <user_id>
in each of them are created with a foreign key to the given user. Remaining accounts are having user_id = null.
Income/Customer/<user_id>
: Used to represent the customer's money. This account will almost always have a negative balanceIncome/Razorpay
: Used to represent our payment processor.Assets/Customer/<user_id>
: Used to represent an individual customer's wallet on our application.Expense/Razorpay
: Used to represent the fees we pay to razorpay. It's usually some % of the transaction amount. This account will almost always have a positive balance.Expense/<service>/<user_id>
: This is used to repsent services inside our system that the customer is using. For example, storage , network etc. These accounts will tend to be positive balance.Transaction
row.Transaction
we create two Posting
rows.Income/Customer/1000 INR -100
representing the money leaving customer's bank account.Income/Razorpay INR +100
representing the money being received by Razorpay.Transaction
entry to represent this process.Transaction
we create the following Posting
entries.Income/Razorpay INR -250
showing the money leaving razorpay's accounts.Expense/Razorpay INR + 5
showing the fees we have paid to razorpay.Assets/Customer/1000 INR + 98
showing how much money has to be added to customer wallet.Assets/Customer/1001 INR +147
showing how much money went into the second customer's wallet. Now, how to actually "consume" services.
Transaction
record.Assets/Customer/1000 INR -3
representing the money leaving the customer wallet.Expense/Storage/1000 INT +3
representing the service account receiving money in exchange for supplying the service.By creating this per-service-per-customer account we also retain information about which of our services are being used to what extent by our customers.
Account
to get it's balance.sum
can get expensive if this is a frequent operation in your application
, you can introduce Clearing Wallets into the workflows and that should make calculations faster.transaction
entries between certain dates and filter to certain Account
entries that you are interested in and you can get statements for any account.meta
in both posting
and transaction
tables, you can use this info to track where certain money went.Income/Customer/1000 -> Income/Razorpay -> Assets/Customer/1000
easily by filtering on meta
fields using JSON filteringIncome/Customer/*
accounts and know the total money that customers have paid us.Expenses/*/*
to know how much customers have 'consumed'. If this is proportional to what our actual expenses are to run the platform we can estimate our actual 'expenses'.Assets/Customer/*
will tell us how much money is in our bank that is not yet 'consumed' by customers.This kind of setup is very easy to create in hindsight and works very well for a lot of simple operations. Perhaps you would like to add something? Do email in your comments :D