Loan Pay-Off Transaction
Overview
A Loan Pay-Off (also called Full Settlement or Early Termination) is a transaction where a borrower pays off the entire remaining balance of a loan before its scheduled maturity date. This transaction closes all outstanding loan schedules and settles the loan account completely.
Key Characteristics
- Purpose: Complete loan settlement before maturity
- Transaction Type:
PYOF(Payoff) - Complexity: High - Multiple calculations (accrued interest, discounts, prepayment penalties)
- Multi-Entity Impact: LoanAccount, all LoanSchedules, DepositAccount, GL accounts
- Critical Requirement: Accurate payoff amount calculation with accrued interest and discounts
Payoff Components
- Outstanding Principal: Remaining principal balance across all schedules
- Accrued Interest: Interest accrued up to payoff date
- Unpaid Fees: Any outstanding fees or charges
- Unpaid Penalties: Any arrears penalties
- Prepayment Penalty: Fee for early settlement (if configured)
- Interest Discount: Reduction for early payment (if configured)
Payoff Formula:
PayoffAmount = OutstandingPrincipal
+ AccruedInterest
+ UnpaidFees
+ UnpaidPenalties
+ PrepaymentPenalty
- InterestDiscount
Transaction Flow
Entities Impacted
Scenario 1: Full Payoff with Early Settlement Discount
Context:
- Loan: NGN 1,000,000 principal, 18% annual interest
- Outstanding Principal: NGN 600,000 (40% paid)
- 12 schedules remaining (of 36 total)
- Accrued Interest: NGN 45,000 (30 days since last payment)
- No unpaid fees or penalties
- Early Settlement Discount: 10% of remaining interest (NGN 4,500)
- No prepayment penalty
Payoff Calculation:
PayoffAmount = 600,000 (principal)
+ 45,000 (accrued interest)
+ 0 (fees)
+ 0 (penalties)
+ 0 (prepayment penalty)
- 4,500 (10% interest discount)
= 640,500
```text
**Entities Impacted** (18 ImpactedEntity records):
```typescript
{
transactionId: "TXN789",
transactionType: "Loan Payoff",
payoffAmount: 640500.00,
payoffDate: "2025-12-28",
impactedEntities: [
// 1-12: LoanSchedule impacts (12 outstanding schedules)
// Schedule 25 (next due)
{
entityType: "LoanSchedule",
entityId: 225,
entityKey: "SCH-225",
fieldName: "PrincipalPaid",
oldValue: 0.00,
newValue: 50000.00,
deltaAmount: +50000.00
},
{
entityType: "LoanSchedule",
entityId: 225,
fieldName: "InterestPaid",
oldValue: 0.00,
newValue: 3750.00, // Accrued interest portion
deltaAmount: +3750.00
},
{
entityType: "LoanSchedule",
entityId: 225,
fieldName: "OutstandingBalance",
oldValue: 50000.00,
newValue: 0.00,
deltaAmount: -50000.00
},
{
entityType: "LoanSchedule",
entityId: 225,
fieldName: "State",
oldValue: "ACTIVE",
newValue: "CLOSED",
deltaAmount: 0
},
// ... (8 more schedules, each with 4 field changes = 32 records)
// Schedules 226-233 (similar structure)
// 13-18: LoanAccount impacts
{
entityType: "LoanAccount",
entityId: 101,
entityKey: "LOAN-101",
fieldName: "PrincipalBalance",
oldValue: 600000.00,
newValue: 0.00,
deltaAmount: -600000.00
},
{
entityType: "LoanAccount",
entityId: 101,
fieldName: "InterestBalance",
oldValue: 135000.00, // Remaining scheduled interest
newValue: 0.00,
deltaAmount: -135000.00
},
{
entityType: "LoanAccount",
entityId: 101,
fieldName: "TotalPaid",
oldValue: 854000.00, // Previous payments
newValue: 1494500.00, // +640,500
deltaAmount: +640500.00
},
{
entityType: "LoanAccount",
entityId: 101,
fieldName: "PayoffDate",
oldValue: null,
newValue: "2025-12-28",
deltaAmount: 0
},
{
entityType: "LoanAccount",
entityId: 101,
fieldName: "State",
oldValue: "ACTIVE",
newValue: "CLOSED",
deltaAmount: 0
},
{
entityType: "LoanAccount",
entityId: 101,
fieldName: "ClosureDate",
oldValue: null,
newValue: "2025-12-28",
deltaAmount: 0
},
// 19-20: DepositAccount impacts
{
entityType: "DepositAccount",
entityId: 501,
entityKey: "DEP-501",
fieldName: "BookBalance",
oldValue: 850000.00,
newValue: 209500.00,
deltaAmount: -640500.00
},
{
entityType: "DepositAccount",
entityId: 501,
fieldName: "AvailableBalance",
oldValue: 850000.00,
newValue: 209500.00,
deltaAmount: -640500.00
},
// 21-24: GL Account impacts
{
entityType: "GLAccount",
entityKey: "1101-LOANS-TO-CUSTOMERS",
fieldName: "CreditAmount",
deltaAmount: +600000.00 // CR: Reduce asset
},
{
entityType: "GLAccount",
entityKey: "1105-INTEREST-RECEIVABLE",
fieldName: "CreditAmount",
deltaAmount: +45000.00 // CR: Reduce receivable
},
{
entityType: "GLAccount",
entityKey: "4101-INTEREST-INCOME",
fieldName: "CreditAmount",
deltaAmount: -4500.00 // CR (negative): Interest discount given
},
{
entityType: "GLAccount",
entityKey: "2101-CUSTOMER-DEPOSITS",
fieldName: "DebitAmount",
deltaAmount: +640500.00 // DR: Reduce liability
}
]
}
```text
**Note**: In reality, all 12 outstanding schedules would have 4 field changes each (48 records total for schedules alone).
---
### Scenario 2: Payoff with Prepayment Penalty
**Context**:
- Loan: NGN 5,000,000 principal, 20% annual interest
- Outstanding Principal: NGN 4,200,000 (16% paid)
- 34 schedules remaining (of 36 total)
- Accrued Interest: NGN 210,000 (45 days since last payment)
- Unpaid Penalties: NGN 15,000 (arrears from previous schedule)
- Unpaid Fees: NGN 5,000 (late payment fee)
- **Prepayment Penalty**: 2% of outstanding principal (NGN 84,000)
- No early settlement discount (penalty takes precedence)
**Payoff Calculation**:
```typescript
PayoffAmount = 4,200,000 (principal)
+ 210,000 (accrued interest)
+ 5,000 (fees)
+ 15,000 (penalties)
+ 84,000 (prepayment penalty - 2%)
- 0 (no discount)
= 4,514,000
```text
**Key Impacts**:
```typescript
{
transactionId: "TXN790",
payoffAmount: 4514000.00,
impactedEntities: [
// LoanAccount impacts
{
entityType: "LoanAccount",
entityId: 102,
fieldName: "PrincipalBalance",
oldValue: 4200000.00,
newValue: 0.00,
deltaAmount: -4200000.00
},
{
entityType: "LoanAccount",
entityId: 102,
fieldName: "InterestBalance",
oldValue: 1680000.00,
newValue: 0.00,
deltaAmount: -1680000.00
},
{
entityType: "LoanAccount",
entityId: 102,
fieldName: "PenaltyBalance",
oldValue: 15000.00,
newValue: 0.00,
deltaAmount: -15000.00
},
{
entityType: "LoanAccount",
entityId: 102,
fieldName: "FeesBalance",
oldValue: 5000.00,
newValue: 0.00,
deltaAmount: -5000.00
},
{
entityType: "LoanAccount",
entityId: 102,
fieldName: "PrepaymentPenalty",
oldValue: 0.00,
newValue: 84000.00,
deltaAmount: +84000.00
},
{
entityType: "LoanAccount",
entityId: 102,
fieldName: "TotalPaid",
oldValue: 1294000.00,
newValue: 5808000.00,
deltaAmount: +4514000.00
},
{
entityType: "LoanAccount",
entityId: 102,
fieldName: "State",
oldValue: "ACTIVE",
newValue: "CLOSED",
deltaAmount: 0
},
// GL for Prepayment Penalty Income
{
entityType: "GLAccount",
entityKey: "4105-PREPAYMENT-PENALTY-INCOME",
fieldName: "CreditAmount",
deltaAmount: +84000.00
}
]
}
```text
---
## Payoff Amount Calculation Algorithm
### Step 1: Load Outstanding Schedules
:::warning[Code Removed]
**Implementation details removed for security.**
Contact support for implementation guidance.
:::text
### Step 2: Calculate Outstanding Principal
:::warning[Code Removed]
**Implementation details removed for security.**
Contact support for implementation guidance.
:::text
### Step 3: Calculate Accrued Interest
:::warning[Code Removed]
**Implementation details removed for security.**
Contact support for implementation guidance.
:::text
### Step 4: Sum Unpaid Fees and Penalties
:::warning[Code Removed]
**Implementation details removed for security.**
Contact support for implementation guidance.
:::text
### Step 5: Calculate Prepayment Penalty (if applicable)
:::warning[Code Removed]
**Implementation details removed for security.**
Contact support for implementation guidance.
:::text
### Step 6: Calculate Early Settlement Discount (if applicable)
:::warning[Code Removed]
**Implementation details removed for security.**
Contact support for implementation guidance.
:::text
### Step 7: Calculate Total Payoff Amount
:::warning[Code Removed]
**Implementation details removed for security.**
Contact support for implementation guidance.
:::text
---
## Payoff Transaction Execution
:::warning[Code Removed]
**Implementation details removed for security.**
Contact support for implementation guidance.
:::text
---
## GL Posting Examples
### Example 1: Payoff with Interest Discount
**Transaction**: Payoff NGN 640,500 (Principal 600K + Interest 45K - Discount 4.5K)
```text
DR: 1101-LOANS-TO-CUSTOMERS 600,000.00 (Reduce asset)
DR: 1105-INTEREST-RECEIVABLE 45,000.00 (Accrued interest received)
CR: 4101-INTEREST-INCOME -4,500.00 (Discount given)
CR: 2101-CUSTOMER-DEPOSITS 640,500.00 (Debit customer account)
```text
**Net Effect**:
- Assets decrease: 600,000 (loan) + 45,000 (receivable) = 645,000
- Liabilities decrease: 640,500 (deposits)
- Income reduced: 4,500 (discount)
### Example 2: Payoff with Prepayment Penalty
**Transaction**: Payoff NGN 4,514,000 (Principal 4.2M + Interest 210K + Penalties 15K + Fees 5K + Prepayment 84K)
```text
DR: 1101-LOANS-TO-CUSTOMERS 4,200,000.00 (Reduce loan asset)
DR: 1105-INTEREST-RECEIVABLE 210,000.00 (Accrued interest)
DR: 1106-FEES-RECEIVABLE 5,000.00 (Unpaid fees)
DR: 1107-PENALTIES-RECEIVABLE 15,000.00 (Arrears penalties)
CR: 4105-PREPAYMENT-PENALTY-INCOME 84,000.00 (Penalty income)
CR: 2101-CUSTOMER-DEPOSITS 4,514,000.00 (Debit customer)
```text
**Net Effect**:
- Assets decrease: 4,430,000 (loans + receivables)
- Liabilities decrease: 4,514,000 (deposits)
- Income increase: 84,000 (prepayment penalty)
---
## API Usage
### Request: Calculate Payoff Amount
```http
POST /api/v2/loans/{loanAccountId}/payoff/calculate
Content-Type: application/json
{
"payoffDate": "2025-12-28"
}
```text
### Response: Payoff Calculation
```json
{
"loanAccountId": 101,
"loanAccountKey": "LOAN-101",
"payoffDate": "2025-12-28",
"calculationDate": "2025-12-28T10:30:00Z",
"calculation": {
"outstandingPrincipal": 600000.00,
"accruedInterest": 45000.00,
"unpaidFees": 0.00,
"unpaidPenalties": 0.00,
"prepaymentPenalty": 0.00,
"interestDiscount": 4500.00,
"totalPayoffAmount": 640500.00
},
"outstandingSchedules": [
{
"scheduleNumber": 25,
"dueDate": "2026-01-15",
"principalDue": 50000.00,
"principalPaid": 0.00,
"interestDue": 15000.00,
"interestPaid": 0.00,
"state": "ACTIVE"
}
// ... 11 more schedules
],
"payoffStatement": {
"loanOriginalAmount": 1000000.00,
"disbursementDate": "2023-01-15",
"totalPaidToDate": 854000.00,
"remainingSchedules": 12,
"maturityDate": "2026-12-15",
"monthsRemaining": 24,
"savingsFromEarlyPayoff": 4500.00
}
}
```text
### Request: Execute Payoff
```http
POST /api/v2/loans/{loanAccountId}/payoff/execute
Content-Type: application/json
Authorization: Bearer {token}
{
"depositAccountKey": "DEP-501",
"payoffDate": "2025-12-28",
"paymentAmount": 640500.00,
"calculationId": "CALC-12345", // From calculate response
"notes": "Early payoff with 10% interest discount"
}
```text
### Response: Payoff Executed
```json
{
"transactionId": 789,
"transactionKey": "TXN-789",
"transactionType": "LOAN_PAYOFF",
"state": "COMPLETED",
"loanAccountId": 101,
"loanAccountKey": "LOAN-101",
"depositAccountKey": "DEP-501",
"payoffAmount": 640500.00,
"payoffDate": "2025-12-28",
"completedDate": "2025-12-28T10:35:00Z",
"breakdown": {
"outstandingPrincipal": 600000.00,
"accruedInterest": 45000.00,
"unpaidFees": 0.00,
"unpaidPenalties": 0.00,
"prepaymentPenalty": 0.00,
"interestDiscount": 4500.00
},
"schedulesClosed": 12,
"loanAccountState": "CLOSED",
"loanClosureDate": "2025-12-28T10:35:00Z",
"impactedEntities": [
{
"entityType": "LoanAccount",
"entityId": 101,
"changes": [
{
"fieldName": "PrincipalBalance",
"oldValue": 600000.00,
"newValue": 0.00,
"delta": -600000.00
},
{
"fieldName": "State",
"oldValue": "ACTIVE",
"newValue": "CLOSED"
}
]
},
{
"entityType": "DepositAccount",
"entityId": 501,
"changes": [
{
"fieldName": "BookBalance",
"oldValue": 850000.00,
"newValue": 209500.00,
"delta": -640500.00
}
]
}
]
}
```text
---
## Validation Rules
### Pre-Execution Validation
1. **Loan Account State**:
- Must be `ACTIVE`
- Cannot be `CLOSED`, `WRITTEN_OFF`, or `CANCELLED`
2. **Outstanding Balance**:
- Must have at least one ACTIVE or OVERDUE schedule
- Total outstanding balance > 0
3. **Payment Source Validation**:
- Deposit account must be ACTIVE
- Sufficient available balance (>= payoff amount)
- Account not LOCKED or FROZEN
4. **Payoff Date**:
- Cannot be before last payment date
- Cannot be more than 30 days in the future (configurable)
- Must be on or after current business date
5. **Calculation Validity**:
- Payment amount must match calculated payoff amount (±0.01 tolerance)
- Calculation must be recent (< 24 hours old)
6. **Approval Requirements**:
- Check if amount exceeds auto-approval limit
- Verify approver authorization if manual approval
### Post-Execution Validation
7. **All Schedules Closed**:
- Verify all schedules marked as CLOSED
- Confirm OutstandingBalance = 0 for all schedules
8. **Loan Account Zeroed**:
- PrincipalBalance = 0
- InterestBalance = 0
- PenaltyBalance = 0
- FeesBalance = 0
9. **GL Balancing**:
- Total debits = Total credits
- All GL entries posted successfully
---
## Error Codes
| Code | Description | Resolution |
|------|-------------|------------|
| **PAYOFF_001** | Loan account not found | Verify loan account exists |
| **PAYOFF_002** | Loan account not ACTIVE | Only active loans can be paid off |
| **PAYOFF_003** | No outstanding schedules | Loan already paid off |
| **PAYOFF_004** | Insufficient funds | Increase deposit balance or reduce payoff amount |
| **PAYOFF_005** | Payment amount mismatch | Recalculate payoff amount |
| **PAYOFF_006** | Calculation expired | Recalculate (calculations valid for 24 hours) |
| **PAYOFF_007** | Payoff date invalid | Use current or recent date |
| **PAYOFF_008** | Deposit account locked | Unlock account before payoff |
| **PAYOFF_009** | Approval required | Submit for approval |
| **PAYOFF_010** | GL posting failed | Contact support |
---
## Best Practices
### For Product Managers
1. **Prepayment Penalty Configuration**:
- Set penalty rates that balance early payoff flexibility with lender revenue
- Typical range: 0-5% of outstanding principal
- Consider penalty period (e.g., waive after 12-24 months)
2. **Interest Discount Configuration**:
- Reward early payoffs with interest discounts
- Typical range: 5-15% of remaining scheduled interest
- Ensure discount > penalty for customer benefit
3. **Approval Workflows**:
- Set auto-approval limits based on loan size and risk
- Large payoffs may warrant review for prepayment risk
### For Developers
1. **Calculation Accuracy**:
- Use high-precision decimal arithmetic (not float)
- Round only at final step
- Maintain calculation audit trail
2. **Concurrent Safety**:
- Lock both loan and deposit accounts during execution
- Use database transactions
- Implement optimistic locking with version fields
3. **Impact Tracking**:
- Track EVERY schedule closure with field-level deltas
- Store calculation breakdown for audit
- Link GL entries to transaction
### For Bank Operations
1. **Payoff Statement Accuracy**:
- Generate statements with calculation breakdown
- Include savings from early payoff (discount - penalty)
- Verify customer understanding before execution
2. **Reconciliation**:
- Daily reconciliation of payoff transactions
- Verify GL postings match transaction records
- Audit schedule closures and account states
3. **Customer Communication**:
- Provide clear payoff quotes with expiration dates
- Explain prepayment penalties and discounts
- Confirm loan closure and provide closure certificate
---
## V2 API Commands
BankLingo V2 provides a BPMCore-compatible command for loan payoff that integrates with the BPM workflow engine.
### Architecture Overview
The V2 loan payoff command follows the **Delegation Pattern**:
- **V2 Facade**: `InitiateLoanPayOffCommand` (BPMCore compatible)
- **V1 Implementation**: Delegates to existing V1 `LoanPayOffCommand` with full business logic
- **BPM Integration**: Accepts parameters via `BpmUtil.GetPropertyValue()`
- **Impact Tracking**: Leverages existing `TransactionImpactTracker`
**Why Delegation?** The V1 implementation already includes:
- ✅ Complete payoff calculation logic (principal + accrued interest + fees + penalties)
- ✅ Prepayment penalty and early settlement discount calculations
- ✅ Schedule closure with balance tracking
- ✅ Loan account state management (ACTIVE → CLOSED)
- ✅ Impact tracking for all entity changes
- ✅ Approval workflow support
**Implementation**: `CB.Administration.Api/Commands/BPMCore/Loans/AdministrationCoreLoanCommandHandlers.Transactions.cs`
---
### InitiateLoanPayOffCommand
**Purpose**: Initiate a loan payoff (full settlement) transaction
**Command**: `InitiateLoanPayOffCommand`
**Delegation**: → `LoanPayOffCommand` (V1 implementation)
#### BPM Parameters
```json
{
"commandName": "InitiateLoanPayOffCommand",
"data": {
"accountEncodedKey": "string (mandatory)",
"clientEncodedKey": "string (mandatory)",
"paymentSourceAccountKey": "string (mandatory)",
"transactionDate": "DateTime (optional)",
"notes": "string (optional)"
}
}
Parameter Details
| Parameter | Type | Required | Description |
|---|---|---|---|
accountEncodedKey | string | ✅ Yes | Loan account to pay off |
clientEncodedKey | string | ✅ Yes | Client/borrower encoded key |
paymentSourceAccountKey | string | ✅ Yes | Deposit account for payment source |
transactionDate | DateTime | ⌠No | Payoff date (defaults to today) |
notes | string | ⌠No | Payoff notes/remarks |
Payoff Calculation (Automated)
The command automatically calculates the full payoff amount:
PayoffAmount = OutstandingPrincipal
+ AccruedInterest // Up to payoff date
+ UnpaidFees
+ UnpaidPenalties
+ PrepaymentPenalty // If configured
- InterestDiscount // If configured
Components Included:
- ✅ Outstanding principal across all schedules
- ✅ Accrued interest (calculated to payoff date)
- ✅ Unpaid fees from all schedules
- ✅ Unpaid penalties (arrears charges)
- ✅ Prepayment penalty (if loan product configured)
- ✅ Early settlement discount (if loan product configured)
Balance Impact
Loan Account:
| Balance Field | Change | Reason |
|---|---|---|
PrincipalBalance | → 0 | All principal paid |
InterestBalance | → 0 | All interest paid |
PenaltyBalance | → 0 | All penalties paid |
FeesBalance | → 0 | All fees paid |
TotalPaid | +PayoffAmount | Track total paid |
State | → CLOSED | Loan fully settled |
PayoffDate | Set to transaction date | Record payoff date |
Deposit Account (Payment Source):
| Balance Field | Change | Reason |
|---|---|---|
AvailableBalance | -PayoffAmount | Funds withdrawn |
BookBalance | -PayoffAmount | Funds withdrawn |
Loan Schedules (All outstanding schedules):
| Field | Change | Reason |
|---|---|---|
PrincipalPaid | += remaining principal | Close schedule |
InterestPaid | += remaining interest | Close schedule |
FeesPaid | += remaining fees | Close schedule |
PenaltyPaid | += remaining penalties | Close schedule |
OutstandingBalance | → 0 | Schedule closed |
State | → CLOSED | Schedule closed |
Transaction States
States:
- PENDING: Payoff initiated, awaiting approval (if required)
- APPROVED: Approved, ready for execution
- SETTLED: Payoff completed, loan closed
- REJECTED: Payoff rejected (loan remains active)
Approval Workflow
- ✅ Auto-approval if payoff amount < configured limit
- âš ï¸ Manual approval required if payoff amount ≥ limit
- 🔒 Funds placed on hold during approval (available balance reduced)
- 🔓 Hold released on rejection
Example Request/Response
Request:
{
"commandName": "InitiateLoanPayOffCommand",
"data": {
"accountEncodedKey": "8a8080827f23loan017f23def456",
"clientEncodedKey": "8a8080827f23client017f23xyz789",
"paymentSourceAccountKey": "8a8080827f23dep017f23abc123",
"transactionDate": "2025-12-29T10:00:00Z",
"notes": "Early settlement - borrower paid off loan"
}
}
Response:
{
"isSuccessful": true,
"transactionId": "TXN-LOAN-PYOF-20251229-0001",
"transactionState": "SETTLED",
"message": "Loan payoff completed successfully",
"data": {
"loanAccountKey": "8a8080827f23loan017f23def456",
"payoffAmount": 640500.00,
"payoffDate": "2025-12-29T10:00:00Z",
"components": {
"outstandingPrincipal": 600000.00,
"accruedInterest": 45000.00,
"unpaidFees": 0.00,
"unpaidPenalties": 0.00,
"prepaymentPenalty": 0.00,
"interestDiscount": -4500.00
},
"loanState": "CLOSED",
"depositAccountBalance": {
"previousBalance": 1000000.00,
"newBalance": 359500.00,
"deducted": 640500.00
},
"schedulesClosedCount": 12,
"impactRecords": 15
}
}
Impact Tracking
All entity changes are tracked via TransactionImpactTracker:
Impacted Entities:
- ✅ Loan Account (6 field changes: balances + state + payoff date)
- ✅ All outstanding Loan Schedules (~4 field changes each)
- ✅ Deposit Account (2 field changes: available + book balance)
- ✅ GL Accounts (journal entries for settlement)
Example Impact Records (per schedule):
{
entityType: "LoanSchedule",
entityId: 12345,
entityKey: "SCH-12345",
fieldName: "OutstandingBalance",
oldValue: 50000.00,
newValue: 0.00,
deltaAmount: -50000.00,
isReversal: false
}
Validation Rules
The command performs comprehensive validation:
✅ Loan Account Validation:
- Loan account must be ACTIVE (not CLOSED, WRITTEN_OFF)
- Loan account must belong to specified client
- Loan account must have outstanding balance > 0
✅ Payment Source Validation:
- Deposit account must be ACTIVE (not CLOSED, LOCKED)
- Deposit account must belong to same client
- Deposit account available balance ≥ calculated payoff amount
✅ Date Validation:
- Transaction date must be ≤ today
- Transaction date must be ≥ loan activation date
⌠Rejection Scenarios:
- Loan account not found or not active
- Payment source insufficient funds
- Deposit account locked or closed
- Client mismatch between loan and deposit accounts
- Payoff calculation fails (invalid product configuration)
Error Handling
Common Errors:
| Error | Reason | Resolution |
|---|---|---|
INSUFFICIENT_FUNDS | Payment source balance < payoff amount | Fund deposit account or use different source |
LOAN_ACCOUNT_NOT_ACTIVE | Loan already closed or written off | Cannot pay off inactive loan |
ACCOUNT_LOCKED | Deposit account is locked | Unlock account first |
CLIENT_MISMATCH | Loan and deposit accounts belong to different clients | Use correct payment source |
INVALID_PAYOFF_AMOUNT | Calculation error or negative amount | Check loan product configuration |
Business Rules
-
Prepayment Penalty: Applied if loan product configured (e.g., 2% of outstanding principal if paid off within first 12 months)
-
Early Settlement Discount: Applied if loan product configured (e.g., 10% discount on remaining interest if paid off early)
-
Accrued Interest: Calculated from last payment date to payoff date using configured interest calculation method
-
Schedule Closure: All outstanding schedules are closed simultaneously with balances set to 0
-
Loan Closure: Loan account state transitions to CLOSED immediately after payoff
-
Reversibility: Payoff transactions can be reversed if executed in error (see
ReverseTransactionCommand)
Integration with Other Commands
Workflow Combination:
-
Get Payoff Quote (optional):
// Call separate GetPayoffQuoteQuery to show customer the amount
// before executing payoff -
Execute Payoff:
// InitiateLoanPayOffCommand -
Reverse if Error (if needed):
// ReverseTransactionCommand with original transaction ID
Performance Considerations
- Schedule Loading: Loads all outstanding schedules (optimized query with indexing)
- Calculation Time: Payoff calculation typically < 100ms for up to 100 schedules
- Transaction Locking: Uses row-level locking to prevent concurrent modifications
- Impact Tracking: Records 10-20 impact entries per payoff (depending on schedule count)
Developer Documentation
For comprehensive technical implementation details, see:
- V2 Loan Commands Documentation: Available in developer technical docs
- V1 Implementation:
CB.Administration.Api/Commands/Loans/LoanPayOffCommand.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 business logic
- ✅ Complete payoff calculation (principal + interest + fees + penalties + discounts)
- ✅ Schedule closure with impact tracking
- ✅ Loan account state management
- ✅ Approval workflow support
- ✅ Reversal support via universal commands
Implementation Checklist
Phase 1: Payoff Calculation
- Implement
CalculatePayoffAmountAsyncwith all components - Add prepayment penalty logic (rate, period)
- Add early settlement discount logic
- Unit tests for calculation accuracy (10+ scenarios)
- Validate calculation expiration (24-hour validity)
Phase 2: Payoff Execution
- Implement
ProcessLoanPayoffAsyncwith approval workflow - Add schedule closure logic with impact tracking
- Add loan account closure with state management
- Add deposit account debit with validation
- Integration tests for payoff execution
Phase 3: GL Posting
- Implement
PostPayoffGLEntriesAsyncfor all scenarios - Handle interest discount GL entries (negative credit)
- Handle prepayment penalty GL entries
- Unit tests for GL balancing
Phase 4: API & Validation
- Create payoff calculation endpoint
- Create payoff execution endpoint
- Add validation rules (10 rules)
- Add error handling with specific error codes
- API integration tests
Phase 5: Testing & Deployment
- End-to-end tests (5+ scenarios)
- Performance tests (100+ concurrent payoffs)
- Load test calculation endpoint
- User acceptance testing
- Production deployment with monitoring
Developer Resources
For API implementation details, see:
Document Version: 1.0
Last Updated: December 28, 2025
Related Documents: