Skip to main content

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

  1. Outstanding Principal: Remaining principal balance across all schedules
  2. Accrued Interest: Interest accrued up to payoff date
  3. Unpaid Fees: Any outstanding fees or charges
  4. Unpaid Penalties: Any arrears penalties
  5. Prepayment Penalty: Fee for early settlement (if configured)
  6. 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

ParameterTypeRequiredDescription
accountEncodedKeystring✅ YesLoan account to pay off
clientEncodedKeystring✅ YesClient/borrower encoded key
paymentSourceAccountKeystring✅ YesDeposit account for payment source
transactionDateDateTime❌ NoPayoff date (defaults to today)
notesstring❌ NoPayoff 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 FieldChangeReason
PrincipalBalance→ 0All principal paid
InterestBalance→ 0All interest paid
PenaltyBalance→ 0All penalties paid
FeesBalance→ 0All fees paid
TotalPaid+PayoffAmountTrack total paid
State→ CLOSEDLoan fully settled
PayoffDateSet to transaction dateRecord payoff date

Deposit Account (Payment Source):

Balance FieldChangeReason
AvailableBalance-PayoffAmountFunds withdrawn
BookBalance-PayoffAmountFunds withdrawn

Loan Schedules (All outstanding schedules):

FieldChangeReason
PrincipalPaid+= remaining principalClose schedule
InterestPaid+= remaining interestClose schedule
FeesPaid+= remaining feesClose schedule
PenaltyPaid+= remaining penaltiesClose schedule
OutstandingBalance→ 0Schedule closed
State→ CLOSEDSchedule closed

Transaction States

States:

  1. PENDING: Payoff initiated, awaiting approval (if required)
  2. APPROVED: Approved, ready for execution
  3. SETTLED: Payoff completed, loan closed
  4. 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:

ErrorReasonResolution
INSUFFICIENT_FUNDSPayment source balance < payoff amountFund deposit account or use different source
LOAN_ACCOUNT_NOT_ACTIVELoan already closed or written offCannot pay off inactive loan
ACCOUNT_LOCKEDDeposit account is lockedUnlock account first
CLIENT_MISMATCHLoan and deposit accounts belong to different clientsUse correct payment source
INVALID_PAYOFF_AMOUNTCalculation error or negative amountCheck loan product configuration

Business Rules

  1. Prepayment Penalty: Applied if loan product configured (e.g., 2% of outstanding principal if paid off within first 12 months)

  2. Early Settlement Discount: Applied if loan product configured (e.g., 10% discount on remaining interest if paid off early)

  3. Accrued Interest: Calculated from last payment date to payoff date using configured interest calculation method

  4. Schedule Closure: All outstanding schedules are closed simultaneously with balances set to 0

  5. Loan Closure: Loan account state transitions to CLOSED immediately after payoff

  6. Reversibility: Payoff transactions can be reversed if executed in error (see ReverseTransactionCommand)

Integration with Other Commands

Workflow Combination:

  1. Get Payoff Quote (optional):

    // Call separate GetPayoffQuoteQuery to show customer the amount
    // before executing payoff
  2. Execute Payoff:

    // InitiateLoanPayOffCommand
  3. 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 CalculatePayoffAmountAsync with 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 ProcessLoanPayoffAsync with 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 PostPayoffGLEntriesAsync for 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: