Loan Repayment
Complete guide to loan repayment transactions in BankLingo V2, including multi-schedule impact tracking, payment allocation, partial payments, and arrears handling.
Overview
A Loan Repayment Transaction applies customer payments to loan accounts, allocating funds across multiple schedules according to business rules.
Key Characteristics:
- Payment Allocation Order: Penalty → Interest → Fees → Principal (configurable)
- Multi-Schedule Impact: Single payment can affect multiple schedules simultaneously
- Partial Payment Support: Handle underpayments gracefully
- Overpayment Handling: Apply excess to future schedules or refund
- Arrears Calculation: Track days past due and penalty accrual
- Prepayment Support: Early payments reduce interest
- Schedule State Management: PENDING → PAID → CLOSED
Critical Requirement: Multi-Schedule Impact Tracking
Problem Statement: When a customer makes a payment, it may affect multiple loan schedules simultaneously. Each schedule's changes must be tracked with field-level deltas for:
- Penalty paid
- Interest paid
- Fees paid
- Principal paid
- Outstanding balances
- State changes
Solution: TransactionImpactRecord tracks EVERY field change on EVERY affected schedule.
Transaction Flow
Entities Impacted
Scenario 1: Full Payment of Single Schedule
Payment of NGN 100,000 to cover Schedule 1 (all components)
Schedule 1 Due:
- Principal: NGN 80,000
- Interest: NGN 15,000
- Fees: NGN 3,000
- Penalty: NGN 2,000
- Total: NGN 100,000
{
transactionId: "RPMT123456",
transactionState: "COMPLETED",
paymentAmount: 100000.00,
impactedEntities: [
// Schedule 1 - Full Payment
{
entityType: "LoanSchedule",
entityId: 201,
entityKey: "SCH-LOAN001-01",
fieldName: "PenaltyPaid",
oldValue: 0.00,
newValue: 2000.00,
deltaAmount: +2000.00
},
{
entityType: "LoanSchedule",
entityId: 201,
entityKey: "SCH-LOAN001-01",
fieldName: "InterestPaid",
oldValue: 0.00,
newValue: 15000.00,
deltaAmount: +15000.00
},
{
entityType: "LoanSchedule",
entityId: 201,
entityKey: "SCH-LOAN001-01",
fieldName: "FeesPaid",
oldValue: 0.00,
newValue: 3000.00,
deltaAmount: +3000.00
},
{
entityType: "LoanSchedule",
entityId: 201,
entityKey: "SCH-LOAN001-01",
fieldName: "PrincipalPaid",
oldValue: 0.00,
newValue: 80000.00,
deltaAmount: +80000.00
},
{
entityType: "LoanSchedule",
entityId: 201,
entityKey: "SCH-LOAN001-01",
fieldName: "TotalPaid",
oldValue: 0.00,
newValue: 100000.00,
deltaAmount: +100000.00
},
{
entityType: "LoanSchedule",
entityId: 201,
entityKey: "SCH-LOAN001-01",
fieldName: "OutstandingBalance",
oldValue: 100000.00,
newValue: 0.00,
deltaAmount: -100000.00
},
{
entityType: "LoanSchedule",
entityId: 201,
entityKey: "SCH-LOAN001-01",
fieldName: "State",
oldValue: "ACTIVE",
newValue: "PAID",
deltaAmount: 0
},
{
entityType: "LoanSchedule",
entityId: 201,
entityKey: "SCH-LOAN001-01",
fieldName: "PaidDate",
oldValue: null,
newValue: "2025-12-28T10:00:00Z",
deltaAmount: 0
},
// Loan Account
{
entityType: "LoanAccount",
entityId: 100,
entityKey: "LOAN-001",
fieldName: "PrincipalBalance",
oldValue: 1000000.00,
newValue: 920000.00,
deltaAmount: -80000.00
},
{
entityType: "LoanAccount",
entityId: 100,
entityKey: "LOAN-001",
fieldName: "InterestBalance",
oldValue: 180000.00,
newValue: 165000.00,
deltaAmount: -15000.00
},
{
entityType: "LoanAccount",
entityId: 100,
entityKey: "LOAN-001",
fieldName: "PenaltyBalance",
oldValue: 2000.00,
newValue: 0.00,
deltaAmount: -2000.00
},
{
entityType: "LoanAccount",
entityId: 100,
entityKey: "LOAN-001",
fieldName: "FeesBalance",
oldValue: 5000.00,
newValue: 2000.00,
deltaAmount: -3000.00
},
{
entityType: "LoanAccount",
entityId: 100,
entityKey: "LOAN-001",
fieldName: "TotalPaid",
oldValue: 0.00,
newValue: 100000.00,
deltaAmount: +100000.00
},
// Customer Deposit Account
{
entityType: "DepositAccount",
entityId: 456,
entityKey: "ACC-CUST-001",
fieldName: "AvailableBalance",
oldValue: 150000.00,
newValue: 50000.00,
deltaAmount: -100000.00
},
{
entityType: "DepositAccount",
entityId: 456,
entityKey: "ACC-CUST-001",
fieldName: "BookBalance",
oldValue: 150000.00,
newValue: 50000.00,
deltaAmount: -100000.00
},
// GL Accounts
{
entityType: "GLAccount",
entityKey: "2100-001", // Customer Deposits
fieldName: "DebitAmount",
deltaAmount: +100000.00
},
{
entityType: "GLAccount",
entityKey: "3100-001", // Loans to Customers
fieldName: "CreditAmount",
deltaAmount: +80000.00
},
{
entityType: "GLAccount",
entityKey: "4300-001", // Interest Income
fieldName: "CreditAmount",
deltaAmount: +15000.00
},
{
entityType: "GLAccount",
entityKey: "4300-002", // Penalty Income
fieldName: "CreditAmount",
deltaAmount: +2000.00
},
{
entityType: "GLAccount",
entityKey: "4300-003", // Fee Income
fieldName: "CreditAmount",
deltaAmount: +3000.00
}
]
}
```text
### Scenario 2: Payment Across Multiple Schedules (CRITICAL)
**Payment of NGN 250,000 covers Schedule 1 fully + Schedule 2 partially**
**Schedule 1 Due**:
- Principal: NGN 80,000
- Interest: NGN 15,000
- Fees: NGN 3,000
- Penalty: NGN 2,000
- **Total**: NGN 100,000
**Schedule 2 Due**:
- Principal: NGN 85,000
- Interest: NGN 17,000
- Fees: NGN 2,000
- Penalty: NGN 0
- **Total**: NGN 104,000
**Payment Allocation**:
1. Schedule 1: NGN 100,000 (FULL) → Remaining: NGN 150,000
2. Schedule 2: NGN 104,000 (FULL) → Remaining: NGN 46,000
3. Schedule 3: NGN 46,000 (PARTIAL) → Remaining: NGN 0
```typescript
{
transactionId: "RPMT234567",
transactionState: "COMPLETED",
paymentAmount: 250000.00,
impactedEntities: [
// â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•
// SCHEDULE 1 - FULL PAYMENT
// â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•
{
entityType: "LoanSchedule",
entityId: 201,
entityKey: "SCH-LOAN001-01",
fieldName: "PenaltyPaid",
oldValue: 0.00,
newValue: 2000.00,
deltaAmount: +2000.00
},
{
entityType: "LoanSchedule",
entityId: 201,
entityKey: "SCH-LOAN001-01",
fieldName: "InterestPaid",
oldValue: 0.00,
newValue: 15000.00,
deltaAmount: +15000.00
},
{
entityType: "LoanSchedule",
entityId: 201,
entityKey: "SCH-LOAN001-01",
fieldName: "FeesPaid",
oldValue: 0.00,
newValue: 3000.00,
deltaAmount: +3000.00
},
{
entityType: "LoanSchedule",
entityId: 201,
entityKey: "SCH-LOAN001-01",
fieldName: "PrincipalPaid",
oldValue: 0.00,
newValue: 80000.00,
deltaAmount: +80000.00
},
{
entityType: "LoanSchedule",
entityId: 201,
entityKey: "SCH-LOAN001-01",
fieldName: "OutstandingBalance",
oldValue: 100000.00,
newValue: 0.00,
deltaAmount: -100000.00
},
{
entityType: "LoanSchedule",
entityId: 201,
entityKey: "SCH-LOAN001-01",
fieldName: "State",
oldValue: "ACTIVE",
newValue: "PAID",
deltaAmount: 0
},
// â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•
// SCHEDULE 2 - FULL PAYMENT
// â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•
{
entityType: "LoanSchedule",
entityId: 202,
entityKey: "SCH-LOAN001-02",
fieldName: "InterestPaid",
oldValue: 0.00,
newValue: 17000.00,
deltaAmount: +17000.00
},
{
entityType: "LoanSchedule",
entityId: 202,
entityKey: "SCH-LOAN001-02",
fieldName: "FeesPaid",
oldValue: 0.00,
newValue: 2000.00,
deltaAmount: +2000.00
},
{
entityType: "LoanSchedule",
entityId: 202,
entityKey: "SCH-LOAN001-02",
fieldName: "PrincipalPaid",
oldValue: 0.00,
newValue: 85000.00,
deltaAmount: +85000.00
},
{
entityType: "LoanSchedule",
entityId: 202,
entityKey: "SCH-LOAN001-02",
fieldName: "OutstandingBalance",
oldValue: 104000.00,
newValue: 0.00,
deltaAmount: -104000.00
},
{
entityType: "LoanSchedule",
entityId: 202,
entityKey: "SCH-LOAN001-02",
fieldName: "State",
oldValue: "ACTIVE",
newValue: "PAID",
deltaAmount: 0
},
// â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•
// SCHEDULE 3 - PARTIAL PAYMENT (46,000 of 110,000)
// â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•
// Allocation: Penalty (0) → Interest (18000) → Fees (2000) → Principal (26000)
// Remaining: 46000 - 18000 - 2000 = 26000 for principal
{
entityType: "LoanSchedule",
entityId: 203,
entityKey: "SCH-LOAN001-03",
fieldName: "InterestPaid",
oldValue: 0.00,
newValue: 18000.00, // Full interest paid
deltaAmount: +18000.00
},
{
entityType: "LoanSchedule",
entityId: 203,
entityKey: "SCH-LOAN001-03",
fieldName: "FeesPaid",
oldValue: 0.00,
newValue: 2000.00, // Full fees paid
deltaAmount: +2000.00
},
{
entityType: "LoanSchedule",
entityId: 203,
entityKey: "SCH-LOAN001-03",
fieldName: "PrincipalPaid",
oldValue: 0.00,
newValue: 26000.00, // PARTIAL principal (of 90000 due)
deltaAmount: +26000.00
},
{
entityType: "LoanSchedule",
entityId: 203,
entityKey: "SCH-LOAN001-03",
fieldName: "OutstandingBalance",
oldValue: 110000.00,
newValue: 64000.00, // 90000 - 26000 = 64000 principal still due
deltaAmount: -46000.00
},
{
entityType: "LoanSchedule",
entityId: 203,
entityKey: "SCH-LOAN001-03",
fieldName: "State",
oldValue: "ACTIVE",
newValue: "ACTIVE", // Still ACTIVE (partially paid)
deltaAmount: 0
},
// â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•
// LOAN ACCOUNT - AGGREGATED IMPACT
// â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•
{
entityType: "LoanAccount",
entityId: 100,
entityKey: "LOAN-001",
fieldName: "PrincipalBalance",
oldValue: 1000000.00,
newValue: 809000.00, // 1M - 80K - 85K - 26K = 809K
deltaAmount: -191000.00
},
{
entityType: "LoanAccount",
entityId: 100,
entityKey: "LOAN-001",
fieldName: "InterestBalance",
oldValue: 180000.00,
newValue: 130000.00, // 180K - 15K - 17K - 18K = 130K
deltaAmount: -50000.00
},
{
entityType: "LoanAccount",
entityId: 100,
entityKey: "LOAN-001",
fieldName: "PenaltyBalance",
oldValue: 2000.00,
newValue: 0.00,
deltaAmount: -2000.00
},
{
entityType: "LoanAccount",
entityId: 100,
entityKey: "LOAN-001",
fieldName: "FeesBalance",
oldValue: 5000.00,
newValue: 0.00, // 5K - 3K - 2K = 0
deltaAmount: -5000.00
},
{
entityType: "LoanAccount",
entityId: 100,
entityKey: "LOAN-001",
fieldName: "TotalPaid",
oldValue: 0.00,
newValue: 250000.00,
deltaAmount: +250000.00
},
{
entityType: "LoanAccount",
entityId: 100,
entityKey: "LOAN-001",
fieldName: "SchedulesPaid",
oldValue: 0,
newValue: 2,
deltaAmount: +2 // Schedule 1 and 2 fully paid
},
// Customer Deposit Account
{
entityType: "DepositAccount",
entityId: 456,
entityKey: "ACC-CUST-001",
fieldName: "AvailableBalance",
oldValue: 300000.00,
newValue: 50000.00,
deltaAmount: -250000.00
},
// GL Accounts
{
entityType: "GLAccount",
entityKey: "2100-001",
fieldName: "DebitAmount",
deltaAmount: +250000.00
},
{
entityType: "GLAccount",
entityKey: "3100-001", // Loans Asset
fieldName: "CreditAmount",
deltaAmount: +191000.00 // Principal
},
{
entityType: "GLAccount",
entityKey: "4300-001", // Interest Income
fieldName: "CreditAmount",
deltaAmount: +50000.00 // 15K + 17K + 18K
},
{
entityType: "GLAccount",
entityKey: "4300-002", // Penalty Income
fieldName: "CreditAmount",
deltaAmount: +2000.00
},
{
entityType: "GLAccount",
entityKey: "4300-003", // Fee Income
fieldName: "CreditAmount",
deltaAmount: +7000.00 // 3K + 2K + 2K
}
]
}
```text
### Scenario 3: Underpayment with Arrears
**Payment of NGN 50,000 when Schedule 1 (NGN 100,000) is 15 days overdue with penalty**
**Schedule 1 Due**:
- Principal: NGN 80,000
- Interest: NGN 15,000
- Fees: NGN 3,000
- Penalty: NGN 4,500 (15 days × 1% × principal)
- **Total**: NGN 102,500
**Payment Allocation** (50,000):
1. Penalty: NGN 4,500 (FULL)
2. Interest: NGN 15,000 (FULL)
3. Fees: NGN 3,000 (FULL)
4. Principal: NGN 27,500 (PARTIAL - 52,500 remains)
```typescript
{
transactionId: "RPMT345678",
transactionState: "COMPLETED",
paymentAmount: 50000.00,
impactedEntities: [
// Schedule 1 - PARTIAL Payment (arrears)
{
entityType: "LoanSchedule",
entityId: 201,
entityKey: "SCH-LOAN001-01",
fieldName: "DaysInArrears",
oldValue: 15,
newValue: 15, // Still in arrears (principal not fully paid)
deltaAmount: 0
},
{
entityType: "LoanSchedule",
entityId: 201,
entityKey: "SCH-LOAN001-01",
fieldName: "PenaltyPaid",
oldValue: 0.00,
newValue: 4500.00, // FULL penalty paid
deltaAmount: +4500.00
},
{
entityType: "LoanSchedule",
entityId: 201,
entityKey: "SCH-LOAN001-01",
fieldName: "InterestPaid",
oldValue: 0.00,
newValue: 15000.00, // FULL interest paid
deltaAmount: +15000.00
},
{
entityType: "LoanSchedule",
entityId: 201,
entityKey: "SCH-LOAN001-01",
fieldName: "FeesPaid",
oldValue: 0.00,
newValue: 3000.00, // FULL fees paid
deltaAmount: +3000.00
},
{
entityType: "LoanSchedule",
entityId: 201,
entityKey: "SCH-LOAN001-01",
fieldName: "PrincipalPaid",
oldValue: 0.00,
newValue: 27500.00, // PARTIAL principal
deltaAmount: +27500.00
},
{
entityType: "LoanSchedule",
entityId: 201,
entityKey: "SCH-LOAN001-01",
fieldName: "PrincipalDue",
oldValue: 80000.00,
newValue: 52500.00, // Remaining principal
deltaAmount: -27500.00
},
{
entityType: "LoanSchedule",
entityId: 201,
entityKey: "SCH-LOAN001-01",
fieldName: "OutstandingBalance",
oldValue: 102500.00,
newValue: 52500.00,
deltaAmount: -50000.00
},
{
entityType: "LoanSchedule",
entityId: 201,
entityKey: "SCH-LOAN001-01",
fieldName: "State",
oldValue: "ACTIVE",
newValue: "ACTIVE", // Still active (partial payment)
deltaAmount: 0
},
// Loan Account
{
entityType: "LoanAccount",
entityId: 100,
entityKey: "LOAN-001",
fieldName: "DaysInArrears",
oldValue: 15,
newValue: 15,
deltaAmount: 0
},
{
entityType: "LoanAccount",
entityId: 100,
entityKey: "LOAN-001",
fieldName: "ArrearsBalance",
oldValue: 102500.00,
newValue: 52500.00,
deltaAmount: -50000.00
}
]
}
```text
---
## Payment Allocation Logic
### Priority Order (Configurable)
:::warning[Code Removed]
**Implementation details removed for security.**
Contact support for implementation guidance.
:::text
### Allocation Algorithm
:::warning[Code Removed]
**Implementation details removed for security.**
Contact support for implementation guidance.
:::text
---
## Arrears & Penalty Calculation
### Days in Arrears
:::warning[Code Removed]
**Implementation details removed for security.**
Contact support for implementation guidance.
:::
### Penalty Calculation
```yaml
penaltyConfiguration:
- loanProductId: "PERSONAL_LOAN"
penaltyType: PERCENTAGE_OF_PRINCIPAL
dailyPenaltyRate: 0.10 # 0.1% per day
maxPenaltyRate: 10.0 # Max 10% of principal
gracePeriodDays: 3 # No penalty for first 3 days
- loanProductId: "SME_LOAN"
penaltyType: FLAT_RATE_PER_DAY
dailyPenaltyAmount: 500.00
maxPenaltyAmount: 50000.00
gracePeriodDays: 7
Implementation details removed for security.
Contact support for implementation guidance.
GL Posting
Journal Entries
Example 1: Full Payment (Single Schedule)
| Account | Description | Debit | Credit |
|---|---|---|---|
| 2100-001 | Customer Deposits | 100,000.00 | - |
| 3100-001 | Loans to Customers | - | 80,000.00 |
| 4300-001 | Interest Income | - | 15,000.00 |
| 4300-002 | Penalty Income | - | 2,000.00 |
| 4300-003 | Fee Income | - | 3,000.00 |
Example 2: Multi-Schedule Payment
| Account | Description | Debit | Credit |
|---|---|---|---|
| 2100-001 | Customer Deposits | 250,000.00 | - |
| 3100-001 | Loans to Customers | - | 191,000.00 |
| 4300-001 | Interest Income | - | 50,000.00 |
| 4300-002 | Penalty Income | - | 2,000.00 |
| 4300-003 | Fee Income | - | 7,000.00 |
API Usage
Request
POST /api/v2/transactions/loan/repayment
Content-Type: application/json
Authorization: Bearer {token}
{
"loanAccountEncodedKey": "LOAN-001",
"paymentAmount": 100000.00,
"paymentDate": "2025-12-28",
"sourceAccountEncodedKey": "ACC-CUST-001",
"paymentMethod": "ACCOUNT_DEBIT",
"channelType": "ONLINE_BANKING",
"narration": "Loan repayment - Schedule 1"
}
```text
### Response - Success
```json
{
"success": true,
"transactionKey": "RPMT123456",
"repaymentId": 789012,
"state": "COMPLETED",
"loanAccountNumber": "LN-0001",
"paymentAmount": 100000.00,
"allocationDetails": {
"totalPenaltyPaid": 2000.00,
"totalInterestPaid": 15000.00,
"totalFeesPaid": 3000.00,
"totalPrincipalPaid": 80000.00,
"schedulesPaid": [
{
"scheduleNumber": "SCH-001-01",
"scheduleDueDate": "2026-01-28",
"penaltyPaid": 2000.00,
"interestPaid": 15000.00,
"feesPaid": 3000.00,
"principalPaid": 80000.00,
"totalPaid": 100000.00,
"status": "PAID"
}
]
},
"loanAccountUpdates": {
"principalBalance": 920000.00,
"interestBalance": 165000.00,
"penaltyBalance": 0.00,
"feesBalance": 2000.00,
"totalOutstanding": 1087000.00,
"nextDueDate": "2026-02-28",
"nextDueAmount": 104000.00
},
"sourceAccountNewBalance": 50000.00,
"impactRecordId": 456,
"message": "Repayment processed successfully"
}
```text
### Response - Multi-Schedule Payment
```json
{
"success": true,
"transactionKey": "RPMT234567",
"paymentAmount": 250000.00,
"allocationDetails": {
"totalPenaltyPaid": 2000.00,
"totalInterestPaid": 50000.00,
"totalFeesPaid": 7000.00,
"totalPrincipalPaid": 191000.00,
"schedulesPaid": [
{
"scheduleNumber": "SCH-001-01",
"status": "PAID",
"totalPaid": 100000.00
},
{
"scheduleNumber": "SCH-001-02",
"status": "PAID",
"totalPaid": 104000.00
},
{
"scheduleNumber": "SCH-001-03",
"status": "PARTIALLY_PAID",
"totalPaid": 46000.00,
"remainingBalance": 64000.00
}
]
},
"message": "Repayment allocated across 3 schedules"
}
```text
---
## Error Codes
| Code | Message | Cause | Resolution |
|------|---------|-------|------------|
| 00 | Success | Repayment processed | None |
| 05 | Loan not active | Loan state != ACTIVE | Check loan status |
| 12 | Invalid amount | Amount ≤ 0 | Enter valid amount |
| 14 | Invalid source account | Account not found or inactive | Verify account |
| 35 | No outstanding balance | Loan fully paid | Check loan balance |
| 51 | Insufficient funds | Source account balance low | Deposit funds |
| 91 | System error | Database failure | Retry or contact support |
---
## Best Practices
### For Loan Officers
1. **Review Schedule Status**: Check which schedules are outstanding before payment
2. **Explain Allocation**: Inform customer how payment will be allocated
3. **Handle Arrears**: Explain penalty charges for late payments
4. **Confirm Amount**: Verify payment amount covers intended schedules
5. **Document Payment**: Record payment source and method
### For Developers
1. **Multi-Schedule Tracking**: ALWAYS track field-level changes for EVERY affected schedule
2. **Allocation Order**: Respect configured priority (Penalty→Interest→Fees→Principal)
3. **Partial Payments**: Handle underpayments gracefully with proper state management
4. **Concurrent Safety**: Lock loan account during repayment processing
5. **Audit Trail**: Log all schedule updates with before/after values
6. **Testing**: Test multi-schedule scenarios extensively
### For Operations
1. **Monitor Arrears**: Track loans in arrears daily
2. **Review Partial Payments**: Investigate partial payments for collection follow-up
3. **Reconcile GL**: Ensure repayment GL entries balance
4. **Track Overpayments**: Handle overpayments according to policy
---
## V2 API Commands
BankLingo V2 provides a BPMCore-compatible command for loan repayment that integrates with the BPM workflow engine.
### Architecture Overview
The V2 loan repayment command follows the **Delegation Pattern**:
- **V2 Facade**: `InitiateLoanRepaymentCommand` (BPMCore compatible)
- **V1 Implementation**: Delegates to existing V1 `LoanRepaymentCommand` with full business logic
- **BPM Integration**: Accepts parameters via `BpmUtil.GetPropertyValue()`
- **Impact Tracking**: Leverages existing `TransactionImpactTracker` for multi-schedule tracking
**Why Delegation?** The V1 implementation already includes:
- ✅ Complete payment allocation logic (Penalty → Interest → Fees → Principal)
- ✅ Multi-schedule impact tracking with field-level deltas
- ✅ Partial payment and overpayment handling
- ✅ Arrears calculation and penalty management
- ✅ Loan account balance updates and state management
- ✅ Approval workflow support
**Implementation**: `CB.Administration.Api/Commands/BPMCore/Loans/AdministrationCoreLoanCommandHandlers.Transactions.cs`
---
### InitiateLoanRepaymentCommand
**Purpose**: Process a loan repayment transaction with multi-schedule allocation
**Command**: `InitiateLoanRepaymentCommand`
**Delegation**: → `LoanRepaymentCommand` (V1 implementation)
#### BPM Parameters
```json
{
"commandName": "InitiateLoanRepaymentCommand",
"data": {
"accountEncodedKey": "string (mandatory)",
"clientEncodedKey": "string (mandatory)",
"paymentAmount": "decimal (mandatory)",
"paymentSourceAccountKey": "string (mandatory)",
"transactionDate": "DateTime (optional)",
"notes": "string (optional)"
}
}
Parameter Details
| Parameter | Type | Required | Description |
|---|---|---|---|
accountEncodedKey | string | ✅ Yes | Loan account for repayment |
clientEncodedKey | string | ✅ Yes | Client/borrower encoded key |
paymentAmount | decimal | ✅ Yes | Payment amount (must be > 0) |
paymentSourceAccountKey | string | ✅ Yes | Deposit account for payment source |
transactionDate | DateTime | ⌠No | Payment date (defaults to today) |
notes | string | ⌠No | Repayment notes/remarks |
Payment Allocation Logic
Allocation Order (configurable, default):
- Penalty (arrears charges) - paid first
- Interest - paid second
- Fees - paid third
- Principal - paid last
Multi-Schedule Allocation:
- Schedules processed in DueDate ASC order (oldest first)
- Full schedule paid if payment ≥ total due
- Partial payment allocated by priority within schedule
- Excess payment applied to future schedules
Balance Impact
Loan Account:
| Balance Field | Change | Calculation |
|---|---|---|
PrincipalBalance | -principalPaid | Sum of principal paid across all schedules |
InterestBalance | -interestPaid | Sum of interest paid across all schedules |
PenaltyBalance | -penaltyPaid | Sum of penalties paid |
FeesBalance | -feesPaid | Sum of fees paid |
TotalPaid | +paymentAmount | Total paid to date |
State | → CLOSED (if fully paid) | Auto-close when all balances = 0 |
Loan Schedules (multiple schedules affected):
| Field | Change | Notes |
|---|---|---|
PrincipalPaid | +amountAllocated | Tracked per schedule |
InterestPaid | +amountAllocated | Tracked per schedule |
FeesPaid | +amountAllocated | Tracked per schedule |
PenaltyPaid | +amountAllocated | Tracked per schedule |
OutstandingBalance | -totalPaid | Per schedule |
State | → PAID (if fully paid) | Per schedule |
Deposit Account (Payment Source):
| Balance Field | Change |
|---|---|
AvailableBalance | -paymentAmount |
BookBalance | -paymentAmount |
Example Request/Response
Request:
{
"commandName": "InitiateLoanRepaymentCommand",
"data": {
"accountEncodedKey": "8a8080827f23loan017f23def456",
"clientEncodedKey": "8a8080827f23client017f23xyz789",
"paymentAmount": 150000.00,
"paymentSourceAccountKey": "8a8080827f23dep017f23abc123",
"transactionDate": "2025-12-29T10:00:00Z",
"notes": "Monthly repayment - 3 schedules"
}
}
Response:
{
"isSuccessful": true,
"transactionId": "TXN-LOAN-RPMT-20251229-0001",
"transactionState": "SETTLED",
"message": "Loan repayment processed successfully",
"data": {
"loanAccountKey": "8a8080827f23loan017f23def456",
"paymentAmount": 150000.00,
"paymentDate": "2025-12-29T10:00:00Z",
"allocation": {
"penaltyPaid": 5000.00,
"interestPaid": 45000.00,
"feesPaid": 2000.00,
"principalPaid": 98000.00
},
"schedulesAffected": 3,
"schedulesClosed": 2,
"loanBalance": {
"previousPrincipal": 500000.00,
"newPrincipal": 402000.00,
"remainingSchedules": 10
},
"depositAccountBalance": {
"previousBalance": 1000000.00,
"newBalance": 850000.00
},
"impactRecords": 18
}
}
Impact Tracking (Multi-Schedule)
Critical: Every schedule affected by payment has field-level impact tracking.
Example Impact Records (per schedule):
// Schedule 1 - Fully Paid
{
entityType: "LoanSchedule",
entityId: 12345,
fieldName: "PrincipalPaid",
oldValue: 25000.00,
newValue: 50000.00,
deltaAmount: +25000.00 // Principal allocated
},
{
entityType: "LoanSchedule",
entityId: 12345,
fieldName: "State",
oldValue: "ACTIVE",
newValue: "PAID",
deltaAmount: 0
}
// Schedule 2 - Partially Paid
{
entityType: "LoanSchedule",
entityId: 12346,
fieldName: "InterestPaid",
oldValue: 5000.00,
newValue: 20000.00,
deltaAmount: +15000.00 // Partial interest allocated
}
Developer Documentation
For comprehensive technical implementation details, see:
- V2 Loan Commands Documentation: Available in developer technical docs
- V1 Implementation:
CB.Administration.Api/Commands/Loans/LoanRepaymentCommand.cs - V2 Facade:
CB.Administration.Api/Commands/BPMCore/Loans/AdministrationCoreLoanCommandHandlers.Transactions.cs
Key Implementation Features:
- ✅ BPM parameter parsing with
BpmUtil.GetPropertyValue() - ✅ Delegation to V1 implementation with full allocation logic
- ✅ Multi-schedule tracking (up to 100+ schedules per loan)
- ✅ Payment allocation by priority (Penalty → Interest → Fees → Principal)
- ✅ Partial payment and overpayment handling
- ✅ Arrears calculation and penalty management
- ✅ Loan account state management (ACTIVE → CLOSED when fully paid)
- ✅ Reversal support via universal commands
Implementation Checklist
Database Requirements
- Add
PenaltyPaid,InterestPaid,FeesPaid,PrincipalPaidto LoanSchedule - Add
DaysInArrears,ArrearsBalanceto LoanAccount - Add
TotalPaid,SchedulesPaidto LoanAccount - Create
LoanRepaymentTransactiontable - Create
TransactionImpactRecordwith capacity for multiple schedule impacts
Code Requirements
- Implement
ProcessLoanRepaymentAsyncwith multi-schedule allocation - Add
AllocatePaymentAsyncwith priority-based allocation - Implement penalty calculation with arrears tracking
- Add schedule state management (ACTIVE → PAID)
- Implement GL posting with component breakdown
- Add overpayment handling
Testing Requirements
- Unit test: Single schedule full payment
- Unit test: Multi-schedule payment (2-3 schedules)
- Unit test: Partial payment with arrears
- Unit test: Payment allocation priority
- Integration test: End-to-end repayment
- Load test: 100 concurrent repayments
Next: Loan Payoff →
Developer Resources
For API implementation details, see: