DEV Community

Cover image for How to Build Loan Disbursement, Interest, and Repayments with Blnk
Etop - Essien Emmanuella Ubokabasi for Blnk Finance

Posted on • Originally published at blnkfinance.com

How to Build Loan Disbursement, Interest, and Repayments with Blnk

A practical guide to building a lending ledger with the Blnk Ledger. Learn how to safely disburse loans, charge interest, track repayments, and prevent customers from accessing more credit than they’re allowed.


When build a lending product, the one mistake no developer wants to make is letting customers access more credit than they are allowed to access. If this happens, the company could lose millions of dollars with no clear way to recover it back from customers. For younger startups, this “fraud event” could mark the end for them.

In most cases, issues like these happen because of how their ledger is designed.

In this article, you’ll learn how to avoid this mistake using the Blnk Ledger. We’ll use Lumen Credit, a loan app that allows customers borrow money and repay at a later date plus interest.

Let’s dive in!

Step 1: Setting up your ledger

Since we’re using Blnk, make sure you have a deployed Blnk instance. If you don’t have one, we wrote a guide here on how to get started.

Before we start working with the ledger, let’s review Lumen Credit’s features. For each signed up customer, we want to:

  • Disburse a loan when requested
  • Charge interest on the amount owed
  • Track repayments from the customer. To do this, we’ll need to track two separate sets of balances for the customer.
  1. Loan balance: This tracks the amount owed by the customer at any point in time. When a customer requests a loan, money is deducted from this balance and the overdraft created tracks the amount owed.
  2. Main balance: This tracks the amount available for the customer to spend.

Ledger table

The ledgers table shows the Customer Main Ledger and Customers Loan Ledger after creation, along with the General Ledger.

To implement this architecture in our ledger:

  1. Create two ledgers folders: Customers Loan Ledger to group all loan balances in one place; then Customer Main Ledger for all main balances. Read docs.‍
  2. Create an identity for each customer: Add each customer as an individual identity. We’ll use this to link the customer to their respective pair of balances in the next step. Read docs.‍
  3. Create the balances per customer: Using the newly created customer's identity, create the loan and main balance under their respective ledgers. Read docs.

Balances table

The balances table shows two balances created for Billy Brian: a main balance and a loan balance, both starting at 0.00

Note: For each ledger, balance, and identity created, Blnk assigns a unique id. Keep these ids, you’ll need them for the rest of the steps.

Step 2: Disbursing a loan

At Lumen Credit, loan disbursement goes through three steps:

First, the customer requests a loan amount. Next, the system checks if the customer is eligible for that loan. This could be a set of conditions such as: KYC tier, location, credit score, loan limit, etc. Finally, if all checks pass, the loan is approved and sent to the customer; if not, loan is rejected.

To model this with Blnk, we’ll record this as an overdraft transaction moving from the customer’s loan balance to their main balance.

Money Movement Map

Money movement map for inflight transaction from the customer's loan balance to their main balance. View the full map.

Since we need to record the loan request first but delay execution until it passes our checks, we’ll use the inflight transaction feature to temporarily hold the transaction while we validate the conditions.

In simple terms, Inflight is how you ensure that a transaction doesn’t get applied to the destination until you tell it to. In our case, until we confirm that all conditions are met, the customer will not have access to those funds in their balance. Learn more about Inflight.

curl -X POST http://localhost:5001/transactions \
  -H "Content-Type: application/json" \
  -H "X-blnk-key: YOUR_API_KEY" \
  -d '{
    "precise_amount": 50000,
    "precision": 100,
    "currency": "USD",
    "reference": "LOAN-DISBURSE-001",
    "source": "bln_LOAN_BALANCE_ID",
    "destination": "bln_MAIN_BALANCE_ID",
    "description": "Loan disbursement to Billy",
    "allow_overdraft": true,
    "inflight": true,
    "meta_data": {
      "transaction_type": "loan_disbursement",
     "customer_id": "CUST_001",
    "loan_amount": 500

    }
}
Enter fullscreen mode Exit fullscreen mode

Note:

  • The precise_amount field uses the smallest currency unit (e.g., cents for USD). With precision: 100, 50000 represents $500.00 (i.e. 500.00 x 100).
  • inflight: true is how you enable inflight on a transaction.
  • allow_overdraft: true tells Blnk that the source balance (i.e. loan balance) should be allowed to go negative.

Why negative for the loan balance?

This is because it allows us to represent debt on the loan balance. Once we submit this, we’ll get a response like this:

{
  "transaction_id": "txn_74382c5a-ee16-41c8-83a5-74464c34051a",
  "status": "QUEUED",
  "inflight": true,
  "precise_amount": 50000,
  "amount": 500,
 "source": "bln_b76a741f-4f1a-4018-b1a9-e7251e6f8541",
  "destination": "bln_1a817670-e647-4089-b1ba-e4d1a89e24f1",
  "reference": "LOAN-DISBURSE-001",
  "allow_overdraft": true,
  "meta_data": {
    "transaction_type": "loan_disbursement",
    "customer_id": "CUST_001",
    "loan_amount": 500
  }
}
Enter fullscreen mode Exit fullscreen mode

Make sure that you keep the transaction ID.

Next, you check all of the conditions for the customer:

if (kyc && loan_limit && credit_score) {
    return approve_loan
} else {
    return reject_loan
}
Enter fullscreen mode Exit fullscreen mode

To approve or reject our loan, we simply commit or void the inflight transaction in Blnk. To do this, we’ll call the update inflight endpoint using the transaction ID from the previous step:

# Approve loan (commit)
curl -X PUT http://localhost:5001/transactions/inflight/{transaction_id} \\
  -H "Content-Type: application/json" \\
  -H "X-blnk-key: YOUR_API_KEY" \\
  -d '{
    "status": "commit"
  }'

# Reject loan (void)
curl -X PUT http://localhost:5001/transactions/inflight/{transaction_id} \\
  -H "Content-Type: application/json" \\
  -H "X-blnk-key: YOUR_API_KEY" \\
  -d '{
    "status": "void"
  }'
Enter fullscreen mode Exit fullscreen mode

A successful response will look something like this:

//Approved loan
{
  "transaction_id": "txn_15d09e5e-b38e-43cf-aeaf-29e42631c845",
  "parent_transaction": "txn_74382c5a-ee16-41c8-83a5-74464c34051a",
  "status": "APPLIED",
  "precise_amount": 50000,
 "amount": 500,
  "source": "bln_b76a741f-4f1a-4018-b1a9-e7251e6f8541",
  "destination": "bln_1a817670-e647-4089-b1ba-e4d1a89e24f1",
  "reference": "ref_5cc4daff-2005-43e4-8dcc-2c2db61a95fa",
  "meta_data": {
    "transaction_type": "loan_disbursement",
    "customer_id": "CUST_001",
    "loan_amount": 500
  }
}
Enter fullscreen mode Exit fullscreen mode

transaction table

The transactions table shows loan requests in different states: one Applied (approved), one Void (rejected), and two Inflight (pending).

Step 3: Charging Interest

At Lumen Credit, let’s assume we charge a 1% interest on a daily basis. When you charge interest, the amount owed naturally increases.

To model this with Blnk, we’ll move money from the customer's loan balance to a dedicated revenue balance. This would correctly track that the customer owes a bit more (i.e. original loan + interest) while recording our expected interest revenue from the customer.

Money movement map for interest charge on a transaction.

Money movement map for interest charge on a transaction.

Our transaction looks something like this:

curl -X POST http://localhost:5001/transactions \\
  -H "Content-Type: application/json" \\
  -H "X-blnk-key: YOUR_API_KEY" \\
  -d '{
    "precise_amount": 500,
    "precision": 100,
    "currency": "USD",
    "reference": "INTEREST-001",
    "source": "bln_LOAN_BALANCE_ID",
    "destination": "@InterestRevenue",
    "description": "Daily interest charge",
    "allow_overdraft": true,
    "meta_data": {
      "transaction_type": "interest_charge",
      "interest_rate": 0.01,
      "principal_amount": 500
    }
  }'
Enter fullscreen mode Exit fullscreen mode

Since, interest revenue is a balance we (Lumen Credit) own, we will use the internal balance feature to specify that.

This automatically creates the balance in the General Ledger if it doesn’t exist and uses it like a normal balance in our transaction. To apply, we’ll specify using an indicator — a unique name with the @ prefix. In our case, we used @InterestRevenue.

Once submitted, we should get a response like this:

{
  "transaction_id": "txn_533d55e7-8daa-468a-958a-9846f877c5bf",
  "status": "QUEUED",
  "precise_amount": 500,
  "amount": 5,
  "source": "bln_b76a741f-4f1a-4018-b1a9-e7251e6f8541",
  "destination": "@InterestRevenue",
  "reference": "INTEREST-001",
  "allow_overdraft": true,
  "meta_data": {
    "transaction_type": "interest_charge",
    "interest_rate": 0.01,
    "principal_amount": 500
  }
}
Enter fullscreen mode Exit fullscreen mode

The transactions table shows an interest charge transaction of $5.00 moving from Billy Brian's loan balance to @InterestRevenue with Applied status.

The transactions table shows an interest charge transaction of $5.00 moving from Billy Brian's loan balance to @InterestRevenue with Applied status.

Step 4: Collecting repayments

To model repayments, our goal is to zero out the loan balance. Once the loan balance is equal to or greater than 0, the customer is deemed as debt-free.

To record a repayment, we’ll simply move money from their main balance back to their loan balance to reduce the debt.

Money movement map of a $200.00 repayment transaction from the customer's main balance back to their loan balance.

Money movement map of a $200.00 repayment transaction from the customer's main balance back to their loan balance.

Here's how to record that repayment in Blnk:

curl -X POST http://localhost:5001/transactions \\
  -H "Content-Type: application/json" \\
  -H "X-blnk-key: YOUR_API_KEY" \\
  -d '{
    "precise_amount": 20000,
    "precision": 100,
    "currency": "USD",
    "reference": "LOAN-REPAYMENT-001",
    "source": "bln_MAIN_BALANCE_ID",
    "destination": "bln_LOAN_BALANCE_ID",
    "description": "Loan repayment from Billy",
    "allow_overdraft": false,
    "meta_data": {
      "transaction_type": "loan_repayment",
      "customer_id": "CUST_001",
      "repayment_amount": 200
    }
  }'
Enter fullscreen mode Exit fullscreen mode

Note:

  • allow_overdraft: false ensures the repayment fails if the customer doesn't have enough funds in their main balance.

Once submitted, we’ll get the following response:

{
 "transaction_id": "txn_a7e37fab-2b33-4fc9-9a54-b4ffa3d7d4e6",
  "status": "QUEUED",
  "precise_amount": 20000,
  "amount": 200,
  "source": "bln_1a817670-e647-4089-b1ba-e4d1a89e24f1",
  "destination": "bln_b76a741f-4f1a-4018-b1a9-e7251e6f8541",
  "reference": "LOAN-REPAYMENT-002",
  "allow_overdraft": false,
  "meta_data": {
   "transaction_type": "loan_repayment",
   "customer_id": "CUST_001",
   "repayment_amount": 200
  }
}

Enter fullscreen mode Exit fullscreen mode

You can repeat this repayment flow as many times as needed. Once the loan balance reaches 0 or higher, the loan is fully repaid.

The balance detail view displays Billy Brian's loan balance at 0.00 after full repayment, showing all transactions that led to this state.

The balance detail view displays Billy Brian's loan balance at 0.00 after full repayment, showing all transactions that led to this state.

Wrapping it up

Now, we have our Lumen Credit product.

You can apply this guide to any lending-related product/feature that you’re building, e.g. credit cards, BNPL, employee loans, merchant advances, and more.

As long as your Blnk Ledger is structured to clearly represent loans, limits, and available balances, you can confidently build on top of it.

To see this in action, check out the demo. If you’d like to explore more, check the Blnk docs, or join our community on Discord to ask questions and get support.

Top comments (0)