Transfer Transaction
Complete guide to transfer transactions in BankLingo V2, including atomic operations, deadlock prevention, and multi-account impact tracking.
Overviewā
A Transfer Transaction moves funds from one deposit account to another. Transfers require atomic operations to ensure both accounts are updated consistently.
Key Characteristics:
- Atomic Operations: Both debit and credit must succeed or both fail
- Deadlock Prevention: Accounts locked in consistent order (ascending by ID)
- Intra-Bank vs Inter-Bank: Different settlement handling
- Approval Workflow: Large transfers may require approval
- Reversal Support: Full reversal with restoration of both accounts
Transaction Types:
- Intra-Bank Transfer: Between accounts in same bank
- Inter-Bank Transfer: To account in different bank (via settlement)
- Own Account Transfer: Between customer's own accounts (may have reduced fees)
Transaction Flowā
Entities Impactedā
Scenario 1: Intra-Bank Transferā
Transfer NGN 50,000 from Account A to Account B
PENDING Phase:
{
transactionId: "TRF123456",
transactionState: "PENDING",
impactedEntities: [
// Source Account (Debit)
{
entityType: "DepositAccount",
entityId: 100,
entityKey: "ACC-SOURCE",
fieldName: "AvailableBalance",
oldValue: 100000.00,
newValue: 49900.00, // 100000 - 50000 - 100 (fee)
deltaAmount: -50100.00
},
{
entityType: "DepositAccount",
entityId: 100,
entityKey: "ACC-SOURCE",
fieldName: "HoldAmount",
oldValue: 0.00,
newValue: 50100.00,
deltaAmount: +50100.00
},
// Destination Account (Credit - pending)
{
entityType: "DepositAccount",
entityId: 200,
entityKey: "ACC-DEST",
fieldName: "AvailableBalance",
oldValue: 50000.00,
newValue: 100000.00, // Pending credit visible
deltaAmount: +50000.00
},
{
entityType: "DepositAccount",
entityId: 200,
entityKey: "ACC-DEST",
fieldName: "PendingCredits",
oldValue: 0.00,
newValue: 50000.00,
deltaAmount: +50000.00
}
]
}
```text
**COMPLETED Phase**:
```typescript
{
transactionId: "TRF123456",
transactionState: "COMPLETED",
impactedEntities: [
// Source Account - Release Hold, Update BookBalance
{
entityType: "DepositAccount",
entityId: 100,
entityKey: "ACC-SOURCE",
fieldName: "BookBalance",
oldValue: 100000.00,
newValue: 49900.00,
deltaAmount: -50100.00
},
{
entityType: "DepositAccount",
entityId: 100,
entityKey: "ACC-SOURCE",
fieldName: "HoldAmount",
oldValue: 50100.00,
newValue: 0.00,
deltaAmount: -50100.00
},
// Destination Account - Finalize Credit
{
entityType: "DepositAccount",
entityId: 200,
entityKey: "ACC-DEST",
fieldName: "BookBalance",
oldValue: 50000.00,
newValue: 100000.00,
deltaAmount: +50000.00
},
{
entityType: "DepositAccount",
entityId: 200,
entityKey: "ACC-DEST",
fieldName: "PendingCredits",
oldValue: 50000.00,
newValue: 0.00,
deltaAmount: -50000.00
},
// GL Accounts
{
entityType: "GLAccount",
entityKey: "2100-001", // Customer Deposits (Source)
fieldName: "DebitAmount",
deltaAmount: +50000.00
},
{
entityType: "GLAccount",
entityKey: "2100-001", // Customer Deposits (Dest)
fieldName: "CreditAmount",
deltaAmount: +50000.00
},
{
entityType: "GLAccount",
entityKey: "4100-004", // Transfer Fee Income
fieldName: "CreditAmount",
deltaAmount: +100.00
}
]
}
```text
### Scenario 2: Inter-Bank Transfer
**Transfer NGN 100,000 to external bank via settlement**
```typescript
{
transactionId: "TRF789012",
transactionState: "COMPLETED",
impactedEntities: [
// Source Account
{
entityType: "DepositAccount",
entityId: 100,
entityKey: "ACC-SOURCE",
fieldName: "AvailableBalance",
oldValue: 200000.00,
newValue: 99700.00, // 200000 - 100000 - 300 (inter-bank fee)
deltaAmount: -100300.00
},
{
entityType: "DepositAccount",
entityId: 100,
entityKey: "ACC-SOURCE",
fieldName: "BookBalance",
oldValue: 200000.00,
newValue: 99700.00,
deltaAmount: -100300.00
},
// Settlement Account
{
entityType: "SettlementAccount",
entityId: 50,
entityKey: "NIBSS-SETTLE-001",
fieldName: "PendingDebits",
oldValue: 500000.00,
newValue: 600000.00,
deltaAmount: +100000.00
},
{
entityType: "SettlementAccount",
entityId: 50,
entityKey: "NIBSS-SETTLE-001",
fieldName: "TransactionCount",
oldValue: 150,
newValue: 151,
deltaAmount: +1
},
// GL Accounts
{
entityType: "GLAccount",
entityKey: "2100-001", // Customer Deposits
fieldName: "DebitAmount",
deltaAmount: +100000.00
},
{
entityType: "GLAccount",
entityKey: "1200-001", // Settlement Account (NIBSS)
fieldName: "CreditAmount",
deltaAmount: +100000.00
},
{
entityType: "GLAccount",
entityKey: "4100-005", // Inter-Bank Transfer Fee Income
fieldName: "CreditAmount",
deltaAmount: +300.00
}
]
}
```text
### Scenario 3: Own Account Transfer
**Transfer NGN 20,000 between customer's savings and current accounts (zero fee)**
```typescript
{
transactionId: "TRF345678",
transactionState: "COMPLETED",
impactedEntities: [
// Savings Account (Source)
{
entityType: "DepositAccount",
entityId: 300,
entityKey: "SAV-001",
fieldName: "AvailableBalance",
oldValue: 80000.00,
newValue: 60000.00,
deltaAmount: -20000.00
},
{
entityType: "DepositAccount",
entityId: 300,
entityKey: "SAV-001",
fieldName: "BookBalance",
oldValue: 80000.00,
newValue: 60000.00,
deltaAmount: -20000.00
},
// Current Account (Destination)
{
entityType: "DepositAccount",
entityId: 310,
entityKey: "CUR-001",
fieldName: "AvailableBalance",
oldValue: 15000.00,
newValue: 35000.00,
deltaAmount: +20000.00
},
{
entityType: "DepositAccount",
entityId: 310,
entityKey: "CUR-001",
fieldName: "BookBalance",
oldValue: 15000.00,
newValue: 35000.00,
deltaAmount: +20000.00
},
// GL (Internal Transfer - no net change to deposits)
{
entityType: "GLAccount",
entityKey: "2100-001", // Customer Deposits
fieldName: "DebitAmount",
deltaAmount: +20000.00
},
{
entityType: "GLAccount",
entityKey: "2100-001", // Customer Deposits
fieldName: "CreditAmount",
deltaAmount: +20000.00
}
]
}
```text
---
## Deadlock Prevention
### The Deadlock Problem
**Without Proper Locking Order**:
```text
Transaction A: Transfer from Account 100 to Account 200
Transaction B: Transfer from Account 200 to Account 100
Timeline:
1. Transaction A locks Account 100
2. Transaction B locks Account 200
3. Transaction A tries to lock Account 200 (waits for B to release)
4. Transaction B tries to lock Account 100 (waits for A to release)
5. DEADLOCK! Both transactions stuck forever.
```text
### Solution: Consistent Lock Order
**Always lock accounts in ascending order by AccountId**:
:::warning[Code Removed]
**Implementation details removed for security.**
Contact support for implementation guidance.
:::text
**Result**: Transaction A and B both lock Account 100 first, then Account 200. No deadlock possible.
---
## Approval Workflow
### Auto-Approval Conditions
Transfer is auto-approved if **ALL** conditions met:
1. Amount Ć¢ā°Ā¤ Auto-Approval Limit (from product config)
2. Source account state is ACTIVE
3. Destination account can receive credits (not CLOSED)
4. No fraud flags on either account
5. Beneficiary is whitelisted (if required by product)
6. Transfer limit not exceeded (single + daily)
### Manual Approval Process
**PENDING Ć¢ā ā APPROVAL Ć¢ā ā COMPLETED**
```typescript
// 1. Create transfer in PENDING state
const transfer = {
state: "PENDING",
amount: 500000, // Above auto-approval limit
sourceAccountId: 100,
destAccountId: 200,
approvalRequired: true
};
// 2. Holds placed immediately
sourceAccount.AvailableBalance -= 500000;
sourceAccount.HoldAmount += 500000;
destAccount.AvailableBalance += 500000; // Pending credit visible
destAccount.PendingCredits += 500000;
// 3. Notify approvers
await notificationService.NotifyApprovers({
transactionId: transfer.id,
amount: 500000,
sourceAccount: "ACC-100",
destAccount: "ACC-200",
reason: "Amount exceeds auto-approval limit"
});
// 4. Approver decision
if (approved) {
// Release holds, update book balances
sourceAccount.BookBalance -= 500000;
sourceAccount.HoldAmount -= 500000;
destAccount.BookBalance += 500000;
destAccount.PendingCredits -= 500000;
transfer.state = "COMPLETED";
} else {
// Reverse everything
sourceAccount.AvailableBalance += 500000;
sourceAccount.HoldAmount -= 500000;
destAccount.AvailableBalance -= 500000;
destAccount.PendingCredits -= 500000;
transfer.state = "REJECTED";
}
```text
---
## Limits & Validation
### Validation Rules
| Rule | Check | Error Code | Message |
|------|-------|------------|---------|
| **Source Account Active** | SourceAccount.State is ACTIVE | 05 | Source account not active |
| **Source Not Locked** | !SourceAccount.IsLocked | 05 | Source account locked |
| **Dest Account Valid** | DestAccount exists and not CLOSED | 14 | Invalid destination account |
| **Positive Amount** | Amount > 0 | 12 | Invalid amount |
| **Sufficient Balance** | Amount Ć¢ā°Ā¤ SourceAccount.AvailableBalance | 51 | Insufficient funds |
| **Single Transfer Limit** | Amount Ć¢ā°Ā¤ Product.TransferTransactionLimit | 61 | Amount exceeds transfer limit |
| **Daily Transfer Limit** | TodayTransfers + Amount Ć¢ā°Ā¤ Product.DailyTransferLimit | 65 | Daily transfer limit exceeded |
| **Beneficiary Whitelisting** | If required: Dest in WhitelistedBeneficiaries | 57 | Beneficiary not whitelisted |
| **Same Currency** | Source.Currency matches Dest.Currency | 12 | Currency mismatch |
| **Different Accounts** | SourceAccountId != DestAccountId | 12 | Cannot transfer to same account |
---
## Fee Calculation
### Fee Configuration
```yaml
transferFees:
- transferType: INTRA_BANK
ownAccount: true
feeType: FLAT
amount: 0.00
- transferType: INTRA_BANK
ownAccount: false
feeType: FLAT
amount: 100.00
- transferType: INTER_BANK
feeType: TIERED
tiers:
- minAmount: 0
maxAmount: 10000
fee: 200.00
- minAmount: 10001
maxAmount: 100000
fee: 500.00
- minAmount: 100001
maxAmount: null
fee: 1000.00
- transferType: INSTANT_TRANSFER
feeType: PERCENTAGE
percentage: 1.5
minAmount: 100.00
maxAmount: 5000.00
Calculation Logicā
Code Removed
Implementation details removed for security.
Contact support for implementation guidance.
GL Postingā
Journal Entriesā
Example 1: Intra-Bank Transfer
| Account | Description | Debit | Credit |
|---|---|---|---|
| 2100-001 | Customer Deposits (Source) | 50,000.00 | - |
| 2100-001 | Customer Deposits (Dest) | - | 50,000.00 |
| 4100-004 | Transfer Fee Income | - | 100.00 |
| 2100-001 | Customer Deposits (Fee from Source) | 100.00 | - |
Example 2: Inter-Bank Transfer
| Account | Description | Debit | Credit |
|---|---|---|---|
| 2100-001 | Customer Deposits (Source) | 100,000.00 | - |
| 1200-001 | Settlement Account (NIBSS) | - | 100,000.00 |
| 4100-005 | Inter-Bank Fee Income | - | 300.00 |
| 2100-001 | Customer Deposits (Fee from Source) | 300.00 | - |
Example 3: Own Account Transfer (Zero Fee)
| Account | Description | Debit | Credit |
|---|---|---|---|
| 2100-001 | Customer Deposits | 20,000.00 | - |
| 2100-001 | Customer Deposits | - | 20,000.00 |
API Usageā
Requestā
POST /api/v2/transactions/transfer
Content-Type: application/json
Authorization: Bearer {token}
{
"sourceAccountEncodedKey": "ACC-SOURCE",
"destAccountEncodedKey": "ACC-DEST",
"amount": 50000.00,
"transferType": "INTRA_BANK",
"narration": "Monthly rent payment",
"customerReference": "RENT-JAN-2025",
"beneficiaryName": "Jane Doe",
"channelType": "ONLINE_BANKING"
}
```text
### Response - Success (Auto-Approved)
```json
{
"success": true,
"transactionKey": "TRF123456",
"transactionId": 789012,
"state": "COMPLETED",
"sourceAccount": {
"accountNumber": "0123456789",
"accountName": "John Doe",
"oldBalance": 100000.00,
"newBalance": 49900.00
},
"destAccount": {
"accountNumber": "9876543210",
"accountName": "Jane Doe",
"oldBalance": 50000.00,
"newBalance": 100000.00
},
"transferAmount": 50000.00,
"feeAmount": 100.00,
"totalDebit": 50100.00,
"transactionDate": "2025-12-28T10:30:00Z",
"impactRecordId": 456,
"message": "Transfer successful"
}
```text
### Response - Pending Approval
```json
{
"success": true,
"transactionKey": "TRF123457",
"transactionId": 789013,
"state": "PENDING",
"sourceAccount": {
"accountNumber": "0123456789",
"holdPlaced": 500000.00,
"availableBalance": 0.00
},
"destAccount": {
"accountNumber": "9876543210",
"pendingCredit": 500000.00
},
"transferAmount": 500000.00,
"approvalRequired": true,
"message": "Transfer pending approval. Funds held."
}
```text
### Response - Error (Insufficient Funds)
```json
{
"success": false,
"errorCode": "51",
"errorMessage": "Insufficient funds",
"sourceAccount": {
"accountNumber": "0123456789",
"availableBalance": 30000.00
},
"requestedAmount": 50000.00,
"shortfall": 20000.00
}
```text
---
## Error Codes
| Code | Message | Cause | Resolution |
|------|---------|-------|------------|
| 00 | Success | Transaction completed | None |
| 05 | Account locked/inactive | Source or dest account not ACTIVE | Contact branch |
| 12 | Invalid amount/params | Amount Ć¢ā°Ā¤ 0, currency mismatch, same account | Correct parameters |
| 14 | Invalid destination | Dest account not found or CLOSED | Verify account number |
| 51 | Insufficient funds | Amount > Source.AvailableBalance | Check balance |
| 57 | Beneficiary not whitelisted | Dest not in whitelist | Add beneficiary first |
| 61 | Amount exceeds limit | Amount > TransferTransactionLimit | Reduce amount or request approval |
| 65 | Daily limit exceeded | Today's transfers + Amount > DailyLimit | Wait until next day |
| 91 | System error | Database/network failure | Retry or contact support |
---
## Narration Templates
### Customer Narration (Source)
```text
Transfer to {BeneficiaryName} - {Amount:N2}
Ref: {TransactionReference}
```text
**Example**: `Transfer to Jane Doe - 50,000.00 Ref: TRF123456`
### Customer Narration (Destination)
```text
Transfer from {SenderName} - {Amount:N2}
Ref: {TransactionReference}
```text
**Example**: `Transfer from John Doe - 50,000.00 Ref: TRF123456`
### Channel Narration
```text
{SenderName} Ć¢ā ā {BeneficiaryName} - {Amount:N2} - {ChannelType}
```text
**Example**: `John Doe Ć¢ā ā Jane Doe - 50,000.00 - ONLINE_BANKING`
---
## Concurrent Safety
### Scenario: Simultaneous Transfers
**Setup**:
- Account A Balance: NGN 100,000
- Transaction 1: Transfer A Ć¢ā ā B (NGN 60,000)
- Transaction 2: Transfer A Ć¢ā ā C (NGN 60,000)
- Both requests arrive simultaneously
**With Proper Locking** (SAFE):
:::warning[Code Removed]
**Implementation details removed for security.**
Contact support for implementation guidance.
:::text
---
## Best Practices
### For Users
1. **Verify Beneficiary Details**: Double-check account number and name
2. **Use References**: Add meaningful references for tracking
3. **Check Limits**: Know your transfer limits to avoid delays
4. **Whitelist Beneficiaries**: Pre-register frequent beneficiaries for faster transfers
5. **Monitor Pending Credits**: Check PendingCredits if transfer seems delayed
### For Developers
1. **Always Lock in Order**: Use ascending AccountId order to prevent deadlocks
2. **Use Transactions**: Wrap entire transfer in database transaction
3. **Track All Impacts**: Record changes to both source and dest in TransactionImpactRecord
4. **Handle Rejections**: Properly reverse holds and pending credits on rejection
5. **Test Concurrency**: Simulate simultaneous transfers in testing
6. **Validate Both Accounts**: Check both source and dest before proceeding
### For Operations
1. **Monitor Large Transfers**: Flag transfers > threshold for review
2. **Review Pending Transfers**: Investigate transfers stuck in PENDING > 1 hour
3. **Reconcile Daily**: Ensure total debits = total credits
4. **Audit Limits**: Review and adjust transfer limits quarterly
5. **Track Failures**: Monitor transfer failure rates by channel and reason
---
## Reversal Process
### Full Reversal Steps
```typescript
// Original Transfer
{
transactionId: "TRF123456",
sourceAccountId: 100,
destAccountId: 200,
amount: 50000.00,
feeAmount: 100.00,
state: "COMPLETED",
impacts: [
{ entity: "DepositAccount", entityId: 100, field: "BookBalance", delta: -50100.00 },
{ entity: "DepositAccount", entityId: 200, field: "BookBalance", delta: +50000.00 }
]
}
// Reversal Transaction
{
transactionId: "REV123456",
originalTransactionId: "TRF123456",
sourceAccountId: 200, // Swapped: now source is original dest
destAccountId: 100, // Swapped: now dest is original source
amount: 50000.00,
feeAmount: 0.00, // No fee on reversal
state: "COMPLETED",
impacts: [
{ entity: "DepositAccount", entityId: 100, field: "BookBalance", delta: +50100.00 }, // Opposite
{ entity: "DepositAccount", entityId: 200, field: "BookBalance", delta: -50000.00 } // Opposite
]
}
// Update Original Transaction
originalTransaction.state = "REVERSED";
originalTransaction.reversedDate = DateTime.UtcNow;
originalTransaction.reversalTransactionId = "REV123456";
```text
---
## V2 API Commands
BankLingo V2 provides the **InitiateTransferCommand** for BPMCore integration.
### Command Overview
- **Command**: `InitiateTransferCommand`
- **Implementation**: `CB.Administration.Api/Commands/BPMCore/DepositWithdrawal/AdministrationCoreTransferCommandHandlers.cs`
- **Purpose**: Initiate fund transfers with dual-account tracking and deadlock prevention
- **BPM Integration**: Accepts parameters via `BpmUtil.GetPropertyValue()`
- **State Management**: PENDING Ć¢ā ā APPROVED Ć¢ā ā SETTLED
### Key Features
**Dual-Account Impact Tracking**:
- Source account: Hold placed on available balance (PENDING)
- Destination account: Pending credit tracked separately
- Both accounts tracked via `TransactionImpactRecord`
- Delta-based tracking for each account independently
**Deadlock Prevention**:
- Accounts locked in deterministic order (by EncodedKey sort)
- Prevents circular wait conditions
- Enables safe concurrent transfers between same accounts
- Example: Transfer AĆ¢ā āB and BĆ¢ā āA can execute concurrently without deadlock
**Hold and Pending Credit Management**:
**PENDING State**:
```text
Source Account:
BookBalance: Unchanged
AvailableBalance: Decreased (hold placed)
Destination Account:
BookBalance: Unchanged
PendingCredit: Increased (not yet available)
SETTLED State:
Source Account:
BookBalance: Decreased
AvailableBalance: Already reduced (hold removed)
Destination Account:
BookBalance: Increased
AvailableBalance: Increased (pending credit finalized)
Approval Workflow Integration:
- Large transfers require approval with holds in place
- Rejection releases source hold and destination pending credit
- Approval proceeds to settlement with dual balance updates
- TransactionImpactTracker records all state changes
Concurrent Safety:
- Optimistic locking with version checking on both accounts
- Deterministic lock ordering prevents deadlocks
- Delta-based balance updates (not absolute values)
- Atomic dual-account updates
Fee Handling:
- Transfer fees deducted from source account
- Fee amount tracked separately in impact records
- GL posting includes fee income account
- Fee waived for own-account transfers
Reversal Support:
- Reversal swaps source/destination accounts
- Delta-based restoration ensures accuracy
- All original impacts reversed with opposite deltas
- Maintains audit trail with original + reversal records
Implementation Checklistā
Database Requirementsā
- Add
PendingCreditscolumn to DepositAccount - Add
HoldAmountcolumn to DepositAccount - Add
Versioncolumn to DepositAccount for optimistic locking - Create
TransferTransactiontable - Create
TransactionImpactRecordtable - Create
ImpactedEntitytable - Add index on
TransferTransaction(SourceAccountId, DestAccountId) - Add index on
ImpactedEntity(EntityType, EntityId)
Code Requirementsā
- Implement
ProcessTransferAsyncwith deadlock prevention - Add account locking in consistent order (ascending by Id)
- Implement hold placement on source account
- Implement pending credit on destination account
- Track all entity impacts in TransactionImpactRecord
- Implement approval workflow with hold management
- Add hold/credit release on rejection
- Implement reversal handler with swapped accounts
- Add fee calculation by transfer type
- Implement GL posting logic for transfers
Testing Requirementsā
- Unit test: Deadlock prevention with simultaneous transfers
- Unit test: Hold placement and release
- Unit test: Pending credit placement and finalization
- Integration test: Intra-bank transfer end-to-end
- Integration test: Inter-bank transfer with settlement
- Integration test: Own account transfer (zero fee)
- Integration test: Transfer + reversal = original balances
- Load test: 100 concurrent transfers
- UI test: Online banking transfer workflow
Documentation Requirementsā
- Update API documentation with transfer endpoints
- Create user guide on beneficiary whitelisting
- Document error codes and resolution steps
- Create runbook for stuck pending transfers
- Document GL account mapping for different transfer types
Developer Resourcesā
For API implementation details, see: