Skip to main content

Teller Withdrawal Transaction

Customer withdraws funds via teller counter (Cash or Cheque).

Overview

Teller withdrawals allow customers to debit funds from their deposit accounts through cash dispensed at the teller counter or via cheque issuance. The transaction decreases both the customer's account balance and the teller till balance (for cash withdrawals).

Key Features:

  • Supports both Cash and Cheque withdrawals
  • Till cash deduction tracking
  • Hold management for concurrent safety
  • Account balance validation
  • Approval workflow for large amounts
  • Concurrent transaction safety with delta-based impact tracking

Transaction Flow

Cash Withdrawal Flow

Cheque Withdrawal Flow

Entities Impacted

Cash Withdrawal Scenario

Command: WithdrawFromTellerTillCommand (BPMCore)

ImpactedEntities:
[
// Phase 1: Hold Placement
{
entityType: "DepositAccount",
entityId: 12345,
fieldName: "AvailableBalance",
oldValue: 100000.00,
newValue: 95000.00, // Reduced by hold
deltaAmount: -5000.00
},
{
entityType: "DepositAccount",
entityId: 12345,
fieldName: "HoldAmount",
oldValue: 0.00,
newValue: 5000.00, // Hold placed
deltaAmount: +5000.00
},

// Phase 2: Execution (Hold cleared, BookBalance debited)
{
entityType: "DepositAccount",
entityId: 12345,
fieldName: "BookBalance",
oldValue: 100000.00,
newValue: 95000.00,
deltaAmount: -5000.00
},
{
entityType: "DepositAccount",
entityId: 12345,
fieldName: "HoldAmount",
oldValue: 5000.00,
newValue: 0.00, // Hold cleared
deltaAmount: -5000.00
},
{
entityType: "TellerTill",
entityId: 789,
fieldName: "CashBalance",
oldValue: 50000.00,
newValue: 45000.00,
deltaAmount: -5000.00
},
{
entityType: "TellerTill",
entityId: 789,
fieldName: "TransactionCount",
oldValue: 42,
newValue: 43,
deltaAmount: +1
},
{
entityType: "GLAccount",
entityKey: "1050-CASH-IN-TILL",
fieldName: "DebitAmount",
deltaAmount: +5000.00
},
{
entityType: "GLAccount",
entityKey: "1001-CUSTOMER-DEPOSITS",
fieldName: "CreditAmount",
deltaAmount: +5000.00
}
]
```text
### Cheque Withdrawal Scenario

**Command**: `InitiateWithdrawalCommand` (with `cashOrCheque=Cheque`)

```typescript
ImpactedEntities:
[
{
entityType: "DepositAccount",
entityId: 12345,
fieldName: "AvailableBalance",
oldValue: 100000.00,
newValue: 90000.00,
deltaAmount: -10000.00
},
{
entityType: "DepositAccount",
entityId: 12345,
fieldName: "BookBalance",
oldValue: 100000.00,
newValue: 90000.00,
deltaAmount: -10000.00
},
{
entityType: "ChequeClearingTransaction",
entityId: 888,
fieldName: "State",
oldValue: null,
newValue: "ISSUED",
deltaAmount: 0
},
{
entityType: "ChequeClearingTransaction",
entityId: 888,
fieldName: "Amount",
oldValue: 0,
newValue: 10000.00,
deltaAmount: +10000.00
},
{
entityType: "GLAccount",
entityKey: "1001-CUSTOMER-DEPOSITS",
fieldName: "DebitAmount",
deltaAmount: +10000.00
},
{
entityType: "GLAccount",
entityKey: "1300-CHEQUE-ISSUANCE",
fieldName: "CreditAmount",
deltaAmount: +10000.00
}
]
```text
---

## Implementation

### Cash Withdrawal Implementation

**BPMCore Command**: `WithdrawFromTellerTillCommand`

:::warning[Code Removed]
**Implementation details removed for security.**

Contact support for implementation guidance.
:::text
### Cheque Withdrawal Implementation

**Command**: `InitiateWithdrawalCommand` (with `cashOrCheque=Cheque`)

:::warning[Code Removed]
**Implementation details removed for security.**

Contact support for implementation guidance.
:::text
---

## Validation Rules

### Till Validation

| Rule | Check | Error Message |
|------|-------|---------------|
| **Till Exists** | Till found by `TillId` or `EncodedKey` | "Till not found" |
| **Till State** | `TillAccountState == OPENED` | "Till {TillId} is not opened" |
| **Till Type** | `TillType == TellerTill` | "Invalid till type" |
| **Sufficient Cash** | `Balance >= Amount` | "Insufficient cash in till. Available: {balance}, Required: {amount}" |
| **Currency Match** | Till currency == Account currency | "Currency mismatch" |
| **Branch Open** | Branch is open for today | "Branch is closed" |

### Account Validation

| Rule | Check | Error Message |
|------|-------|---------------|
| **Account Exists** | Account found by `AccountNumber` or `EncodedKey` | "Account not found" |
| **Account Active** | `DepositState == ACTIVE` | "Account is not active" |
| **Not Locked** | `DepositState != LOCKED` | "Account is locked" |
| **Not Closed** | `DepositState != CLOSED` | "Account is closed" |
| **Sufficient Funds** | `AvailableBalance >= Amount` | "Insufficient funds. Available: {balance}, Required: {amount}" |
| **Amount Valid** | `Amount > 0` | "Amount must be greater than zero" |

### Cheque Validation (if applicable)

| Rule | Check | Error Message |
|------|-------|---------------|
| **Cheque Number** | Cheque number provided when `CashOrCheque == Cheque` | "Cheque number is required" |
| **Cheque Format** | Valid cheque number format | "Invalid cheque number format" |
| **Duplicate Check** | Cheque number not already issued | "Duplicate cheque number" |

---

## GL Account Postings

### Cash Withdrawal

| Account | Debit (Dr) | Credit (Cr) | Description |
|---------|------------|-------------|-------------|
| **Cash-in-Till Asset** | Amount | - | Decrease till cash balance |
| **Customer Deposits Liability** | - | Amount | Decrease customer liability |

**Example** (₦5,000 withdrawal):

```text
DR: 1050-Cash-in-Till ₦5,000
CR: 2001-Customer-Deposits-Liability ₦5,000
```text
### Cheque Withdrawal

| Account | Debit (Dr) | Credit (Cr) | Description |
|---------|------------|-------------|-------------|
| **Customer Deposits Liability** | Amount | - | Decrease customer liability |
| **Cheque Issuance Control** | - | Amount | Track issued cheques |

**Example** (₦10,000 cheque withdrawal):

```text
DR: 2001-Customer-Deposits-Liability ₦10,000
CR: 1300-Cheque-Issuance ₦10,000
```text
---

## Testing

### Test Scenario 1: Cash Withdrawal - Auto-Approve

**Setup**:
- Account: Active Savings Account (₦100,000 balance)
- Till: TELLER-01 (₦50,000 cash balance)
- Amount: ₦5,000 (below approval limit)

**Expected Results**:

```typescript
// Phase 1: Hold Placement
DepositAccount:
AvailableBalance: 100000 → 95000 ✓ (hold placed)
HoldAmount: 0 → 5000 ✓
BookBalance: 100000 (unchanged) ✓

// Phase 2: Execution
DepositAccount:
BookBalance: 100000 → 95000 ✓
HoldAmount: 5000 → 0 ✓ (hold cleared)
LastTransactionDate: Updated ✓

TellerTill:
Balance: 50000 → 45000 ✓
TransactionCount: +1 ✓

Transaction:
State: PENDING → APPROVED → COMPLETED ✓

GLEntries:
DR: Cash-in-Till: ₦5,000 ✓
CR: Customer-Deposits: ₦5,000 ✓
```text
### Test Scenario 2: Cheque Withdrawal

**Setup**:
- Account: Active Current Account (₦50,000 balance)
- Cheque Number: CHQ-654321
- Amount: ₦10,000

**Expected Results**:

```typescript
DepositAccount:
AvailableBalance: 50000 → 40000 ✓
BookBalance: 50000 → 40000 ✓
HoldAmount: 0 (NO HOLD for cheque) ✓

ChequeClearingTransaction:
State: ISSUED ✓
Amount: ₦10,000 ✓
ChequeNumber: CHQ-654321 ✓

Transaction:
CashOrCheque: CHEQUE ✓
State: COMPLETED ✓

GLEntries:
DR: Customer-Deposits: ₦10,000 ✓
CR: Cheque-Issuance: ₦10,000 ✓
```text
### Test Scenario 3: Insufficient Funds

**Setup**:
- Account Balance: ₦3,000
- Withdrawal Amount: ₦5,000

**Expected Results**:

```typescript
Validation:
Error: "Insufficient funds. Available: ₦3,000, Required: ₦5,000" ✓
Transaction: REJECTED ✓

Account Balance: 3000 (NO CHANGE) ✓
Till Balance: NO CHANGE ✓
```text
### Test Scenario 4: Insufficient Till Cash

**Setup**:
- Till Cash Balance: ₦2,000
- Withdrawal Amount: ₦5,000
- Account has sufficient funds

**Expected Results**:

```typescript
Validation:
Error: "Insufficient cash in till. Available: ₦2,000, Required: ₦5,000" ✓
Transaction: REJECTED ✓

Till Balance: 2000 (NO CHANGE) ✓
Account Balance: NO CHANGE ✓
```text
### Test Scenario 5: Approval Workflow

**Setup**:
- Amount: ₦500,000 (above approval limit of ₦100,000)
- Account Balance: ₦600,000

**Expected Results**:

```typescript
// Phase 1: Hold Placement
Transaction:
State: PENDING ✓
RequiresApproval: true ✓

DepositAccount:
AvailableBalance: 600000 → 100000 ✓ (hold placed)
HoldAmount: 0 → 500000 ✓
BookBalance: 600000 (unchanged until approval) ✓

// After Approval:
Transaction:
State: APPROVED → COMPLETED ✓

DepositAccount:
BookBalance: 600000 → 100000 ✓
HoldAmount: 500000 → 0 ✓ (hold cleared)
```text
---

## Hold Management

### Purpose of Holds

Holds prevent **overdraft in concurrent scenarios** by reserving funds before execution:

1. **Place Hold**: Reduce `AvailableBalance` immediately
2. **Execute Transaction**: Debit `BookBalance`, clear hold
3. **Reject Transaction**: Restore `AvailableBalance`, clear hold

### Concurrent Withdrawal Safety

**Scenario**: Two tellers process withdrawals simultaneously

**Initial State**:
- Account Balance: ₦10,000

**Transaction A**: Withdraw ₦6,000
**Transaction B**: Withdraw ₦7,000

**Without Holds** (PROBLEM):

```text
T1: A checks balance (₦10,000) → ✓ Sufficient
T2: B checks balance (₦10,000) → ✓ Sufficient
T3: A debits ₦6,000 → Balance: ₦4,000
T4: B debits ₦7,000 → Balance: -₦3,000 ❌ OVERDRAFT!
```text
**With Holds** (SOLUTION):

```text
T1: A places hold ₦6,000 → AvailableBalance: ₦4,000
T2: B checks AvailableBalance (₦4,000) → ❌ Insufficient (needs ₦7,000)
T3: B rejected → "Insufficient funds"
T4: A executes → BookBalance: ₦4,000 ✓
```text
---

## Concurrent Transaction Safety

### Scenario: Simultaneous Withdrawals

Two tellers withdraw from the same account:

**Teller A**: Withdraw ₦5,000
**Teller B**: Withdraw ₦3,000

**Initial State**:
- Account Balance: ₦100,000

**Delta-Based Tracking**:

```typescript
Transaction A Impact:
OldValue: 100000
NewValue: 95000
Delta: -5000

Transaction B Impact:
OldValue: 100000 // Same starting point
NewValue: 97000
Delta: -3000

Final Balance: 100000 - 5000 - 3000 = 92000 ✓

Both deltas are preserved, ensuring accurate final balance even with concurrent execution.


V2 API Commands

BankLingo V2 provides BPMCore-compatible commands for teller withdrawal transactions.

Architecture Overview

The V2 teller withdrawal uses the core withdrawal command with till tracking:

  • V2 Command: InitiateWithdrawalCommand (with tillId parameter)
  • BPM Integration: Accepts parameters via BpmUtil.GetPropertyValue()
  • Impact Tracking: Tracks account + till changes via TransactionImpactTracker
  • State Management: PENDING → APPROVED → SETTLED
  • Till Integration: Automatically updates till balance for cash withdrawals
  • Hold Management: Prevents overdraft with concurrent safety

Implementation: CB.Administration.Api/Commands/BPMCore/DepositWithdrawal/AdministrationCoreDepositTransactionCommandHandlers.cs


InitiateWithdrawalCommand (Teller Context)

Purpose: Process customer withdrawal via teller counter (cash payment)

Command: InitiateWithdrawalCommand

Key Parameters for Teller Context:

{
"commandName": "InitiateWithdrawalCommand",
"data": {
"accountEncodedKey": "string (mandatory)",
"amount": "decimal (mandatory)",
"tillId": "string (optional - for cash withdrawals)",
"referenceId": "string (optional)",
"remarks": "string (optional)"
}
}

Teller-Specific Behavior

Cash Withdrawal (with tillId):

  • ✅ Account balance decreased
  • ✅ Available balance hold placed during PENDING
  • ✅ Till balance decreased
  • ✅ GL: DR Cash-in-Till, CR Customer Deposits
  • ✅ Concurrent safety via locking

Example: Cash Withdrawal via Teller

Request:

{
"commandName": "InitiateWithdrawalCommand",
"data": {
"accountEncodedKey": "8a8080827f23dep017f23abc123",
"amount": 30000.00,
"tillId": "TILL-002",
"remarks": "Cash withdrawal at teller counter"
}
}

Response:

{
"isSuccessful": true,
"transactionId": "TXN-WTD-20251229-0001",
"transactionState": "SETTLED",
"message": "Withdrawal processed successfully",
"data": {
"accountEncodedKey": "8a8080827f23dep017f23abc123",
"amount": 30000.00,
"accountBalance": {
"previousBalance": 150000.00,
"newBalance": 120000.00,
"previousAvailableBalance": 150000.00,
"newAvailableBalance": 120000.00
},
"tillBalance": {
"tillId": "TILL-002",
"previousBalance": 350000.00,
"newBalance": 320000.00
},
"impactRecords": 6
}
}

Validation and Safety

Balance Validation:

  1. Account Check: Available balance >= amount (no overdraft)
  2. Till Check: Till balance >= amount (sufficient cash)
  3. Hold Placement: If approval required, hold placed on account
  4. Concurrent Safety: Locking prevents race conditions

Hold Management (for approvals):

// During PENDING state (awaiting approval)
AvailableBalance -= amount; // Hold placed
BookBalance unchanged; // Not yet deducted

// After APPROVED and SETTLED
BookBalance -= amount; // Actually deducted
Hold released; // Available balance already reduced

Till Integration Details

When tillId is provided:

  1. Validation:
    • Till must be OPENED and belong to authorized teller
    • Till must have sufficient cash balance
  2. Balance Update: Till cash balance decreased by withdrawal amount
  3. Impact Tracking: Both account and till changes tracked atomically
  4. GL Posting: Includes till-specific GL accounts
  5. Concurrent Safety: Uses locking on both account and till

Benefits:

  • ✅ Real-time till balance tracking
  • ✅ Automated till reconciliation
  • ✅ Teller performance metrics
  • ✅ Cash position monitoring
  • ✅ Prevents till overdraft (insufficient cash rejection)

Error Scenarios

Insufficient Account Balance:

{
"isSuccessful": false,
"error": "INSUFFICIENT_FUNDS",
"message": "Account balance insufficient for withdrawal",
"data": {
"requestedAmount": 200000.00,
"availableBalance": 120000.00,
"shortfall": 80000.00
}
}

Insufficient Till Balance:

{
"isSuccessful": false,
"error": "INSUFFICIENT_TILL_CASH",
"message": "Till does not have sufficient cash for this withdrawal",
"data": {
"requestedAmount": 500000.00,
"tillBalance": 320000.00,
"shortfall": 180000.00,
"tillId": "TILL-002"
}
}

Large Withdrawal Example (with Approval)

Request:

{
"commandName": "InitiateWithdrawalCommand",
"data": {
"accountEncodedKey": "8a8080827f23dep017f23abc123",
"amount": 500000.00,
"tillId": "TILL-001",
"remarks": "Large cash withdrawal - requires approval"
}
}

Response (PENDING state):

{
"isSuccessful": true,
"transactionId": "TXN-WTD-20251229-0005",
"transactionState": "PENDING",
"message": "Withdrawal requires approval",
"data": {
"accountEncodedKey": "8a8080827f23dep017f23abc123",
"amount": 500000.00,
"approvalRequired": true,
"accountBalance": {
"bookBalance": 1000000.00,
"availableBalance": 500000.00,
"holdAmount": 500000.00
},
"approvers": ["supervisor@bank.com"],
"expiresAt": "2025-12-30T17:00:00Z"
}
}


Developer Resources

For API implementation details, see: