Skip to content

Double-Entry Principles

Double-entry bookkeeping is a method where every transaction affects at least two accounts. For every debit, there must be a corresponding credit of equal value.

The Golden Rule

Total Debits = Total Credits (always!)

This fundamental principle ensures:

  • Books always balance
  • No money appears or disappears
  • Every transaction is complete and traceable

How It Works

Basic Example: P2P Transfer

User A sends E100 to User B

Entry 1: DEBIT User A's Wallet -E100
Entry 2: CREDIT User B's Wallet +E100

Verification:
Total Debits: E100
Total Credits: E100
Balanced: ✅

What happened:

  • User A's asset (wallet) decreased by E100 (debit)
  • User B's asset (wallet) increased by E100 (credit)
  • Net effect on total assets: 0 (internal transfer)

Complex Example: Vendor Payment with Fee

User pays vendor E95, Keshless charges E5 fee

Entry 1: DEBIT User Wallet -E100
Entry 2: CREDIT Vendor Wallet +E95
Entry 3: CREDIT Transaction Fee Revenue +E5

Verification:
Total Debits: E100
Total Credits: E95 + E5 = E100
Balanced: ✅

What happened:

  • User's asset decreased by E100
  • Vendor's asset increased by E95
  • Keshless revenue increased by E5
  • Net effect: Assets -E5, Revenue +E5 (profit!)

Debit vs Credit

Not "Addition" and "Subtraction"

Debit and Credit don't mean "add" or "subtract". They mean:

Account TypeDebit EffectCredit Effect
AssetIncreaseDecrease
LiabilityDecreaseIncrease
EquityDecreaseIncrease
RevenueDecreaseIncrease
ExpenseIncreaseDecrease

Why This Matters

Example: User tops up wallet E100

DEBIT  User Wallet +E100 (Asset increases)
CREDIT Cash in Transit +E100 (Liability increases)

Both accounts increase, but one is debit and one is credit.
This is correct because:
- Asset increase = Debit
- Liability increase = Credit

T-Account Visualization

Accountants use "T-accounts" to visualize transactions:

        User Wallet (Asset)
     DEBIT    |    CREDIT
   -------------------------
   +Topup     |  -Payment
   +Received  |  -Sent
   +Refund    |  -Withdrawal


    Transaction Fee Revenue (Revenue)
     DEBIT    |    CREDIT
   -------------------------
   -Refund    |  +Fee earned
   -Reversal  |  +Commission

Common Transaction Patterns

1. Asset to Asset Exchange

Transfer between two asset accounts (P2P transfer):

DEBIT  Asset A -100 (decrease)
CREDIT Asset B +100 (increase)

Example:
DEBIT  User A Wallet -100
CREDIT User B Wallet +100

2. Asset Increase with Liability Increase

Receiving external funds (topup):

DEBIT  Asset +100 (increase)
CREDIT Liability +100 (increase)

Example:
DEBIT  User Wallet +100
CREDIT Cash in Transit +100

3. Asset Decrease with Revenue Increase

Earning revenue from operations:

DEBIT  Asset A -100 (decrease)
CREDIT Asset B +95 (increase)
CREDIT Revenue +5 (increase)

Example:
DEBIT  User Wallet -100
CREDIT Vendor Wallet +95
CREDIT Transaction Fee Revenue +5

4. Expense Increase with Asset Decrease

Paying for operational costs:

DEBIT  Expense +50 (increase)
CREDIT Asset -50 (decrease)

Example:
DEBIT  Processing Costs +50
CREDIT Cash in Transit -50

5. Revenue Decrease with Asset Increase

Refunding previously earned revenue:

DEBIT  Revenue -5 (decrease)
DEBIT  Asset A -95 (decrease)
CREDIT Asset B +100 (increase)

Example:
DEBIT  Transaction Fee Revenue -5
DEBIT  Vendor Wallet -95
CREDIT User Wallet +100

Transaction Templates

The system provides pre-built templates that handle double-entry logic automatically:

typescript
// You write:
const template = transactionTemplatesService.getUserTransferTemplate(
  senderId,
  recipientId,
  100
);

// System creates:
{
  entries: [
    { accountCode: '1110', type: 'DEBIT', amount: 100, entityId: senderId },
    { accountCode: '1110', type: 'CREDIT', amount: 100, entityId: recipientId }
  ]
}

// Verified automatically: ✅ Debits (100) = Credits (100)

Balance Verification

Trial Balance

A trial balance lists all accounts with their debit and credit balances:

Account Code | Account Name              | Debit  | Credit
---------------------------------------------------------
1110         | User Wallets              | 10000  | 0
1120         | Vendor Wallets            | 5000   | 0
2110         | Pending Settlements       | 0      | 500
4110         | Transaction Fee Revenue   | 0      | 1500
5110         | Processing Costs          | 200    | 0
---------------------------------------------------------
TOTAL                                     15200    2000

Wait, that doesn't balance! Let me fix:

Account Code | Account Name              | Debit  | Credit
---------------------------------------------------------
1110         | User Wallets              | 10000  | 0
1120         | Vendor Wallets            | 5000   | 0
2110         | Pending Settlements       | 0      | 500
3100         | Retained Earnings         | 0      | 13200
4110         | Transaction Fee Revenue   | 0      | 1500
5110         | Processing Costs          | 200    | 0
---------------------------------------------------------
TOTAL                                     15200    15200 ✅

If total debits ≠ total credits, there's an error in the system.

Account Reconciliation

Each account should reconcile with source data:

User Wallet Balance (from User model): E1,250.00
User Wallet Ledger Total:
  Total Debits:  E3,500.00
  Total Credits: E2,250.00
  Balance:       E1,250.00 ✅

Match! The wallet balance matches the ledger.

If they don't match, reconciliation flags a discrepancy.

Error Prevention

Template Validation

Templates are validated before creation:

typescript
// ❌ This would fail:
{
  entries: [
    { accountCode: '1110', type: 'DEBIT', amount: 100 },
    { accountCode: '1120', type: 'CREDIT', amount: 95 }
  ]
}
// Error: Debits (100) ≠ Credits (95)

// ✅ This succeeds:
{
  entries: [
    { accountCode: '1110', type: 'DEBIT', amount: 100 },
    { accountCode: '1120', type: 'CREDIT', amount: 95 },
    { accountCode: '4110', type: 'CREDIT', amount: 5 }
  ]
}
// Valid: Debits (100) = Credits (95 + 5)

Journal Entry Validation

Before committing to database:

typescript
const totalDebits = entries
  .filter(e => e.type === 'DEBIT')
  .reduce((sum, e) => sum + e.amount, 0);

const totalCredits = entries
  .filter(e => e.type === 'CREDIT')
  .reduce((sum, e) => sum + e.amount, 0);

if (Math.abs(totalDebits - totalCredits) > 0.01) {
  throw new Error('Journal entry not balanced');
}

Accounting Equation

The accounting equation must always hold:

Assets = Liabilities + Equity + Revenue - Expenses

Or rearranged:

Assets + Expenses = Liabilities + Equity + Revenue

Every transaction maintains this balance:

Example: User pays E95, Keshless earns E5 fee

Before:
Assets: 10000
Revenue: 1000
Total Right: 11000

Transaction:
Assets: -100 (user) +95 (vendor) = -5
Revenue: +5

After:
Assets: 9995
Revenue: 1005
Total Right: 11000 ✅

Equation holds!

Reversals

To correct a posted transaction, create an offsetting entry:

Original Transaction:
DEBIT  User A -100
CREDIT User B +100

Reversal Transaction:
CREDIT User A +100
DEBIT  User B -100

Net Effect:
User A: -100 +100 = 0
User B: +100 -100 = 0
Transaction cancelled ✅

The original entry remains in the ledger for audit purposes.

Best Practices

  1. Always use templates - They ensure balanced entries
  2. Never create manual entries - Risk of imbalance
  3. Verify trial balance daily - Catches errors early
  4. Use reversals, not edits - Maintains audit trail
  5. Reconcile regularly - Wallet balances vs ledger

Next Steps

Internal use only - Keshless Payment Platform