Contents

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.

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.
The ledgers table shows the Customer Main Ledger and Customers Loan Ledger after creation, along with the General Ledger.
  1. 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.
  2. Create the balances per customer: Using the newly created customer's identity, create the loan and main balance under their respective ledgers. Read docs.
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.

```

--CODE language-mermaid--

graph LR

%% Add a comparison that shows the balances before disbursement and after disbursement.

Loan(["Loan balance"]) -->|"overdraft=true"| Main(["Main balance"])

```

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.

```

--CODE language-bash--

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

   }
}'

```

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:

```

--CODE language-json--

{
 "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
 }
}

```

Make sure that you keep the transaction ID.

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

```
--CODE language-javascript--

if (kyc && loan_limit && credit_score) {
return approve_loan
} else {
return reject_loan
}

```

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:

```

--CODE language-bash--

# 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"
 }'

```

A successful response will look something like this:

```

--CODE language-json--

//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
 }
}
```

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.

```

--CODE language-mermaid--

graph LR

Loan(["Loan balance"]) -->|"interest charge"| Interest(["Interest Revenue"])

```

Our transaction looks something like this:

```

--CODE language-bash--

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
   }
 }'

```

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 e @ prefix. In our case, we used @InterestRevenue.

Once submitted, we should get a response like this:

```

--CODE language-json--

{
 "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
 }
}
```

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.

```

--CODE language-mermaid--

graph LR

Main(["Main balance"]) --> Loan(["Loan balance"])

```

Here's how to record that repayment in Blnk:

```

--CODE language-bash--

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
   }
 }'

```

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:

```

--CODE language-json--

{
"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
 }
}
```

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.

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.