Overview

Efficiently managing expenses is crucial for both personal and business finances. This guide will help you implement a comprehensive expense management system using the Blnk Ledger.

We’ll learn about:

  • Defining and creating your ledger structure
  • Balance (expense account) creation
  • Recording expenses
  • Approving expenses
  • Best Practices (with Inflight)

1. Ledger Structure

The entry point of the Blnk ledger system is ledger folders. These folders serve as a way to group and manage assets, accounts, and balances that fit your product or organization’s structure.

In this guide, we’ll use the following structure:

  • Marketing Ledger: Contains all expense accounts for the marketing department.
  • HR Ledger: Contains all expense accounts for the HR department.

The ledger structure is flexible and can be customized based on your specific needs. For instance, you could group by users instead of currencies, or use a combination of both.

See also:

Creating a Marketing Ledger

NodeJS
const request = require('request');

const options = {
  method: 'POST',
  url: 'http://localhost:5001/ledgers',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'YOUR_AUTH_TOKEN_HERE'
  },
  body: JSON.stringify({
    "name": "Marketing Department Ledger",
    "meta_data": {
      "department": "Marketing"
    }
  })
};

request(options, (error, response) => {
  if (error) throw new Error(error);
  console.log(response.body);
});
Response
{
    "ledger_id": "ldg_12345678-90ab-cdef-1234-567890abcdef",
    "name": "Marketing Department Ledger",
    "created_at": "2024-07-05T08:06:26.84333909Z",
    "meta_data": {
        "department": "Marketing"
    }
}

Always store the ledger_id in your database. You’ll need it for future operations related to this ledger.

Creating an HR Ledger

NodeJS
const request = require('request');

const options = {
  method: 'POST',
  url: 'http://localhost:5001/ledgers',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'YOUR_AUTH_TOKEN_HERE'
  },
  body: JSON.stringify({
    "name": "HR Department Ledger",
    "meta_data": {
      "department": "HR"
    }
  })
};

request(options, (error, response) => {
  if (error) throw new Error(error);
  console.log(response.body);
});
Response
{
    "ledger_id": "ldg_abcdef12-3456-7890-abcd-ef1234567890",
    "name": "HR Department Ledger",
    "created_at": "2024-07-05T08:06:26.84333909Z",
    "meta_data": {
        "department": "HR"
    }
}

2. Balance (Expense Account) Creation

Blnk uses the concept of ledger balances to manage accounts/balances in a ledger. In this example, we’ll create expense accounts for various categories within each department.

See also:

Creating a marketing expense account for advertising

NodeJS
const request = require('request');

const options = {
  method: 'POST',
  url: 'http://localhost:5001/balances',
  headers: {
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    "ledger_id": "ldg_12345678-90ab-cdef-1234-567890abcdef",  // The Marketing Ledger ID
    "currency": "USD",
    "meta_data": {
      "expense_type": "Advertising",
      "department": "Marketing"
    }
  })
};

request(options, (error, response) => {
  if (error) throw new Error(error);
  console.log(response.body);
});
Response
{
    "balance": 0,
    "version": 0,
    "inflight_balance": 0,
    "credit_balance": 0,
    "inflight_credit_balance": 0,
    "debit_balance": 0,
    "inflight_debit_balance": 0,
    "precision": 0,
    "ledger_id": "ldg_12345678-90ab-cdef-1234-567890abcdef",
    "identity_id": "",
    "balance_id": "bln_1a2b3c4d-5e6f-7g8h-9i0j-1k2l3m4n5o6p",
    "indicator": "",
    "currency": "USD",
    "created_at": "2024-07-05T08:13:18.882616461Z",
    "meta_data": {
        "expense_type": "Advertising",
        "department": "Marketing"
    }
}

The balance_id is crucial. Always store this in your database and associate it with the customer. You’ll use this ID for all future transactions involving this loyalty point account.

Creating an HR expense account for recruitment

NodeJS
const request = require('request');

const options = {
  method: 'POST',
  url: 'http://localhost:5001/balances',
  headers: {
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    "ledger_id": "ldg_abcdef12-3456-7890-abcd-ef1234567890",  // The HR Ledger ID
    "currency": "USD",
    "meta_data": {
      "expense_type": "Recruitment",
      "department": "HR"
    }
  })
};

request(options, (error, response) => {
  if (error) throw new Error(error);
  console.log(response.body);
});
Response
{
    "balance": 0,
    "version": 0,
    "inflight_balance": 0,
    "credit_balance": 0,
    "inflight_credit_balance": 0,
    "debit_balance": 0,
    "inflight_debit_balance": 0,
    "precision": 0,
    "ledger_id": "ldg_abcdef12-3456-7890-abcd-ef1234567890",
    "identity_id": "",
    "balance_id": "bln_9i8u7y6t-5r4e-3w2q-1a2s-3d4f5g6h7j8k",
    "indicator": "",
    "currency": "USD",
    "created_at": "2024-07-05T08:13:18.882616461Z",
    "meta_data": {
        "expense_type": "Recruitment",
        "department": "HR"
    }
}

3. Recording Expenses

Whenever a department incurs an expense, you can record it using the Inflight feature to manage the expense transactions.

See also:

Recording an advertising expense for marketing

NodeJS
const request = require('request');

const options = {
  method: 'POST',
  url: 'http://localhost:5001/transactions',
  headers: {
    'X-Blnk-Key': 'blnk-api',
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    "amount": 500,
    "precision": 100,
    "reference": "ad-expense-001",
    "description": "Payment for social media ads",
    "currency": "USD",
    "source": "@CompanyFunds",
    "destination": "bln_1a2b3c4d-5e6f-7g8h-9i0j-1k2l3m4n5o6p",  // Marketing Advertising expense balance_id
    "inflight": true,
    "meta_data": {
      "expense_type": "Advertising",
      "department": "Marketing",
      "vendor": "SocialMediaCo"
    }
  })
};

request(options, (error, response) => {
  if (error) throw new Error(error);
  console.log(response.body);
});
Response
{
    "transaction_id": "txn_abcdef01-2345-6789-abcd-ef0123456789",
    "amount": 500,
    "precision": 100,
    "precise_amount": 50000,
    "reference": "ad-expense-001",
    "description": "Payment for social media ads",
    "currency": "USD",
    "status": "INFLIGHT",
    "source": "@CompanyFunds",
    "destination": "bln_1a2b3c4d-5e6f-7g8h-9i0j-1k2l3m4n5o6p",
    "created_at": "2024-07-05T08:21:04.001458635Z",
    "meta_data": {
        "expense_type": "Advertising",
        "department": "Marketing",
        "vendor": "SocialMediaCo"
    }
}

Using inflight allows you to be able to set and apply approval rules on the transaction. If it is approved, the inflight transaction is committed. If it is declined, the transaction is voided.

Recording a recruitment expense for HR

NodeJS
const request = require('request');

const options = {
  method: 'POST',
  url: 'http://localhost:5001/transactions',
  headers: {
    'X-Blnk-Key': 'blnk-api',
    'Content-Type

': 'application/json'
  },
  body: JSON.stringify({
    "amount": 200, 
    "precision": 100,
    "reference": "rec-expense-001",
    "description": "Payment for job postings",
    "currency": "USD",
    "source": "@CompanyFunds",
    "destination": "bln_9i8u7y6t-5r4e-3w2q-1a2s-3d4f5g6h7j8k",  // HR Recruitment expense balance_id
    "inflight": true,
    "meta_data": {
      "expense_type": "Recruitment",
      "department": "HR",
      "vendor": "JobBoardCo"
    }
  })
};

request(options, (error, response) => {
  if (error) throw new Error(error);
  console.log(response.body);
});
Response
{
    "transaction_id": "txn_abcdef02-3456-7890-abcd-ef0234567890",
    "amount": 200,
    "precision": 100,
    "precise_amount": 20000,
    "reference": "ad-expense-001",
    "description": "Payment for job postings",
    "currency": "USD",
    "status": "INFLIGHT",
    "source": "@CompanyFunds",
    "destination": "bln_9i8u7y6t-5r4e-3w2q-1a2s-3d4f5g6h7j8k",
    "created_at": "2024-07-05T08:21:04.001458635Z",
    "meta_data": {
        "expense_type": "Recruitment",
        "department": "HR",
        "vendor": "JobBoardCo"
    }
}

4. Approving Expenses

After recording an expense, it needs to be approved before being finalized.

Approving an advertising expense for marketing

NodeJS
const request = require('request');

const options = {
  method: 'POST',
  url: `http://localhost:5001/transactions/txn_abcdef01-2345-6789-abcd-ef0123456789/inflight`,
  headers: {
    'X-Blnk-Key': 'blnk-api',
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    "status": "commit"
  })
};

request(options, (error, response) => {
  if (error) throw new Error(error);
  console.log(response.body);
});

Response
{
    "transaction_id": "txn_abcdef01-2345-6789-abcd-ef0123456789",
    "amount": 500,
    "precision": 100,
    "precise_amount": 50000,
    "reference": "ad-expense-001",
    "description": "Payment for social media ads",
    "currency": "USD",
    "status": "APPLIED",
    "source": "@CompanyFunds",
    "destination": "bln_1a2b3c4d-5e6f-7g8h-9i0j-1k2l3m4n5o6p",
    "created_at": "2024-07-05T08:21:04.001458635Z",
    "meta_data": {
        "expense_type": "Advertising",
        "department": "Marketing",
        "vendor": "SocialMediaCo"
    }
}

Approving a recruitment expense for HR

NodeJS
const request = require('request');

const options = {
  method: 'POST',
  url: `http://localhost:5001/transactions/txn_abcdef02-3456-7890-abcd-ef0234567890/inflight`,
  headers: {
    'X-Blnk-Key': 'blnk-api',
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    "status": "commit"
  })
};

request(options, (error, response) => {
  if (error) throw new Error(error);
  console.log(response.body);
});
Response
{
    "transaction_id": "txn_abcdef02-3456-7890-abcd-ef0234567890",
    "amount": 200,
    "precision": 100,
    "precise_amount": 20000,
    "reference": "ad-expense-001",
    "description": "Payment for job postings",
    "currency": "USD",
    "status": "APPLIED",
    "source": "@CompanyFunds",
    "destination": "bln_9i8u7y6t-5r4e-3w2q-1a2s-3d4f5g6h7j8k",
    "created_at": "2024-07-05T08:21:04.001458635Z",
    "meta_data": {
        "expense_type": "Recruitment",
        "department": "HR",
        "vendor": "JobBoardCo"
    }
}

See also

Managing side effects with Inflight

A deep-dive guide into how to implement Inflight in your application.

Best Practices (with Inflight)

  1. Available Balance Calculation: In your application, calculate the available balance to prevent users from accessing funds that are held in Inflight transactions. This can be done as follows:
const availableBalance = balance - inflight_debit_balance;
  1. Error Handling: Implement robust error handling in your Inflight process. If a commit or void operation fails, you may need to retry or escalate to manual intervention.
  2. Approval Process: Implement a clear approval process for expenses, including notifications and roles for approvers. This helps ensure that all expenses are reviewed and authorized properly.
  3. Reconciliation: Regularly reconcile your internal records with Blnk’s transaction logs to ensure accuracy. Pay special attention to Inflight transactions. Blnk v1 will support reconciliation features built into the ledger, which can aid in this process.
  4. Webhook Authentication: In a production environment, implement a mechanism to verify that webhooks are genuinely from your payment provider. This helps in maintaining the integrity and security of your transaction processing.
  5. Data Security: Always ensure that sensitive card details are securely managed and stored. Mask or encrypt card numbers, CVVs, and other sensitive information when storing or displaying them.
  6. Transaction State Management: Use metadata to manage the state of virtual cards and transactions effectively. For example, track the transaction verification status (e.g., VERIFIED, DECLINED) in the metadata.
  7. Compliance: Ensure that your virtual card issuance and transaction processing comply with relevant regulations and standards, such as PCI-DSS for handling card data securely.
  8. Testing: Thoroughly test your virtual card issuance and transaction processing flows, including Inflight handling, to identify and resolve any issues before going live.

Need help?

Are you stuck? Do you have a question that isn’t answered in this doc? Have you run into a problem you can’t solve? Want to file a bug report?

Join our Discord server and share your questions/thoughts with other developers building financial applications like you.

Was this page helpful?