Skip to main content

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

FieldTypeRequiredDescriptionValidation
sourceAccountstringYesSource account number or encoded keyMust exist, cannot be closed/locked/frozen
destinationAccountstringYesDestination account number or encoded keyMust exist, cannot be closed
amountdecimalYesTransfer amount in account currencyMust be > 0, subject to tier limits
channelCodestringNoTransaction channel identifierOptional channel tracking
notesstringNoTransfer description/remarksFree-text notes
serviceIdstringNoService identifier for categorizationOptional service tracking
serviceDescriptionstringNoService descriptionHuman-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

FieldTypeDescription
isSuccessfulbooleanIndicates if transfer was successful
messagestringDescriptive result message
statusCodestringResponse code (00 = success, error codes vary)
transactionIdstringUnique 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

  1. Field Extraction: Extract source account, destination account, amount, and optional fields from request data
  2. 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
  3. Destination Account Validation:
    • Load destination account with same relationships
    • Verify account exists
    • Check account is not null
  4. Same Account Check: Prevent transfers where source and destination are identical
  5. Destination State Check: Block transfers if destination account is Closed or Closed_Written_Off
  6. Currency Match Validation: Ensure both accounts use the same currency (e.g., USD, NGN, LRD)
  7. Source Account State Check: Block if source account is on freeze or locked state
  8. Overdraft Calculation:
    • Check if source account has active overdraft facility
    • Verify overdraft expiry date > current date
    • Calculate allowable overdraft amount
  9. Blacklist Check: Verify source account client is not blacklisted
  10. Product Configuration Validation: Load and validate source account product configuration
  11. 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
  12. Balance Sufficiency Check: Verify source account balance ≥ transfer amount - allowable overdraft
  13. Transaction Key Generation: Generate unique GUID-based transaction key
  14. Narration Template Execution:
    • Execute source account narration template with transaction context
    • Execute destination account narration template with transaction context
    • Execute transaction ID template
  15. 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 CodeMessageScenarioResolution
N/A"The source deposit account is not valid."Source account doesn't existVerify account number/key is correct
N/A"Invalid destination account details"Destination account doesn't existVerify destination account number/key
N/A"Transaction not permitted. Source account and destination account are the same"Same account used for both source and destinationProvide different accounts
DEPOSIT_CLOSED"You cannot perform any transaction on the account. The account is closed."Destination account is closed or written offUse an active destination account
CURRENCY_MISMATCH"The currency mismatch between source account {currency} and destination account ({currency})."Accounts use different currenciesEnsure 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 lockedUnlock/unfreeze account before transfer
N/A"Transaction cannot be performed on any of the customer's account presently. Please contact the administrator"Customer is blacklistedContact 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 limitReduce 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 exceededWait 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 exceededWait 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 exceededWait 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 exceededWait 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

  1. Eager Loading: Load related entities (Client, DepositProduct, Currency) in single query
  2. Index Usage: Queries use indexed fields (EncodedKey, AccountNumber)
  3. Transaction Breakdown Caching: Consider caching transaction aggregates for high-frequency accounts
  4. Batch Processing: For multiple transfers, consider bulk command processing
  5. 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

CodeDescriptionAction Required
00SuccessNo action needed
DEPOSIT_CLOSEDDestination account closedUse active account
CURRENCY_MISMATCHCurrency mismatch between accountsEnsure same currency
Transaction_not_permitted_to_senderSource account locked/frozenUnlock/unfreeze account
Transfer_limit_exceededTier limit exceededReduce amount or upgrade tier
Invalid_AmountDaily/monthly limit exceededWait or request increase
INSUFFICIENT_BALANCESource account has insufficient balanceDeposit funds or activate overdraft
Client_Not_FoundSource/destination account not foundVerify account numbers

Business Rules

Validation Rules

  1. Source Account Required: Source account must exist and be valid (by encoded key or account number)
  2. Destination Account Required: Destination account must exist and be valid
  3. Amount Required: Transfer amount must be greater than zero
  4. Same Account Prevention: Source and destination accounts cannot be the same
  5. Currency Match Required: Both accounts must use identical currency codes
  6. Closed Account Block: Cannot transfer to closed or written-off destination accounts
  7. Frozen/Locked Block: Cannot transfer from frozen or locked source accounts
  8. Blacklist Block: Cannot transfer if source account customer is blacklisted
  9. Balance Sufficiency: Source account balance (including overdraft) must cover transfer amount
  10. Tier Limit Compliance: Transfer amount must be within account tier's withdrawal transaction limit
  11. Daily Limit Compliance: Total daily withdrawals + transfer amount must not exceed tier's daily limit
  12. Monthly Limit Compliance: Total monthly withdrawals + transfer amount must not exceed tier's monthly limit
  13. Daily Count Limit: Daily transaction count must not exceed tier's daily count limit
  14. Monthly Count Limit: Monthly transaction count must not exceed tier's monthly count limit

Operational Rules

  1. Overdraft Expiry Check: Overdraft facility only applies if expiry date > current date
  2. Transaction Key Generation: Each transfer receives unique GUID-based transaction key
  3. Dual Narration: Both source and destination accounts receive customized narrations based on product templates
  4. Transaction Context: Narration templates have access to full transaction context (accounts, amount, transaction key)
  5. Transaction ID Template: Transaction ID can be customized via product configuration template
  6. User Context Recording: All transfers record initiating user's name and encoded key
  7. Timestamp Recording: Multiple timestamps captured (DateCreated, BookDate, ValueDate, EntryDate)
  8. Channel Tracking: Optional channel code allows categorization of transfer source (MOBILE, BRANCH, ATM, etc.)
  9. Service Categorization: Optional service ID and description enable transfer categorization for reporting
  10. Eager Loading: Related entities (Client, DepositProduct, Currency) loaded in single query for performance
  11. Transaction Breakdown Query: Daily and monthly aggregate amounts and counts queried before limit validation
  12. State Transition: Destination account may transition from Approved to Active state upon receiving transfer
  13. Settlement State: Transfers are created in SETTLED state (immediate balance impact on both accounts)
  14. Error Code Mapping: Standard NIBSS response codes mapped for interoperability
  15. Logging: Transfer initiation logged with amount, source, destination, and notes for audit trail

  • 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

VersionDateChanges
1.0.02024-01-15Initial implementation with dual account validation
1.1.02024-03-20Added currency mismatch validation
1.2.02024-06-10Implemented tier limit enforcement
1.3.02024-08-15Added overdraft facility support
1.4.02024-10-01Enhanced narration templates with dynamic context

Support

For technical assistance with the InitiateTransferCommand: