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