Initiate Transfer
Transfer funds between deposit accounts with comprehensive validation, limit checks, and dual-account balance updates.
Overview
The InitiateTransferCommand enables secure fund transfers between two deposit accounts within the banking system. This operation performs coordinated validation and balance updates across both source (debit) and destination (credit) accounts, ensuring data consistency, currency matching, and compliance with account tier limits.
Key Capabilities
- Internal Account Transfer: Move funds between any two deposit accounts
- Dual Account Validation: Validates both source and destination accounts
- Currency Matching: Ensures both accounts use the same currency
- Overdraft Support: Allows transfers with approved overdraft facilities
- Tier Limit Enforcement: Validates withdrawal limits on source account
- Account State Checks: Prevents transfers to/from locked, frozen, or closed accounts
- Blacklist Validation: Blocks transfers for blacklisted customers
- Transaction Tracking: Generates unique transaction keys for audit trails
- Narration Templates: Dynamic customer narrations for both accounts
API Endpoint
POST /api/bpm/cmd
Headers
Content-Type: application/json
Authorization: Bearer {access_token}
X-Tenant-ID: {tenant_id}
Request Structure
Command Payload
{
"commandName": "InitiateTransferCommand",
"data": {
"sourceAccount": "string",
"destinationAccount": "string",
"amount": "decimal",
"channelCode": "string",
"notes": "string",
"serviceId": "string",
"serviceDescription": "string"
}
}
Request Fields
| Field | Type | Required | Description | Validation |
|---|---|---|---|---|
sourceAccount | string | Yes | Source account number or encoded key | Must exist, cannot be closed/locked/frozen |
destinationAccount | string | Yes | Destination account number or encoded key | Must exist, cannot be closed |
amount | decimal | Yes | Transfer amount in account currency | Must be > 0, subject to tier limits |
channelCode | string | No | Transaction channel identifier | Optional channel tracking |
notes | string | No | Transfer description/remarks | Free-text notes |
serviceId | string | No | Service identifier for categorization | Optional service tracking |
serviceDescription | string | No | Service description | Human-readable service info |
Response Structure
Success Response
{
"isSuccessful": true,
"message": "Transfer has been effected successfully.",
"statusCode": "00",
"transactionId": "8A3F2D1E9B5C4F7A6E8D2C1B3A9F5E7D"
}
Error Response
{
"isSuccessful": false,
"message": "The source account does not have sufficient balance.",
"statusCode": "INSUFFICIENT_BALANCE"
}
Response Fields
| Field | Type | Description |
|---|---|---|
isSuccessful | boolean | Indicates if transfer was successful |
message | string | Descriptive result message |
statusCode | string | Response code (00 = success, error codes vary) |
transactionId | string | Unique transaction identifier (on success) |
Sample Requests
1. Basic Internal Transfer
{
"commandName": "InitiateTransferCommand",
"data": {
"sourceAccount": "ACC001234567",
"destinationAccount": "ACC007654321",
"amount": 5000.00,
"notes": "Monthly rent payment"
}
}
Use Case: Simple account-to-account transfer for personal payments.
2. Bill Payment Transfer
{
"commandName": "InitiateTransferCommand",
"data": {
"sourceAccount": "8A3F2D1E9B5C4F7A6E8D2C1B3A9F5E7D",
"destinationAccount": "9B4E3C2F8A6D5E7C9B2A1F3E5D8C7A6B",
"amount": 15000.00,
"channelCode": "MOBILE",
"notes": "Utility bill payment - Electricity",
"serviceId": "BILL_PAY_ELEC",
"serviceDescription": "Electricity Bill Payment"
}
}
Use Case: Automated bill payment from customer account to utility company account.
3. Loan Repayment Transfer
{
"commandName": "InitiateTransferCommand",
"data": {
"sourceAccount": "SAV123456789",
"destinationAccount": "LOAN987654321",
"amount": 25000.00,
"channelCode": "BRANCH",
"notes": "Monthly loan installment - October 2024",
"serviceId": "LOAN_REPAY",
"serviceDescription": "Loan Repayment"
}
}
Use Case: Transferring funds from savings account to loan repayment account.
4. Cross-Branch Transfer
{
"commandName": "InitiateTransferCommand",
"data": {
"sourceAccount": "ACC001111111",
"destinationAccount": "ACC002222222",
"amount": 100000.00,
"channelCode": "BRANCH",
"notes": "Business transfer to Lagos branch",
"serviceId": "INTER_BRANCH",
"serviceDescription": "Inter-Branch Transfer"
}
}
Use Case: Large business transfer between branches requiring additional documentation.
Business Logic
Processing Workflow
The transfer operation follows this sequence:
Validation Steps
- Field Extraction: Extract source account, destination account, amount, and optional fields from request data
- Source Account Validation:
- Load source account with client, product, currency relationships
- Verify account exists (by encoded key or account number)
- Check account is not null
- Destination Account Validation:
- Load destination account with same relationships
- Verify account exists
- Check account is not null
- Same Account Check: Prevent transfers where source and destination are identical
- Destination State Check: Block transfers if destination account is Closed or Closed_Written_Off
- Currency Match Validation: Ensure both accounts use the same currency (e.g., USD, NGN, LRD)
- Source Account State Check: Block if source account is on freeze or locked state
- Overdraft Calculation:
- Check if source account has active overdraft facility
- Verify overdraft expiry date > current date
- Calculate allowable overdraft amount
- Blacklist Check: Verify source account client is not blacklisted
- Product Configuration Validation: Load and validate source account product configuration
- Tier Limit Validation:
- Get source account tier limits from product configuration
- Validate withdrawal transaction limit (single transaction)
- Query transaction breakdown for daily/monthly aggregates
- Validate daily withdrawal limit
- Validate monthly withdrawal limit
- Validate daily transaction count limit
- Validate monthly transaction count limit
- Balance Sufficiency Check: Verify source account balance ≥ transfer amount - allowable overdraft
- Transaction Key Generation: Generate unique GUID-based transaction key
- Narration Template Execution:
- Execute source account narration template with transaction context
- Execute destination account narration template with transaction context
- Execute transaction ID template
- Success Response: Return transaction details with generated transaction ID
Balance Impact
Source Account (Debit):
New Balance = Current Balance - Transfer Amount
Destination Account (Credit):
New Balance = Current Balance + Transfer Amount
With Overdraft:
Minimum Required Balance = Transfer Amount - Allowable Overdraft
Limit Enforcement Logic
Withdrawal Transaction Limit:
if (tierLimit.WithdrawalTransactionLimit < transferAmount)
{
return Error: "Maximum transaction withdrawal limit exceeded"
}
Daily Withdrawal Limit:
if (tierLimit.MaxDailyWithdrawal < transactionBreakdown.TotalOutflowToday + transferAmount)
{
return Error: "Exceeded daily withdrawal limit"
}
Monthly Withdrawal Limit:
if (tierLimit.maxMonthlyWithdrawal < transactionBreakdown.TotalMonthlyOutflow + transferAmount)
{
return Error: "Exceeded monthly withdrawal limit"
}
Transaction Count Limits:
if (tierLimit.maxTransactionCountPerDay < transactionBreakdown.TransactionCountToday)
{
return Error: "Exceeded daily transaction count"
}
if (tierLimit.maxTransactionCountPerMonth < transactionBreakdown.TotalTransactionCount)
{
return Error: "Exceeded monthly transaction count"
}
Error Responses
6 Error Scenarios
| Error Code | Message | Scenario | Resolution |
|---|---|---|---|
N/A | "The source deposit account is not valid." | Source account doesn't exist | Verify account number/key is correct |
N/A | "Invalid destination account details" | Destination account doesn't exist | Verify destination account number/key |
N/A | "Transaction not permitted. Source account and destination account are the same" | Same account used for both source and destination | Provide different accounts |
DEPOSIT_CLOSED | "You cannot perform any transaction on the account. The account is closed." | Destination account is closed or written off | Use an active destination account |
CURRENCY_MISMATCH | "The currency mismatch between source account {currency} and destination account ({currency})." | Accounts use different currencies | Ensure both accounts use same currency |
Transaction_not_permitted_to_sender | "Transaction not permitted on account as it is either locked or on freeze." | Source account is frozen or locked | Unlock/unfreeze account before transfer |
N/A | "Transaction cannot be performed on any of the customer's account presently. Please contact the administrator" | Customer is blacklisted | Contact administrator to review customer status |
Transfer_limit_exceeded | "The maximum transaction withdrawal limit on the account tier is {limit} {currency}." | Single transaction amount exceeds tier limit | Reduce transfer amount or upgrade account tier |
Invalid_Amount | "Exceeded the transaction limit for the day. The maximum amount allowed for withdrawal for the day is {limit} {currency}." | Daily withdrawal limit exceeded | Wait until next day or request limit increase |
Invalid_Amount | "Exceeded the transaction limit for the month. The maximum amount allowed for withdrawal for the month is {limit} {currency}." | Monthly withdrawal limit exceeded | Wait until next month or request limit increase |
Invalid_Amount | "Exceeded the transaction limit for the day. The maximum number of transactions allowed for the day is {count}." | Daily transaction count exceeded | Wait until next day |
Invalid_Amount | "Exceeded the transaction limit for the month. The maximum number of transactions allowed for the month is {count}." | Monthly transaction count exceeded | Wait until next month |
N/A | "The source account does not have sufficient balance." | Insufficient balance (even with overdraft) | Deposit funds or check overdraft availability |
Architecture
Component Interaction
Key Components
- InitiateTransferCommand: Command object with request data
- MediatR Handler: Processes command and orchestrates transfer logic
- DepositAccount Repository: Manages account data persistence
- DepositProductConfigHelper: Validates and retrieves product configuration
- GetDepositAccountTransactionBreakdownQuery: Queries daily/monthly transaction totals
- BankLingoCommandEngineExecutor: Executes narration and ID templates
- Transaction Context: Provides dynamic data for template execution
Security Considerations
Authentication & Authorization
- Bearer Token Required: All requests must include valid JWT token
- User Context: Transaction records include user encoded key and username
- Tenant Isolation: Multi-tenant environment requires X-Tenant-ID header
- Account Access: Verify user has permission to access both accounts
Data Protection
- Encrypted Communication: HTTPS/TLS required for all API calls
- PII Protection: Customer narrations should not expose sensitive data
- Audit Trail: All transfers logged with transaction key, user, and timestamp
- Blacklist Enforcement: Automatic blocking of blacklisted customer transactions
Performance Considerations
Optimization Strategies
- Eager Loading: Load related entities (Client, DepositProduct, Currency) in single query
- Index Usage: Queries use indexed fields (EncodedKey, AccountNumber)
- Transaction Breakdown Caching: Consider caching transaction aggregates for high-frequency accounts
- Batch Processing: For multiple transfers, consider bulk command processing
- Async Operations: Handler uses async/await throughout for non-blocking execution
Expected Performance
- Single Transfer: < 500ms under normal load
- With Limit Validation: +100ms for transaction breakdown query
- High Volume: Consider rate limiting to prevent system overload
- Database Impact: Dual account updates require careful transaction management
Troubleshooting
Common Issues
1. Currency Mismatch Error
Symptom: "The currency mismatch between source account USD and destination account (NGN)."
Diagnosis:
- Check source account currency
- Check destination account currency
- Verify both are compatible
Solution: Only transfer between accounts with matching currencies. Use exchange services for cross-currency transfers.
2. Insufficient Balance Despite Visible Balance
Symptom: "The source account does not have sufficient balance" even when balance appears sufficient.
Diagnosis:
- Check if account has held amounts (locks, pending transactions)
- Verify overdraft facility is active and not expired
- Calculate: Available Balance = Account Balance + Allowable Overdraft
Solution:
- Release unnecessary amount locks
- Activate/renew overdraft facility
- Wait for pending transactions to settle
3. Tier Limit Exceeded
Symptom: "The maximum transaction withdrawal limit on the account tier is 50000.00 USD."
Diagnosis:
- Check account tier configuration
- Verify WithdrawalTransactionLimit in tier settings
- Confirm amount is within single transaction limit
Solution:
- Reduce transfer amount to within limit
- Split into multiple smaller transfers
- Request account tier upgrade from administrator
4. Daily/Monthly Limit Exceeded
Symptom: "Exceeded the transaction limit for the day. The maximum amount allowed for withdrawal for the day is 100000.00 USD."
Diagnosis:
- Query GetDepositAccountTransactionBreakdownQuery for transaction history
- Check TotalOutflowToday or TotalMonthlyOutflow
- Verify MaxDailyWithdrawal or maxMonthlyWithdrawal tier setting
Solution:
- Wait until next day/month for limit reset
- Request temporary limit increase from administrator
- Prioritize critical transfers within available limit
5. Account Locked/Frozen
Symptom: "Transaction not permitted on account as it is either locked or on freeze."
Diagnosis:
- Check source account DepositState (is it Locked?)
- Check source account IsOnFreeze flag
- Review account restrictions in admin panel
Solution:
- Use UnlockDepositAccountCommand if locked
- Remove freeze restriction if applicable
- Contact administrator to review account status
6. Same Account Transfer Attempt
Symptom: "Transaction not permitted. Source account and destination account are the same"
Diagnosis:
- Verify source account number/encoded key
- Verify destination account number/encoded key
- Check if both resolve to same account ID
Solution: Provide different source and destination accounts. Transfers to the same account are not permitted.
Monitoring & Alerts
Key Metrics
- Transfer Success Rate: Percentage of successful transfers vs. failed attempts
- Average Transfer Amount: Monitor for unusual patterns
- Limit Exceeded Rate: Frequency of tier limit rejections
- Currency Mismatch Rate: Track configuration issues
- Response Time: P50, P95, P99 latency metrics
- Error Distribution: Count by error type for root cause analysis
Alert Thresholds
- High Failure Rate: > 5% failures in 5-minute window
- Large Transfer: Single transfer > $100,000 USD equivalent
- Rapid Transfers: > 10 transfers from same source in 1 minute
- Blacklist Attempts: Any transfer attempt from blacklisted customer
- Insufficient Balance Rate: > 20% insufficient balance errors (may indicate limit misconfiguration)
Audit & Compliance
Audit Trail
Every transfer records:
- Transaction Key: Unique identifier (GUID-based)
- User Context: UserName, UserEncodedKey from JWT token
- Timestamp: DateCreated, TransactionBookDate, TransactionValueDate, TransactionEntryDate
- Account Details: Source and destination account numbers, client names
- Amount: Transfer amount and currency
- Channel: Transaction channel (MOBILE, BRANCH, ATM, etc.)
- Service: Service ID and description for categorization
- Narration: Customer-facing narration for both accounts
- Status: Transaction status (SETTLED, PENDING, REVERSED)
Compliance Requirements
- AML Monitoring: Flag transfers above threshold for review
- KYC Verification: Ensure both customers have completed KYC
- Regulatory Limits: Enforce daily/monthly limits per regulations
- Blacklist Enforcement: Automatic blocking as shown in validation
- Record Retention: Maintain transfer records per regulatory requirements
Code Examples
C# Implementation
using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
public class BankLingoTransferClient
{
private readonly HttpClient _httpClient;
private readonly string _baseUrl;
private readonly string _accessToken;
public BankLingoTransferClient(string baseUrl, string accessToken)
{
_baseUrl = baseUrl;
_accessToken = accessToken;
_httpClient = new HttpClient();
_httpClient.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", accessToken);
_httpClient.DefaultRequestHeaders.Add("X-Tenant-ID", "your-tenant-id");
}
public async Task<TransferResponse> InitiateTransferAsync(
string sourceAccount,
string destinationAccount,
decimal amount,
string notes = null,
string channelCode = null,
string serviceId = null,
string serviceDescription = null)
{
var request = new
{
commandName = "InitiateTransferCommand",
data = new
{
sourceAccount,
destinationAccount,
amount,
notes,
channelCode,
serviceId,
serviceDescription
}
};
var json = JsonSerializer.Serialize(request);
var content = new StringContent(json, Encoding.UTF8, "application/json");
var response = await _httpClient.PostAsync(
$"{_baseUrl}/api/bpm/cmd",
content);
var responseJson = await response.Content.ReadAsStringAsync();
return JsonSerializer.Deserialize<TransferResponse>(responseJson);
}
}
public class TransferResponse
{
public bool IsSuccessful { get; set; }
public string Message { get; set; }
public string StatusCode { get; set; }
public string TransactionId { get; set; }
}
// Usage
var client = new BankLingoTransferClient(
"https://api.banklingo.com",
"your-jwt-token");
var result = await client.InitiateTransferAsync(
sourceAccount: "ACC001234567",
destinationAccount: "ACC007654321",
amount: 5000.00m,
notes: "Monthly rent payment",
channelCode: "MOBILE"
);
if (result.IsSuccessful)
{
Console.WriteLine($"Transfer successful! Transaction ID: {result.TransactionId}");
}
else
{
Console.WriteLine($"Transfer failed: {result.Message}");
}
JavaScript Implementation
class BankLingoTransferClient {
constructor(baseUrl, accessToken, tenantId) {
this.baseUrl = baseUrl;
this.accessToken = accessToken;
this.tenantId = tenantId;
}
async initiateTransfer({
sourceAccount,
destinationAccount,
amount,
notes = null,
channelCode = null,
serviceId = null,
serviceDescription = null
}) {
const request = {
commandName: 'InitiateTransferCommand',
data: {
sourceAccount,
destinationAccount,
amount,
notes,
channelCode,
serviceId,
serviceDescription
}
};
const response = await fetch(`${this.baseUrl}/api/bpm/cmd`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${this.accessToken}`,
'X-Tenant-ID': this.tenantId
},
body: JSON.stringify(request)
});
return await response.json();
}
}
// Usage
const client = new BankLingoTransferClient(
'https://api.banklingo.com',
'your-jwt-token',
'your-tenant-id'
);
const result = await client.initiateTransfer({
sourceAccount: 'ACC001234567',
destinationAccount: 'ACC007654321',
amount: 5000.00,
notes: 'Monthly rent payment',
channelCode: 'MOBILE'
});
if (result.isSuccessful) {
console.log(`Transfer successful! Transaction ID: ${result.transactionId}`);
} else {
console.error(`Transfer failed: ${result.message}`);
}
Python Implementation
import requests
from typing import Optional
from dataclasses import dataclass
@dataclass
class TransferResponse:
is_successful: bool
message: str
status_code: str
transaction_id: Optional[str] = None
class BankLingoTransferClient:
def __init__(self, base_url: str, access_token: str, tenant_id: str):
self.base_url = base_url
self.access_token = access_token
self.tenant_id = tenant_id
self.headers = {
'Content-Type': 'application/json',
'Authorization': f'Bearer {access_token}',
'X-Tenant-ID': tenant_id
}
def initiate_transfer(
self,
source_account: str,
destination_account: str,
amount: float,
notes: Optional[str] = None,
channel_code: Optional[str] = None,
service_id: Optional[str] = None,
service_description: Optional[str] = None
) -> TransferResponse:
request_data = {
'commandName': 'InitiateTransferCommand',
'data': {
'sourceAccount': source_account,
'destinationAccount': destination_account,
'amount': amount,
'notes': notes,
'channelCode': channel_code,
'serviceId': service_id,
'serviceDescription': service_description
}
}
response = requests.post(
f'{self.base_url}/api/bpm/cmd',
headers=self.headers,
json=request_data
)
result = response.json()
return TransferResponse(
is_successful=result.get('isSuccessful', False),
message=result.get('message', ''),
status_code=result.get('statusCode', ''),
transaction_id=result.get('transactionId')
)
# Usage
client = BankLingoTransferClient(
base_url='https://api.banklingo.com',
access_token='your-jwt-token',
tenant_id='your-tenant-id'
)
result = client.initiate_transfer(
source_account='ACC001234567',
destination_account='ACC007654321',
amount=5000.00,
notes='Monthly rent payment',
channel_code='MOBILE'
)
if result.is_successful:
print(f'Transfer successful! Transaction ID: {result.transaction_id}')
else:
print(f'Transfer failed: {result.message}')
Response Codes
| Code | Description | Action Required |
|---|---|---|
00 | Success | No action needed |
DEPOSIT_CLOSED | Destination account closed | Use active account |
CURRENCY_MISMATCH | Currency mismatch between accounts | Ensure same currency |
Transaction_not_permitted_to_sender | Source account locked/frozen | Unlock/unfreeze account |
Transfer_limit_exceeded | Tier limit exceeded | Reduce amount or upgrade tier |
Invalid_Amount | Daily/monthly limit exceeded | Wait or request increase |
INSUFFICIENT_BALANCE | Source account has insufficient balance | Deposit funds or activate overdraft |
Client_Not_Found | Source/destination account not found | Verify account numbers |
Business Rules
Validation Rules
- Source Account Required: Source account must exist and be valid (by encoded key or account number)
- Destination Account Required: Destination account must exist and be valid
- Amount Required: Transfer amount must be greater than zero
- Same Account Prevention: Source and destination accounts cannot be the same
- Currency Match Required: Both accounts must use identical currency codes
- Closed Account Block: Cannot transfer to closed or written-off destination accounts
- Frozen/Locked Block: Cannot transfer from frozen or locked source accounts
- Blacklist Block: Cannot transfer if source account customer is blacklisted
- Balance Sufficiency: Source account balance (including overdraft) must cover transfer amount
- Tier Limit Compliance: Transfer amount must be within account tier's withdrawal transaction limit
- Daily Limit Compliance: Total daily withdrawals + transfer amount must not exceed tier's daily limit
- Monthly Limit Compliance: Total monthly withdrawals + transfer amount must not exceed tier's monthly limit
- Daily Count Limit: Daily transaction count must not exceed tier's daily count limit
- Monthly Count Limit: Monthly transaction count must not exceed tier's monthly count limit
Operational Rules
- Overdraft Expiry Check: Overdraft facility only applies if expiry date > current date
- Transaction Key Generation: Each transfer receives unique GUID-based transaction key
- Dual Narration: Both source and destination accounts receive customized narrations based on product templates
- Transaction Context: Narration templates have access to full transaction context (accounts, amount, transaction key)
- Transaction ID Template: Transaction ID can be customized via product configuration template
- User Context Recording: All transfers record initiating user's name and encoded key
- Timestamp Recording: Multiple timestamps captured (DateCreated, BookDate, ValueDate, EntryDate)
- Channel Tracking: Optional channel code allows categorization of transfer source (MOBILE, BRANCH, ATM, etc.)
- Service Categorization: Optional service ID and description enable transfer categorization for reporting
- Eager Loading: Related entities (Client, DepositProduct, Currency) loaded in single query for performance
- Transaction Breakdown Query: Daily and monthly aggregate amounts and counts queried before limit validation
- State Transition: Destination account may transition from Approved to Active state upon receiving transfer
- Settlement State: Transfers are created in SETTLED state (immediate balance impact on both accounts)
- Error Code Mapping: Standard NIBSS response codes mapped for interoperability
- Logging: Transfer initiation logged with amount, source, destination, and notes for audit trail
Related Operations
- InitiateDepositCommand: For crediting accounts without source debit
- InitiateWithdrawalCommand: For debiting accounts without destination credit
- GetDepositAccountTransactionBreakdownQuery: Query transaction aggregates for limit validation
- LockDepositAccountCommand: Lock account to prevent all transactions including transfers
- UnlockDepositAccountCommand: Unlock account to restore transfer capability
- ActivatePNDOnAccountCommand: Activate Post No Debit to block withdrawals/transfers from source account
Changelog
| Version | Date | Changes |
|---|---|---|
| 1.0.0 | 2024-01-15 | Initial implementation with dual account validation |
| 1.1.0 | 2024-03-20 | Added currency mismatch validation |
| 1.2.0 | 2024-06-10 | Implemented tier limit enforcement |
| 1.3.0 | 2024-08-15 | Added overdraft facility support |
| 1.4.0 | 2024-10-01 | Enhanced narration templates with dynamic context |
Support
For technical assistance with the InitiateTransferCommand:
- Documentation: https://docs.banklingo.com/api/deposit-transactions/initiate-transfer
- API Support: api-support@banklingo.com
- Developer Forum: https://community.banklingo.com/api-questions