How to collect and disburse millions worth of payments securely.
This article aims to build a payment disbursement system, for the frequent and high worth of transactions at the order of 100s of Million.
The core responsibility of a fin-tech or neo-bank start-up is to make sure money movement happens securely from companies (clients) to respective agencies and employees. Varied payment methods and frequencies make the entire collection and disbursement process a bit complex. While maintaining hyper-growth and operating at the rate of 100s of million dollars, a single miss could make us lose money in a similar order.
What is Disbursement?
A company needs to create disbursement checks for a multitude of payment types including:
- Employee salaries
- Payroll expenses
- Payments to suppliers, contractors, and vendors
- Reimbursements to workers for out-of-pocket expenses
- Dividend payments to shareholders
- Profit distributions to other business owners
Responsibilities and features
There was a need to build a single authority system that takes care of the following responsibilities:
- Avoid duplicate payments: If beneficiaries get funds at regular frequencies due to dividend or salary. There’s a higher chance of duplicate payments due to the recurring nature of the code.
- Avoid over-payments: Disbursing the funds more than the funds collected from the company (including the in-flight amount) is a loss.
- Able to consume payment status: It’s not a rare occasion to have payment failure, this module should be able to rollback the calculation if there’s a failure.
- Check on the credit-limit: It’s common practice for businesses to allow credit-limit to their clients. At any point in time, the system should able to keep current credit per client.
Data models and Flow
Based on the above requirements, it seems a classic problem of transactions and its accounting. But with the variations, this module will be used as an intermediate account rather instead of a terminal bank-account. Let’s call it BalanceAccount, which will have three book-keeping variables for
- The true amount for payroll, loan, profit: will be stored in trueAmount
- Collections: will be stored in amountCollected
- Disbursements: will be stored in amountDisbursed
Important metrics to be observed
The above 3 variables are sufficient to determine important states of the accounting system.
Overpayment: Amount paid to the agencies higher than the actual liabilities.
Overpayment = amountDisbursed - trueAmount
FroentedAmount: Amount paid to the agencies before collecting from the company.
FrontedAmount = amountDisbursed — amountCollected
DeficitAmount: Amount needs to be collected from the company for tax-liabilities.
DeficitAmount = trueAmount - amountCollected
And Transaction model will be the same and generic with the source and destination of the funds.
Data Models
- BalanceAccount
- trueAmount: Current amount should be for the beneficiary (either for profit/payroll).
- amountCollected: Amount received from the company.
- amountDisbursed: Amount paid ahead to the beneficiary.
- companyId: Primary Id for the company.
- amountTxn: List of all instances of amount records attributed.
- collectedTxn: List of transactions collected and are attributed.
- disbursedTxn: List of transactions disbursement and attributed. - Transactions
- amount: Amount this transaction is carrying.
- source: The entity where the amount will be debited.
- destination: The entity where the amount will be credited.
- transactionDate: This date will help to attribute quarter and year.
Data Flow
Each Transaction will commit the amount in the BalanceAccount as per the source and destination. This commit has to be atomic and in isolation from the concurrent calls. In case of failure, there will be a reverse commit to rollback the amount.
The following diagrams illustrate the flow connecting dependent components.
Implementation with MongoDB
MongoDB 3.6 has vast adaptation across industries but does not support any notion of a transaction across a document or multiple documents. It does however guarantee atomic operations on single documents. Migrating DB to MongoDB 4+ could be a huge investment depending upon the vastness of the code-base.
Hence to work with MongoDB3.6, we will explore atomic operations restrict to a single document. As single document operations being atomic, MongoDB can only offer transaction-like semantics. It’s still possible for applications to return intermediate during the two-phase commit or rollback.
Commit
For commit, we need to push the transaction to the list and increase the amount atomically.
def commit(transaction):
BalanceAccount.objects(
company=transaction.company,
transactions__ne=transaction
).modify(
inc__amount=transaction.amount,
push__transactions=transaction
)
Rollback
If transactions under the window of rollback, we need to do the atomic reverse of the commit operation.
def rollback(transaction):
assert transaction.canRollback
BalanceAccount.objects(
company=transaction.company,
transactions=transaction
).modify(
dec__amount=transaction.amount,
pull__transactions=transaction
)
Sample Code Snippet using Django ORM
Further reading
To learn more about accounting and transactions, MongoDB has recommended an approach here http://mongodb.github.io/node-mongodb-native/schema/chapter9/.