Credit repayment engine — the math (Task 10.2)

Status: ✅ Complete

Plan reference: Task 10.2 in the Credit module implementation plan (operations workspace root)

The credit module has two repayment engines:

  1. A declining-balance engine shared by Diminishing Musharakah and Ijarah — rent accrues on the shrinking outstanding balance.
  2. A fixed-profit engine for Murabaha — total profit is fixed at signing and recognised straight-line as the customer pays.

Both write credit_schedule_lines and both close the final-period residual so the totals tie out to the penny.

Declining-balance engine (DM / Ijarah)

Inputs

facilityAmount (= assetCost − downPayment), annual rate, number of periods N (termPeriods), periods-per-year.

Per-period formulas

  • Principal (buy-out), straight-line: principalPortion = facilityAmount / N for every period; the final period absorbs the rounding residual so Σ principalPortion === facilityAmount exactly.
  • Rent on the declining balance: profitPortion = beginningBalance × periodRate, where periodRate = rate / periodsPerYear. Because the balance shrinks each period, the rent declines every period.
  • totalPayment = principalPortion + profitPortion.
  • endingBalance = beginningBalance − principalPortion.

Ownership

Each period the customer's owned share rises by principalPortion / facilityAmount; Ethica's falls by the same. Written to credit_ownership_ledger and onto each schedule line.

Worked example (the DM / Ijarah golden case)

AssetCost ₦100,000,000, downPayment ₦0, rate 8% p.a., 15 years × 1/yr → 15 periods.

  • facilityAmount = 100,000,000.
  • principalPortion = 100,000,000 / 15 = ₦6,666,666.67 per period (straight-line).
  • Period 1: beginningBalance = 100,000,000; rent = 100,000,000 × 8% = ₦8,000,000; total = 6,666,666.67 + 8,000,000 = ₦14,666,666.67; ending = ₦93,333,333.33.
  • Period 2: rent = 93,333,333.33 × 8% = ₦7,466,666.67 — lower than period 1, because the balance shrank. And so on: rent declines every period.
  • Totals: Σ rent ≈ ₦64,000,000; Σ principal = ₦100,000,000 exactly (final-period residual closed).
PeriodBeginning balancePrincipal (buy-out)Rent (8% of beginning)TotalCustomer owned
1100,000,000.006,666,666.678,000,000.0014,666,666.676.67%
293,333,333.336,666,666.677,466,666.6714,133,333.3413.33%
15≈6,666,666.676,666,666.67*≈533,333.33≈7,200,000.00100%
Σ100,000,000.00≈64,000,000.00≈164,000,000.00

* Period 15's principal is the residual that closes Σ principal to exactly 100,000,000.

Fixed-profit engine (Murabaha)

The anti-riba rule

Murabaha total payable is assetCost + fixedTotalProfit, where fixedTotalProfit is locked at signing and supplied on the facility — it is never derived from the rate. This is the core AAOIFI no-riba guarantee for Murabaha: the profit is a sale margin agreed up front, not a time-value-of-money charge.

Per-period formulas

  • Principal, straight-line: principalPortion = assetCost / N, with the final period closing the residual so Σ principalPortion === assetCost exactly.
  • Profit, straight-line: profitPortion = fixedTotalProfit / N, with the final period closing the residual so Σ profitPortion === fixedTotalProfit exactly.
  • totalPayment = principalPortion + profitPortion.

Pace-invariance guarantee

Because both principal and profit are scheduled straight-line from amounts fixed at signing, the total profit the customer pays is invariant to the pace of payment. Paying early, late, or in lumps changes when profit is recognised, never how much. On early settlement Murabaha recognises all remaining deferred profit at once (there is no ibra' rebate yet — see open items). Contrast DM/Ijarah, where early settlement waives future rent because rent is a function of time-on-balance.

Residual closing (both engines)

Naive total / N leaves a sub-naira remainder after N periods because of rounding to 4dp. Both engines compute periods 1..N−1 at the rounded straight-line figure and set period N = the exact remaining balance, guaranteeing:

  • DM/Ijarah: Σ principalPortion === facilityAmount.
  • Murabaha: Σ principalPortion === assetCost and Σ profitPortion === fixedTotalProfit.

This is asserted in the engine's unit tests against the golden example above.

Files

  • Engine — ethica-api/src/credit/engine/*.ts (declining-balance + fixed-profit schedulers)
  • Tests — credit engine specs covering the golden DM/Ijarah case and Murabaha pace-invariance