How to collect and disburse millions worth of payments securely.

Arink Verma
4 min readFeb 17, 2021

This article aims to build a payment disbursement system, for the frequent and high worth of transactions at the order of 100s of Million.

Illustration showing payment collections and disbursements.

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:

  1. 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.
  2. Avoid over-payments: Disbursing the funds more than the funds collected from the company (including the in-flight amount) is a loss.
  3. 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.
  4. 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

  1. The true amount for payroll, loan, profit: will be stored in trueAmount
  2. Collections: will be stored in amountCollected
  3. 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.

Data-flow diagram representing connection with relevant entities.

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/.

--

--

Arink Verma

Code, arts, process and aspirations. co-Founded GreedyGame | IIT Ropar. Found at www.arinkverma.in