OOD - ATM
Problem
Design a comprehensive object-oriented ATM (Automated Teller Machine) system that handles customer authentication via card and PIN, processes banking transactions (balance inquiry, cash withdrawal, cash/check deposits, fund transfers), manages cash inventory with optimal denomination dispensing, maintains session state with security timeouts, integrates with bank backend systems for account validation and transaction posting, supports operator maintenance operations (cash refill, receipt paper reload), and provides audit logging for all transactions and hardware events. Your design should demonstrate SOLID principles and utilize design patterns to ensure the system is secure, reliable, and maintainable.
Solution
1. Requirements Analysis
Functional Requirements:
- Card authentication: Read card, validate card number, verify PIN with bank backend
- Session management: Track active session, implement timeout after inactivity, secure card retention
- Support multiple transaction types:
- Balance Inquiry: Check available and total balance
- Cash Withdrawal: Dispense cash in optimal denominations
- Cash Deposit: Accept bills and validate amounts
- Check Deposit: Scan check, capture amount, store for clearing
- Fund Transfer: Transfer between accounts (checking/savings or to other accounts)
- Cash dispenser management: Track denominations (50, $100 bills), optimize dispensing algorithm
- Receipt printing: Generate receipts for all transactions
- Account types: Support Checking and Savings accounts with different rules
- Operator functions: Refill cash, reload receipt paper, collect deposited items
- Transaction limits: Daily withdrawal limits, per-transaction limits
- Error handling: Insufficient funds, insufficient cash in ATM, card retention for invalid PIN
- Audit logging: Record all transactions, hardware events, errors
Non-Functional Requirements:
- Security: Encrypt PIN transmission, limit PIN attempts (3 max), secure card retention on failure
- Reliability: Handle hardware failures gracefully, transaction atomicity (complete or rollback)
- Performance: Response time < 3 seconds for balance inquiry, < 5 seconds for cash dispensing
- Availability: 24/7 operation with graceful degradation when low on cash
- Maintainability: Modular hardware components, easy to swap/upgrade
- Auditability: Complete transaction trail, tamper-proof logs
2. Use Case Diagram
Actors:
- Customer: Primary user who performs banking transactions
- Operator: Maintains ATM (refills cash, paper, clears jams)
- Bank System: Backend that validates accounts and processes transactions
- Administrator: Monitors ATM health, generates reports, manages configurations
Primary Use Cases:
- Authenticate Customer - Validate card and PIN
- Check Balance - View account balance
- Withdraw Cash - Dispense cash from account
- Deposit Cash - Accept cash deposits
- Deposit Check - Accept check deposits
- Transfer Funds - Move money between accounts
- Print Receipt - Generate transaction receipt
- Refill Cash - Operator replenishes cash cassettes
- View Transaction Logs - Admin reviews transaction history
- Monitor ATM Status - Check hardware health and cash levels
graph TD subgraph ATMSystem["๐ง ATM System"] UC1[๐ Authenticate Customer] UC2[๐ฐ Check Balance] UC3[๐ต Withdraw Cash] UC4[๐ฅ Deposit Cash] UC5[๐ Deposit Check] UC6[๐ Transfer Funds] UC7[๐งพ Print Receipt] UC8[๐ง Refill Cash] UC9[๐ View Transaction Logs] UC10[๐ Monitor ATM Status] UC11[โ ๏ธ Handle Errors] UC12[๐ Retain Card] end Customer[๐ค Customer] Operator[๐ง Operator] BankSystem[๐ฆ Bank System] Admin[๐จโ๐ผ Administrator] Customer --> UC1 Customer --> UC2 Customer --> UC3 Customer --> UC4 Customer --> UC5 Customer --> UC6 Operator --> UC8 BankSystem --> UC1 BankSystem --> UC3 BankSystem --> UC4 BankSystem --> UC5 BankSystem --> UC6 Admin --> UC9 Admin --> UC10 UC1 -.includes.-> UC12 UC2 -.includes.-> UC7 UC3 -.includes.-> UC7 UC4 -.includes.-> UC7 UC5 -.includes.-> UC7 UC6 -.includes.-> UC7 UC1 -.extends.-> UC11 UC3 -.extends.-> UC11 UC4 -.extends.-> UC11 style Customer fill:#E3F2FD,stroke:#1976D2,stroke-width:2px style Operator fill:#FFF3E0,stroke:#F57C00,stroke-width:2px style BankSystem fill:#E8F5E9,stroke:#388E3C,stroke-width:2px style Admin fill:#F3E5F5,stroke:#7B1FA2,stroke-width:2px style UC1 fill:#FFCCBC,stroke:#D84315,stroke-width:2px style UC2 fill:#B3E5FC,stroke:#0277BD,stroke-width:2px style UC3 fill:#FFE082,stroke:#F57F17,stroke-width:2px style UC4 fill:#C8E6C9,stroke:#388E3C,stroke-width:2px style UC5 fill:#C8E6C9,stroke:#388E3C,stroke-width:2px style UC6 fill:#81D4FA,stroke:#0277BD,stroke-width:2px style UC7 fill:#E1BEE7,stroke:#7B1FA2,stroke-width:2px style UC8 fill:#FFCC80,stroke:#EF6C00,stroke-width:2px style UC9 fill:#CE93D8,stroke:#6A1B9A,stroke-width:2px style UC10 fill:#BA68C8,stroke:#6A1B9A,stroke-width:2px style UC11 fill:#EF9A9A,stroke:#C62828,stroke-width:2px style UC12 fill:#FFAB91,stroke:#D84315,stroke-width:2px
3. Class Diagram
Core Classes and Their Responsibilities:
ATM Core:
ATM- Main controller coordinating all components and managing sessionsATMState(interface) - State pattern for ATM operational statesIdleState,CardInsertedState,AuthenticatedState,TransactionState- Concrete statesSession- Tracks active customer session with timeout management
Hardware Components:
CardReader- Reads card magnetic stripe or chipPINPad- Secure PIN entry keypadScreen- Display interface for user interactionCashDispenser- Manages cash inventory and dispensing with denominationsCashAcceptor- Accepts and validates deposited billsCheckScanner- Scans and reads check detailsReceiptPrinter- Prints transaction receiptsDepositEnvelope- Stores deposited checks
Banking:
BankingService- Interface to bank backendAccount(abstract) - Base account with balance operationsCheckingAccount,SavingsAccount- Concrete account typesCard- ATM card with encrypted PINCustomer- Account holder information
Transactions (Strategy Pattern):
Transaction(abstract) - Template method for transaction executionBalanceInquiry- Query account balanceWithdrawal- Cash withdrawal with denomination optimizationCashDeposit- Deposit cashCheckDeposit- Deposit checkTransfer- Fund transfer between accounts
Supporting Classes:
Money- Value object for currency amountsCashDenomination- Represents bill denominationsTransactionReceipt- Receipt dataAuditLog- Transaction and event loggingDenominationDispenser(Chain of Responsibility) - Optimal bill dispensing
classDiagram %% ATM Core class ATM { -String atmId -String location -ATMState currentState -Session activeSession -CardReader cardReader -PINPad pinPad -Screen screen -CashDispenser cashDispenser -ReceiptPrinter receiptPrinter -BankingService bankingService +insertCard(Card) void +enterPIN(String) bool +selectTransaction(TransactionType) void +executeTransaction(Transaction) TransactionResult +ejectCard() void +setState(ATMState) void } class ATMState { <<interface>> +insertCard(ATM, Card) void +enterPIN(ATM, String) void +selectTransaction(ATM, TransactionType) void +ejectCard(ATM) void } class IdleState { +insertCard(ATM, Card) void +enterPIN(ATM, String) void +selectTransaction(ATM, TransactionType) void +ejectCard(ATM) void } class CardInsertedState { -int pinAttempts +insertCard(ATM, Card) void +enterPIN(ATM, String) void +selectTransaction(ATM, TransactionType) void +ejectCard(ATM) void } class AuthenticatedState { +insertCard(ATM, Card) void +enterPIN(ATM, String) void +selectTransaction(ATM, TransactionType) void +ejectCard(ATM) void } class TransactionState { -Transaction currentTransaction +insertCard(ATM, Card) void +enterPIN(ATM, String) void +selectTransaction(ATM, TransactionType) void +ejectCard(ATM) void } ATMState <|.. IdleState ATMState <|.. CardInsertedState ATMState <|.. AuthenticatedState ATMState <|.. TransactionState ATM --> ATMState ATM --> Session class Session { -String sessionId -Card card -Account account -LocalDateTime startTime -LocalDateTime lastActivityTime -int timeoutSeconds +isExpired() bool +updateActivity() void +getAccount() Account } Session --> Card Session --> Account %% Hardware Components class CardReader { -CardReaderStatus status +readCard() Card +ejectCard() void +retainCard() void +isCardPresent() bool } class PINPad { -bool isSecure +getEncryptedPIN() String +clear() void } class Screen { -String currentMessage +displayMessage(String) void +displayMenu(List~String~) void +showBalance(Money) void +showError(String) void +clear() void } class CashDispenser { -Map~CashDenomination Int~ inventory -int maxDispenseAmount -DenominationDispenser dispenserChain +dispenseCash(Money) DispenseResult +canDispense(Money) bool +getInventory() Map~CashDenomination Int~ +refill(Map~CashDenomination Int~) void +isLowOnCash() bool } class CashAcceptor { -List~Money~ acceptedBills +acceptBill() Money +getTotalAccepted() Money +clear() void } class CheckScanner { +scanCheck() Check +validateCheck(Check) bool } class ReceiptPrinter { -int paperLevel +printReceipt(TransactionReceipt) void +hasPaper() bool +refillPaper() void } ATM --> CardReader ATM --> PINPad ATM --> Screen ATM --> CashDispenser ATM --> CashAcceptor ATM --> CheckScanner ATM --> ReceiptPrinter %% Cash Denomination Management class CashDenomination { <<enumeration>> TWENTY FIFTY HUNDRED } class DenominationDispenser { <<interface>> -DenominationDispenser nextDispenser +dispense(Money) DispenseResult +setNext(DenominationDispenser) void } class HundredDollarDispenser { -int count +dispense(Money) DispenseResult } class FiftyDollarDispenser { -int count +dispense(Money) DispenseResult } class TwentyDollarDispenser { -int count +dispense(Money) DispenseResult } DenominationDispenser <|.. HundredDollarDispenser DenominationDispenser <|.. FiftyDollarDispenser DenominationDispenser <|.. TwentyDollarDispenser CashDispenser --> DenominationDispenser CashDispenser --> CashDenomination %% Banking Domain class BankingService { <<interface>> +validateCard(Card) bool +authenticatePIN(Card, String) bool +getAccount(String) Account +processTransaction(Transaction) TransactionResult +postTransaction(Transaction) void } class Card { -String cardNumber -String cardHolderName -LocalDate expiryDate -String encryptedPIN -CardType cardType -String accountNumber +isExpired() bool +validatePIN(String) bool } class Account { <<abstract>> -String accountNumber -String accountHolderName -Money availableBalance -Money totalBalance -Money dailyWithdrawalLimit -Money todayWithdrawn +canWithdraw(Money) bool +withdraw(Money) bool +deposit(Money) void +getAvailableBalance() Money* +getTotalBalance() Money* } class CheckingAccount { -Money overdraftLimit -bool hasDebitCard +getAvailableBalance() Money +getTotalBalance() Money } class SavingsAccount { -double interestRate -int withdrawalsThisMonth -int maxWithdrawalsPerMonth +getAvailableBalance() Money +getTotalBalance() Money +canWithdraw(Money) bool } Account <|-- CheckingAccount Account <|-- SavingsAccount Card --> Account BankingService --> Account ATM --> BankingService %% Transactions (Strategy Pattern) class Transaction { <<abstract>> -String transactionId -LocalDateTime timestamp -Account account -TransactionStatus status -Money amount +execute() TransactionResult* +validate() bool* +createReceipt() TransactionReceipt -logTransaction() void } class BalanceInquiry { +execute() TransactionResult +validate() bool } class Withdrawal { -Money requestedAmount -Map~CashDenomination Int~ dispensedBills +execute() TransactionResult +validate() bool -optimizeDenominations() Map~CashDenomination Int~ } class CashDeposit { -List~Money~ depositedBills -Money totalAmount +execute() TransactionResult +validate() bool } class CheckDeposit { -Check check -Money checkAmount +execute() TransactionResult +validate() bool } class Transfer { -Account sourceAccount -Account destinationAccount -Money transferAmount -TransferType transferType +execute() TransactionResult +validate() bool } Transaction <|-- BalanceInquiry Transaction <|-- Withdrawal Transaction <|-- CashDeposit Transaction <|-- CheckDeposit Transaction <|-- Transfer Transaction --> Account ATM --> Transaction class TransactionResult { -bool success -String message -Money balanceAfter -TransactionReceipt receipt } class TransactionReceipt { -String transactionId -LocalDateTime timestamp -TransactionType type -Money amount -Money balanceAfter -String atmLocation +format() String } Transaction --> TransactionResult TransactionResult --> TransactionReceipt %% Value Objects class Money { <<value object>> -BigDecimal amount -String currency +add(Money) Money +subtract(Money) Money +multiply(int) Money +compareTo(Money) int +isPositive() bool } %% Supporting Classes class Check { -String checkNumber -String routingNumber -String accountNumber -Money amount -LocalDate date } class AuditLog { -String logId -LocalDateTime timestamp -String atmId -String eventType -String details +log(String, String) void } class Customer { -String customerId -String name -String email -String phoneNumber -List~Account~ accounts -CustomerStatus status } Customer --> Account CheckDeposit --> Check
Design Patterns Applied:
- State Pattern:
ATMStatemanages ATM operational states (Idle, CardInserted, Authenticated, Transaction) - Strategy Pattern:
Transactionhierarchy for different transaction types - Template Method:
Transaction.execute()defines transaction processing template - Chain of Responsibility:
DenominationDispenserfor optimal cash dispensing - Singleton: ATM instance (implied)
- Factory Pattern: Transaction creation based on user selection
- Value Object:
Moneyfor immutable currency representation
SOLID Principles Demonstrated:
- Single Responsibility: Each hardware component has one clear purpose
- Open/Closed: New transaction types can be added without modifying ATM core
- Liskov Substitution: Any
TransactionorAccountsubclass can replace parent - Interface Segregation: Focused interfaces for hardware components and banking service
- Dependency Inversion: ATM depends on
BankingServiceinterface, not concrete implementation
4. Activity Diagrams
Activity: Customer Withdrawal Transaction
flowchart TD Start([๐ง Customer approaches ATM]) --> InsertCard[๐ณ Insert card] InsertCard --> ReadCard[๐ Read card data] ReadCard --> ValidCard{โ Valid card?} ValidCard -->|No| InvalidCard[โ Show invalid card error] InvalidCard --> EjectCard1[โ๏ธ Eject card] EjectCard1 --> End1([End]) ValidCard -->|Yes| PromptPIN[๐ข Prompt for PIN] PromptPIN --> EnterPIN[๐ Enter PIN] EnterPIN --> ValidatePIN[๐ Validate PIN with bank] ValidatePIN --> PINValid{โ Correct PIN?} PINValid -->|No| IncrementAttempts[โ Increment attempts] IncrementAttempts --> MaxAttempts{โ Max attempts (3)?} MaxAttempts -->|Yes| RetainCard[๐ Retain card] RetainCard --> NotifyBank[๐ง Notify bank] NotifyBank --> End1 MaxAttempts -->|No| PromptPIN PINValid -->|Yes| CreateSession[โ Create session] CreateSession --> DisplayMenu[๐ Display transaction menu] DisplayMenu --> SelectWithdraw[๐ต Select withdrawal] SelectWithdraw --> EnterAmount[๐ฐ Enter amount] EnterAmount --> ValidateAmount{โ Valid amount?} ValidateAmount -->|No| AmountError[โ Invalid amount error] AmountError --> EnterAmount ValidateAmount -->|Yes| CheckBalance{๐ณ Sufficient balance?} CheckBalance -->|No| InsufficientFunds[โ ๏ธ Insufficient funds] InsufficientFunds --> TryAgain{๐ Try different amount?} TryAgain -->|Yes| EnterAmount TryAgain -->|No| CancelTx1 CheckBalance -->|Yes| CheckDailyLimit{๐ Within daily limit?} CheckDailyLimit -->|No| LimitExceeded[โ ๏ธ Daily limit exceeded] LimitExceeded --> CancelTx1 CheckDailyLimit -->|Yes| CheckCash{๐ต Cash available in ATM?} CheckCash -->|No| InsufficientCash[โ ๏ธ Insufficient cash in ATM] InsufficientCash --> CancelTx1 CheckCash -->|Yes| CalculateDenominations[๐งฎ Calculate bill denominations] CalculateDenominations --> DebitAccount[๐ณ Debit account] DebitAccount --> DispenseCash[๐ธ Dispense cash] DispenseCash --> VerifyDispense{โ Cash dispensed?} VerifyDispense -->|No| RollbackTx[โป๏ธ Rollback transaction] RollbackTx --> HardwareError[โ ๏ธ Hardware error] HardwareError --> CancelTx1 VerifyDispense -->|Yes| UpdateInventory[๐ Update cash inventory] UpdateInventory --> PrintReceipt[๐งพ Print receipt] PrintReceipt --> LogTransaction[๐ Log transaction] LogTransaction --> MoreTransactions{๐ค More transactions?} MoreTransactions -->|Yes| DisplayMenu MoreTransactions -->|No| ThankYou[๐ Thank you message] ThankYou --> EjectCard2[โ๏ธ Eject card] EjectCard2 --> EndSession[๐ End session] EndSession --> End2([โ Transaction complete]) CancelTx1[โ Cancel transaction] --> EjectCard2 style Start fill:#E1F5FE,stroke:#01579B,stroke-width:3px style End1 fill:#FFCDD2,stroke:#C62828,stroke-width:3px style End2 fill:#C8E6C9,stroke:#2E7D32,stroke-width:3px style InsertCard fill:#B3E5FC,stroke:#0277BD,stroke-width:2px style ReadCard fill:#81D4FA,stroke:#0277BD,stroke-width:2px style PromptPIN fill:#FFCCBC,stroke:#D84315,stroke-width:2px style EnterPIN fill:#FFAB91,stroke:#D84315,stroke-width:2px style ValidatePIN fill:#FF8A65,stroke:#D84315,stroke-width:2px style CreateSession fill:#A5D6A7,stroke:#388E3C,stroke-width:2px style DisplayMenu fill:#81C784,stroke:#2E7D32,stroke-width:2px style SelectWithdraw fill:#66BB6A,stroke:#2E7D32,stroke-width:2px style EnterAmount fill:#FFE082,stroke:#F57F17,stroke-width:2px style CalculateDenominations fill:#FFCC80,stroke:#EF6C00,stroke-width:2px style DebitAccount fill:#FFE0B2,stroke:#EF6C00,stroke-width:2px style DispenseCash fill:#CE93D8,stroke:#7B1FA2,stroke-width:2px style UpdateInventory fill:#E1BEE7,stroke:#8E24AA,stroke-width:2px style PrintReceipt fill:#BA68C8,stroke:#6A1B9A,stroke-width:2px style LogTransaction fill:#AB47BC,stroke:#6A1B9A,stroke-width:2px style ValidCard fill:#FFF9C4,stroke:#F9A825,stroke-width:2px style PINValid fill:#FFF9C4,stroke:#F9A825,stroke-width:2px style MaxAttempts fill:#FFF9C4,stroke:#F9A825,stroke-width:2px style ValidateAmount fill:#FFF9C4,stroke:#F9A825,stroke-width:2px style CheckBalance fill:#FFF9C4,stroke:#F9A825,stroke-width:2px style CheckDailyLimit fill:#FFF9C4,stroke:#F9A825,stroke-width:2px style CheckCash fill:#FFF9C4,stroke:#F9A825,stroke-width:2px style VerifyDispense fill:#FFF9C4,stroke:#F9A825,stroke-width:2px style MoreTransactions fill:#FFF9C4,stroke:#F9A825,stroke-width:2px style TryAgain fill:#FFF9C4,stroke:#F9A825,stroke-width:2px style InvalidCard fill:#EF9A9A,stroke:#C62828,stroke-width:2px style RetainCard fill:#EF5350,stroke:#C62828,stroke-width:2px style InsufficientFunds fill:#FFCCBC,stroke:#E64A19,stroke-width:2px style LimitExceeded fill:#FFCCBC,stroke:#E64A19,stroke-width:2px style InsufficientCash fill:#FFCCBC,stroke:#E64A19,stroke-width:2px style AmountError fill:#FFCCBC,stroke:#E64A19,stroke-width:2px style HardwareError fill:#EF9A9A,stroke:#C62828,stroke-width:2px
Activity: Cash Deposit Transaction
flowchart TD Start([๐ณ Customer authenticated]) --> SelectDeposit[๐ฅ Select deposit] SelectDeposit --> ChooseType{๐ต Cash or Check?} ChooseType -->|Cash| PromptBills[๐ Prompt to insert bills] ChooseType -->|Check| ScanCheck[๐ Scan check] PromptBills --> InsertBill[๐ต Insert bill] InsertBill --> ValidateBill{โ Valid bill?} ValidateBill -->|No| RejectBill[โ Reject bill] RejectBill --> MoreBills1{โ Insert more?} ValidateBill -->|Yes| AcceptBill[โ Accept bill] AcceptBill --> UpdateTotal[๐ฐ Update total] UpdateTotal --> MoreBills1 MoreBills1 -->|Yes| InsertBill MoreBills1 -->|No| ConfirmCashAmount{โ Confirm total?} ConfirmCashAmount -->|No| ReturnBills[โฉ๏ธ Return all bills] ReturnBills --> Cancel1 ConfirmCashAmount -->|Yes| ProcessCashDeposit[โ๏ธ Process deposit] ProcessCashDeposit --> CreditCashAccount ScanCheck --> ValidateCheck{โ Valid check?} ValidateCheck -->|No| CheckError[โ Invalid check] CheckError --> ReturnCheck[โฉ๏ธ Return check] ReturnCheck --> Cancel1 ValidateCheck -->|Yes| CaptureAmount[๐ฐ Capture check amount] CaptureAmount --> ConfirmCheckAmount{โ Confirm amount?} ConfirmCheckAmount -->|No| ReturnCheck ConfirmCheckAmount -->|Yes| StoreCheck[๐ Store check for clearing] StoreCheck --> ProcessCheckDeposit[โ๏ธ Process deposit] ProcessCheckDeposit --> PendingCredit[โณ Mark as pending] PendingCredit --> CreditCheckAccount CreditCashAccount[๐ณ Credit account - Cash] CreditCheckAccount[๐ณ Credit account - Check] CreditCashAccount --> UpdateBalance1 CreditCheckAccount --> UpdateBalance1 UpdateBalance1[๐ Update balance] --> PrintDepositReceipt[๐งพ Print receipt] PrintDepositReceipt --> LogDepositTx[๐ Log transaction] LogDepositTx --> ShowConfirmation[โ Show confirmation] ShowConfirmation --> MoreTx{๐ค More transactions?} MoreTx -->|Yes| ReturnToMenu[๐ Return to menu] ReturnToMenu --> End1([Continue]) MoreTx -->|No| End2([โ Complete]) Cancel1[โ Cancel deposit] --> End3([End]) style Start fill:#E1F5FE,stroke:#01579B,stroke-width:3px style End1 fill:#81D4FA,stroke:#0277BD,stroke-width:3px style End2 fill:#C8E6C9,stroke:#2E7D32,stroke-width:3px style End3 fill:#FFCDD2,stroke:#C62828,stroke-width:3px style SelectDeposit fill:#B3E5FC,stroke:#0277BD,stroke-width:2px style PromptBills fill:#81D4FA,stroke:#0277BD,stroke-width:2px style InsertBill fill:#4FC3F7,stroke:#01579B,stroke-width:2px style ScanCheck fill:#CE93D8,stroke:#7B1FA2,stroke-width:2px style ValidateCheck fill:#E1BEE7,stroke:#8E24AA,stroke-width:2px style CaptureAmount fill:#BA68C8,stroke:#6A1B9A,stroke-width:2px style StoreCheck fill:#AB47BC,stroke:#6A1B9A,stroke-width:2px style AcceptBill fill:#A5D6A7,stroke:#388E3C,stroke-width:2px style UpdateTotal fill:#81C784,stroke:#2E7D32,stroke-width:2px style ProcessCashDeposit fill:#66BB6A,stroke:#2E7D32,stroke-width:2px style ProcessCheckDeposit fill:#66BB6A,stroke:#2E7D32,stroke-width:2px style CreditCashAccount fill:#FFE082,stroke:#F57F17,stroke-width:2px style CreditCheckAccount fill:#FFE082,stroke:#F57F17,stroke-width:2px style UpdateBalance1 fill:#FFCC80,stroke:#EF6C00,stroke-width:2px style PrintDepositReceipt fill:#E1BEE7,stroke:#7B1FA2,stroke-width:2px style LogDepositTx fill:#CE93D8,stroke:#6A1B9A,stroke-width:2px style ShowConfirmation fill:#C8E6C9,stroke:#388E3C,stroke-width:2px style ChooseType fill:#FFF9C4,stroke:#F9A825,stroke-width:2px style ValidateBill fill:#FFF9C4,stroke:#F9A825,stroke-width:2px style MoreBills1 fill:#FFF9C4,stroke:#F9A825,stroke-width:2px style ConfirmCashAmount fill:#FFF9C4,stroke:#F9A825,stroke-width:2px style ConfirmCheckAmount fill:#FFF9C4,stroke:#F9A825,stroke-width:2px style MoreTx fill:#FFF9C4,stroke:#F9A825,stroke-width:2px style RejectBill fill:#FFCCBC,stroke:#E64A19,stroke-width:2px style CheckError fill:#FFCCBC,stroke:#E64A19,stroke-width:2px style ReturnBills fill:#FFAB91,stroke:#D84315,stroke-width:2px style ReturnCheck fill:#FFAB91,stroke:#D84315,stroke-width:2px
Activity: Fund Transfer Transaction
flowchart TD Start([๐ณ Customer authenticated]) --> SelectTransfer[๐ Select transfer] SelectTransfer --> ChooseAccounts{๐ Transfer type?} ChooseAccounts -->|Between own accounts| SelectSource1[๐ค Select source account] ChooseAccounts -->|To another customer| SelectSource2[๐ค Select source account] SelectSource1 --> SelectDest1[๐ฅ Select destination account] SelectDest1 --> EnterAmount1 SelectSource2 --> EnterDestAccount[๐ข Enter destination account] EnterDestAccount --> ValidateDestAccount{โ Valid account?} ValidateDestAccount -->|No| InvalidAccount[โ Invalid account] InvalidAccount --> TryAgain1{๐ Try again?} TryAgain1 -->|Yes| EnterDestAccount TryAgain1 -->|No| CancelTransfer ValidateDestAccount -->|Yes| EnterAmount1 EnterAmount1[๐ฐ Enter transfer amount] --> ValidateAmount{โ Valid amount?} ValidateAmount -->|No| AmountError[โ Invalid amount] AmountError --> EnterAmount1 ValidateAmount -->|Yes| CheckSourceBalance{๐ณ Sufficient balance?} CheckSourceBalance -->|No| InsufficientFunds[โ ๏ธ Insufficient funds] InsufficientFunds --> TryAgain2{๐ Different amount?} TryAgain2 -->|Yes| EnterAmount1 TryAgain2 -->|No| CancelTransfer CheckSourceBalance -->|Yes| CheckTransferLimit{๐ Within limits?} CheckTransferLimit -->|No| LimitExceeded[โ ๏ธ Transfer limit exceeded] LimitExceeded --> CancelTransfer CheckTransferLimit -->|Yes| ShowSummary[๐ Show transfer summary] ShowSummary --> ConfirmTransfer{โ Confirm transfer?} ConfirmTransfer -->|No| CancelTransfer ConfirmTransfer -->|Yes| BeginTransaction[๐ Begin transaction] BeginTransaction --> DebitSource[๐ Debit source account] DebitSource --> SourceDebited{โ Debit successful?} SourceDebited -->|No| TransactionError[โ Transaction error] TransactionError --> Rollback[โป๏ธ Rollback] Rollback --> CancelTransfer SourceDebited -->|Yes| CreditDestination[๐ Credit destination] CreditDestination --> DestCredited{โ Credit successful?} DestCredited -->|No| RollbackDebit[โป๏ธ Rollback debit] RollbackDebit --> TransactionError DestCredited -->|Yes| CommitTransaction[โ Commit transaction] CommitTransaction --> RecordTransfer[๐ Record transfer] RecordTransfer --> PrintTransferReceipt[๐งพ Print receipt] PrintTransferReceipt --> ShowSuccess[โ Transfer successful] ShowSuccess --> LogTransferTx[๐ Log transaction] LogTransferTx --> MoreTx{๐ค More transactions?} MoreTx -->|Yes| ReturnMenu[๐ Return to menu] ReturnMenu --> End1([Continue]) MoreTx -->|No| End2([โ Complete]) CancelTransfer[โ Cancel transfer] --> End3([End]) style Start fill:#E1F5FE,stroke:#01579B,stroke-width:3px style End1 fill:#81D4FA,stroke:#0277BD,stroke-width:3px style End2 fill:#C8E6C9,stroke:#2E7D32,stroke-width:3px style End3 fill:#FFCDD2,stroke:#C62828,stroke-width:3px style SelectTransfer fill:#B3E5FC,stroke:#0277BD,stroke-width:2px style SelectSource1 fill:#81D4FA,stroke:#0277BD,stroke-width:2px style SelectSource2 fill:#81D4FA,stroke:#0277BD,stroke-width:2px style SelectDest1 fill:#4FC3F7,stroke:#01579B,stroke-width:2px style EnterDestAccount fill:#4FC3F7,stroke:#01579B,stroke-width:2px style EnterAmount1 fill:#FFE082,stroke:#F57F17,stroke-width:2px style ShowSummary fill:#FFCC80,stroke:#EF6C00,stroke-width:2px style BeginTransaction fill:#CE93D8,stroke:#7B1FA2,stroke-width:2px style DebitSource fill:#E1BEE7,stroke:#8E24AA,stroke-width:2px style CreditDestination fill:#BA68C8,stroke:#6A1B9A,stroke-width:2px style CommitTransaction fill:#AB47BC,stroke:#6A1B9A,stroke-width:2px style RecordTransfer fill:#A5D6A7,stroke:#388E3C,stroke-width:2px style PrintTransferReceipt fill:#81C784,stroke:#2E7D32,stroke-width:2px style ShowSuccess fill:#66BB6A,stroke:#2E7D32,stroke-width:2px style LogTransferTx fill:#4CAF50,stroke:#1B5E20,stroke-width:2px style ChooseAccounts fill:#FFF9C4,stroke:#F9A825,stroke-width:2px style ValidateDestAccount fill:#FFF9C4,stroke:#F9A825,stroke-width:2px style ValidateAmount fill:#FFF9C4,stroke:#F9A825,stroke-width:2px style CheckSourceBalance fill:#FFF9C4,stroke:#F9A825,stroke-width:2px style CheckTransferLimit fill:#FFF9C4,stroke:#F9A825,stroke-width:2px style ConfirmTransfer fill:#FFF9C4,stroke:#F9A825,stroke-width:2px style SourceDebited fill:#FFF9C4,stroke:#F9A825,stroke-width:2px style DestCredited fill:#FFF9C4,stroke:#F9A825,stroke-width:2px style TryAgain1 fill:#FFF9C4,stroke:#F9A825,stroke-width:2px style TryAgain2 fill:#FFF9C4,stroke:#F9A825,stroke-width:2px style MoreTx fill:#FFF9C4,stroke:#F9A825,stroke-width:2px style InvalidAccount fill:#FFCCBC,stroke:#E64A19,stroke-width:2px style AmountError fill:#FFCCBC,stroke:#E64A19,stroke-width:2px style InsufficientFunds fill:#FFCCBC,stroke:#E64A19,stroke-width:2px style LimitExceeded fill:#FFCCBC,stroke:#E64A19,stroke-width:2px style TransactionError fill:#EF9A9A,stroke:#C62828,stroke-width:2px style Rollback fill:#EF5350,stroke:#C62828,stroke-width:2px style RollbackDebit fill:#EF5350,stroke:#C62828,stroke-width:2px
5. High-Level Code Implementation
Java
import java.time.*;
import java.util.*;
import java.math.BigDecimal;
// ============================================================================
// Enums
// ============================================================================
enum TransactionType {
BALANCE_INQUIRY,
WITHDRAWAL,
CASH_DEPOSIT,
CHECK_DEPOSIT,
TRANSFER
}
enum TransactionStatus {
PENDING,
IN_PROGRESS,
COMPLETED,
FAILED,
CANCELLED,
ROLLED_BACK
}
enum CardType {
DEBIT,
CREDIT,
ATM
}
enum CustomerStatus {
ACTIVE,
BLOCKED,
SUSPENDED,
CLOSED
}
enum CardReaderStatus {
IDLE,
CARD_INSERTED,
READING,
ERROR
}
enum CashDenomination {
TWENTY(20),
FIFTY(50),
HUNDRED(100);
private final int value;
CashDenomination(int value) {
this.value = value;
}
public int getValue() { return value; }
}
enum TransferType {
CHECKING_TO_SAVINGS,
SAVINGS_TO_CHECKING,
TO_EXTERNAL_ACCOUNT
}
// ============================================================================
// Value Objects
// ============================================================================
class Money {
private final BigDecimal amount;
private final String currency;
public Money(BigDecimal amount, String currency) {
if (amount.compareTo(BigDecimal.ZERO) < 0) {
throw new IllegalArgumentException("Amount cannot be negative");
}
this.amount = amount;
this.currency = currency;
}
public Money(double amount) {
this(new BigDecimal(amount), "USD");
}
public Money add(Money other) {
validateCurrency(other);
return new Money(this.amount.add(other.amount), this.currency);
}
public Money subtract(Money other) {
validateCurrency(other);
return new Money(this.amount.subtract(other.amount), this.currency);
}
public Money multiply(int factor) {
return new Money(this.amount.multiply(BigDecimal.valueOf(factor)), this.currency);
}
public boolean isGreaterThan(Money other) {
validateCurrency(other);
return this.amount.compareTo(other.amount) > 0;
}
public boolean isGreaterThanOrEqual(Money other) {
validateCurrency(other);
return this.amount.compareTo(other.amount) >= 0;
}
public boolean isPositive() {
return this.amount.compareTo(BigDecimal.ZERO) > 0;
}
private void validateCurrency(Money other) {
if (!this.currency.equals(other.currency)) {
throw new IllegalArgumentException("Currency mismatch");
}
}
public BigDecimal getAmount() { return amount; }
public String getCurrency() { return currency; }
@Override
public String toString() {
return String.format("%s %.2f", currency, amount);
}
}
// ============================================================================
// ATM State Pattern
// ============================================================================
interface ATMState {
void insertCard(ATM atm, Card card);
void enterPIN(ATM atm, String pin);
void selectTransaction(ATM atm, TransactionType type);
void ejectCard(ATM atm);
}
class IdleState implements ATMState {
@Override
public void insertCard(ATM atm, Card card) {
if (card.isExpired()) {
atm.getScreen().showError("Card expired");
atm.getCardReader().ejectCard();
return;
}
atm.setCurrentCard(card);
atm.setState(new CardInsertedState());
atm.getScreen().displayMessage("Please enter your PIN");
}
@Override
public void enterPIN(ATM atm, String pin) {
atm.getScreen().showError("Please insert card first");
}
@Override
public void selectTransaction(ATM atm, TransactionType type) {
atm.getScreen().showError("Please insert card first");
}
@Override
public void ejectCard(ATM atm) {
// No card to eject
}
}
class CardInsertedState implements ATMState {
private int pinAttempts = 0;
private static final int MAX_PIN_ATTEMPTS = 3;
@Override
public void insertCard(ATM atm, Card card) {
atm.getScreen().showError("Card already inserted");
}
@Override
public void enterPIN(ATM atm, String pin) {
Card card = atm.getCurrentCard();
boolean valid = atm.getBankingService().authenticatePIN(card, pin);
if (valid) {
Account account = atm.getBankingService().getAccount(card.getAccountNumber());
Session session = new Session(card, account);
atm.setActiveSession(session);
atm.setState(new AuthenticatedState());
atm.getScreen().displayMessage("Authentication successful");
// Display main menu
List<String> menuOptions = Arrays.asList(
"1. Balance Inquiry",
"2. Withdraw Cash",
"3. Deposit Cash",
"4. Transfer Funds"
);
atm.getScreen().displayMenu(menuOptions);
} else {
pinAttempts++;
if (pinAttempts >= MAX_PIN_ATTEMPTS) {
atm.getCardReader().retainCard();
atm.getBankingService().notifyCardRetained(card);
atm.getScreen().showError("Card retained due to incorrect PIN");
atm.setState(new IdleState());
} else {
atm.getScreen().showError("Incorrect PIN. Attempt " + pinAttempts + " of " + MAX_PIN_ATTEMPTS);
}
}
}
@Override
public void selectTransaction(ATM atm, TransactionType type) {
atm.getScreen().showError("Please enter PIN first");
}
@Override
public void ejectCard(ATM atm) {
atm.getCardReader().ejectCard();
atm.setState(new IdleState());
}
}
class AuthenticatedState implements ATMState {
@Override
public void insertCard(ATM atm, Card card) {
atm.getScreen().showError("Session already active");
}
@Override
public void enterPIN(ATM atm, String pin) {
atm.getScreen().showError("Already authenticated");
}
@Override
public void selectTransaction(ATM atm, TransactionType type) {
Session session = atm.getActiveSession();
if (session.isExpired()) {
atm.getScreen().showError("Session expired. Please restart.");
ejectCard(atm);
return;
}
session.updateActivity();
Transaction transaction = createTransaction(type, session.getAccount(), atm);
atm.setState(new TransactionState(transaction));
atm.executeTransaction(transaction);
}
@Override
public void ejectCard(ATM atm) {
atm.endSession();
atm.getCardReader().ejectCard();
atm.setState(new IdleState());
atm.getScreen().displayMessage("Thank you for using our ATM");
}
private Transaction createTransaction(TransactionType type, Account account, ATM atm) {
switch (type) {
case BALANCE_INQUIRY:
return new BalanceInquiry(account);
case WITHDRAWAL:
return new Withdrawal(account, atm.getCashDispenser());
case CASH_DEPOSIT:
return new CashDeposit(account, atm.getCashAcceptor());
case TRANSFER:
return new Transfer(account);
default:
throw new IllegalArgumentException("Unknown transaction type");
}
}
}
class TransactionState implements ATMState {
private Transaction currentTransaction;
public TransactionState(Transaction transaction) {
this.currentTransaction = transaction;
}
@Override
public void insertCard(ATM atm, Card card) {
atm.getScreen().showError("Transaction in progress");
}
@Override
public void enterPIN(ATM atm, String pin) {
// Handle PIN for transaction authorization if needed
}
@Override
public void selectTransaction(ATM atm, TransactionType type) {
atm.getScreen().showError("Please complete current transaction");
}
@Override
public void ejectCard(ATM atm) {
// Complete or cancel transaction first
atm.getScreen().showError("Please complete transaction first");
}
public Transaction getCurrentTransaction() {
return currentTransaction;
}
}
// ============================================================================
// Session Management
// ============================================================================
class Session {
private String sessionId;
private Card card;
private Account account;
private LocalDateTime startTime;
private LocalDateTime lastActivityTime;
private static final int TIMEOUT_SECONDS = 120; // 2 minutes
public Session(Card card, Account account) {
this.sessionId = UUID.randomUUID().toString();
this.card = card;
this.account = account;
this.startTime = LocalDateTime.now();
this.lastActivityTime = LocalDateTime.now();
}
public boolean isExpired() {
return LocalDateTime.now().isAfter(lastActivityTime.plusSeconds(TIMEOUT_SECONDS));
}
public void updateActivity() {
this.lastActivityTime = LocalDateTime.now();
}
public Account getAccount() { return account; }
public Card getCard() { return card; }
public String getSessionId() { return sessionId; }
}
// ============================================================================
// ATM Core
// ============================================================================
class ATM {
private String atmId;
private String location;
private ATMState currentState;
private Session activeSession;
private Card currentCard;
// Hardware components
private CardReader cardReader;
private PINPad pinPad;
private Screen screen;
private CashDispenser cashDispenser;
private CashAcceptor cashAcceptor;
private CheckScanner checkScanner;
private ReceiptPrinter receiptPrinter;
// Services
private BankingService bankingService;
private AuditLog auditLog;
public ATM(String atmId, String location) {
this.atmId = atmId;
this.location = location;
this.currentState = new IdleState();
// Initialize hardware
this.cardReader = new CardReader();
this.pinPad = new PINPad();
this.screen = new Screen();
this.cashDispenser = new CashDispenser();
this.cashAcceptor = new CashAcceptor();
this.checkScanner = new CheckScanner();
this.receiptPrinter = new ReceiptPrinter();
this.auditLog = new AuditLog(atmId);
}
public void insertCard(Card card) {
currentState.insertCard(this, card);
}
public void enterPIN(String pin) {
currentState.enterPIN(this, pin);
}
public void selectTransaction(TransactionType type) {
currentState.selectTransaction(this, type);
}
public void executeTransaction(Transaction transaction) {
if (!transaction.validate()) {
screen.showError("Transaction validation failed");
return;
}
TransactionResult result = transaction.execute();
if (result.isSuccess()) {
if (receiptPrinter.hasPaper()) {
receiptPrinter.printReceipt(result.getReceipt());
}
screen.displayMessage("Transaction successful");
auditLog.log("TRANSACTION_SUCCESS", transaction.toString());
} else {
screen.showError("Transaction failed: " + result.getMessage());
auditLog.log("TRANSACTION_FAILED", result.getMessage());
}
// Return to authenticated state
setState(new AuthenticatedState());
// Display menu again
List<String> menuOptions = Arrays.asList(
"1. Balance Inquiry",
"2. Withdraw Cash",
"3. Deposit Cash",
"4. Transfer Funds",
"5. Exit"
);
screen.displayMenu(menuOptions);
}
public void ejectCard() {
currentState.ejectCard(this);
}
public void endSession() {
this.activeSession = null;
this.currentCard = null;
}
// Getters and setters
public void setState(ATMState state) { this.currentState = state; }
public void setActiveSession(Session session) { this.activeSession = session; }
public void setCurrentCard(Card card) { this.currentCard = card; }
public Session getActiveSession() { return activeSession; }
public Card getCurrentCard() { return currentCard; }
public CardReader getCardReader() { return cardReader; }
public Screen getScreen() { return screen; }
public CashDispenser getCashDispenser() { return cashDispenser; }
public CashAcceptor getCashAcceptor() { return cashAcceptor; }
public BankingService getBankingService() { return bankingService; }
public String getAtmId() { return atmId; }
public String getLocation() { return location; }
public void setBankingService(BankingService service) {
this.bankingService = service;
}
}
// ============================================================================
// Hardware Components
// ============================================================================
class CardReader {
private CardReaderStatus status;
private Card insertedCard;
public CardReader() {
this.status = CardReaderStatus.IDLE;
}
public Card readCard() {
this.status = CardReaderStatus.READING;
// Simulate card reading
this.status = CardReaderStatus.CARD_INSERTED;
return insertedCard;
}
public void ejectCard() {
insertedCard = null;
status = CardReaderStatus.IDLE;
}
public void retainCard() {
// Card is kept by machine
insertedCard = null;
status = CardReaderStatus.IDLE;
}
public boolean isCardPresent() {
return insertedCard != null;
}
}
class PINPad {
public String getEncryptedPIN() {
// Simulate secure PIN entry
// In real implementation, PIN would be encrypted
return "encrypted_pin";
}
public void clear() {
// Clear PIN entry
}
}
class Screen {
private String currentMessage;
public void displayMessage(String message) {
this.currentMessage = message;
System.out.println("Screen: " + message);
}
public void displayMenu(List<String> options) {
System.out.println("Screen Menu:");
for (String option : options) {
System.out.println(" " + option);
}
}
public void showBalance(Money available, Money total) {
System.out.println("Available Balance: " + available);
System.out.println("Total Balance: " + total);
}
public void showError(String error) {
System.out.println("ERROR: " + error);
}
public void clear() {
currentMessage = "";
}
}
class CashDispenser {
private Map<CashDenomination, Integer> inventory;
private DenominationDispenser dispenserChain;
public CashDispenser() {
this.inventory = new EnumMap<>(CashDenomination.class);
inventory.put(CashDenomination.TWENTY, 100);
inventory.put(CashDenomination.FIFTY, 50);
inventory.put(CashDenomination.HUNDRED, 30);
// Setup chain of responsibility
DenominationDispenser hundred = new HundredDollarDispenser();
DenominationDispenser fifty = new FiftyDollarDispenser();
DenominationDispenser twenty = new TwentyDollarDispenser();
hundred.setNext(fifty);
fifty.setNext(twenty);
this.dispenserChain = hundred;
}
public DispenseResult dispenseCash(Money amount) {
if (!canDispense(amount)) {
return new DispenseResult(false, "Insufficient cash in ATM", null);
}
DispenseResult result = dispenserChain.dispense(amount, inventory);
if (result.isSuccess()) {
// Update inventory
for (Map.Entry<CashDenomination, Integer> entry : result.getDispensedBills().entrySet()) {
inventory.merge(entry.getKey(), -entry.getValue(), Integer::sum);
}
}
return result;
}
public boolean canDispense(Money amount) {
int totalAvailable = 0;
for (Map.Entry<CashDenomination, Integer> entry : inventory.entrySet()) {
totalAvailable += entry.getKey().getValue() * entry.getValue();
}
return totalAvailable >= amount.getAmount().intValue();
}
public void refill(Map<CashDenomination, Integer> bills) {
for (Map.Entry<CashDenomination, Integer> entry : bills.entrySet()) {
inventory.merge(entry.getKey(), entry.getValue(), Integer::sum);
}
}
public boolean isLowOnCash() {
int total = inventory.values().stream().mapToInt(Integer::intValue).sum();
return total < 50;
}
public Map<CashDenomination, Integer> getInventory() {
return new EnumMap<>(inventory);
}
}
class CashAcceptor {
private List<Money> acceptedBills;
public CashAcceptor() {
this.acceptedBills = new ArrayList<>();
}
public void acceptBill(Money bill) {
// Validate bill
acceptedBills.add(bill);
}
public Money getTotalAccepted() {
Money total = new Money(0);
for (Money bill : acceptedBills) {
total = total.add(bill);
}
return total;
}
public void clear() {
acceptedBills.clear();
}
}
class CheckScanner {
public Check scanCheck() {
// Simulate check scanning
return new Check("123456", "987654321", "1234567890", new Money(100), LocalDate.now());
}
public boolean validateCheck(Check check) {
// Basic validation
return check.getAmount().isPositive();
}
}
class ReceiptPrinter {
private int paperLevel;
public ReceiptPrinter() {
this.paperLevel = 100;
}
public void printReceipt(TransactionReceipt receipt) {
if (paperLevel > 0) {
System.out.println("Printing receipt:");
System.out.println(receipt.format());
paperLevel--;
}
}
public boolean hasPaper() {
return paperLevel > 0;
}
public void refillPaper() {
paperLevel = 100;
}
}
// ============================================================================
// Chain of Responsibility for Denomination Dispensing
// ============================================================================
interface DenominationDispenser {
void setNext(DenominationDispenser next);
DispenseResult dispense(Money amount, Map<CashDenomination, Integer> inventory);
}
class HundredDollarDispenser implements DenominationDispenser {
private DenominationDispenser next;
@Override
public void setNext(DenominationDispenser next) {
this.next = next;
}
@Override
public DispenseResult dispense(Money amount, Map<CashDenomination, Integer> inventory) {
int remaining = amount.getAmount().intValue();
int available = inventory.getOrDefault(CashDenomination.HUNDRED, 0);
int needed = remaining / 100;
int toDispense = Math.min(needed, available);
Map<CashDenomination, Integer> dispensed = new EnumMap<>(CashDenomination.class);
if (toDispense > 0) {
dispensed.put(CashDenomination.HUNDRED, toDispense);
remaining -= toDispense * 100;
}
if (remaining > 0 && next != null) {
DispenseResult nextResult = next.dispense(new Money(remaining), inventory);
if (nextResult.isSuccess()) {
dispensed.putAll(nextResult.getDispensedBills());
return new DispenseResult(true, "Cash dispensed", dispensed);
}
}
if (remaining == 0) {
return new DispenseResult(true, "Cash dispensed", dispensed);
}
return new DispenseResult(false, "Cannot dispense exact amount", null);
}
}
class FiftyDollarDispenser implements DenominationDispenser {
private DenominationDispenser next;
@Override
public void setNext(DenominationDispenser next) {
this.next = next;
}
@Override
public DispenseResult dispense(Money amount, Map<CashDenomination, Integer> inventory) {
int remaining = amount.getAmount().intValue();
int available = inventory.getOrDefault(CashDenomination.FIFTY, 0);
int needed = remaining / 50;
int toDispense = Math.min(needed, available);
Map<CashDenomination, Integer> dispensed = new EnumMap<>(CashDenomination.class);
if (toDispense > 0) {
dispensed.put(CashDenomination.FIFTY, toDispense);
remaining -= toDispense * 50;
}
if (remaining > 0 && next != null) {
DispenseResult nextResult = next.dispense(new Money(remaining), inventory);
if (nextResult.isSuccess()) {
dispensed.putAll(nextResult.getDispensedBills());
return new DispenseResult(true, "Cash dispensed", dispensed);
}
}
if (remaining == 0) {
return new DispenseResult(true, "Cash dispensed", dispensed);
}
return new DispenseResult(false, "Cannot dispense exact amount", null);
}
}
class TwentyDollarDispenser implements DenominationDispenser {
private DenominationDispenser next;
@Override
public void setNext(DenominationDispenser next) {
this.next = next;
}
@Override
public DispenseResult dispense(Money amount, Map<CashDenomination, Integer> inventory) {
int remaining = amount.getAmount().intValue();
int available = inventory.getOrDefault(CashDenomination.TWENTY, 0);
int needed = remaining / 20;
int toDispense = Math.min(needed, available);
Map<CashDenomination, Integer> dispensed = new EnumMap<>(CashDenomination.class);
if (toDispense > 0) {
dispensed.put(CashDenomination.TWENTY, toDispense);
remaining -= toDispense * 20;
}
if (remaining == 0) {
return new DispenseResult(true, "Cash dispensed", dispensed);
}
return new DispenseResult(false, "Cannot dispense exact amount", null);
}
}
class DispenseResult {
private boolean success;
private String message;
private Map<CashDenomination, Integer> dispensedBills;
public DispenseResult(boolean success, String message, Map<CashDenomination, Integer> dispensedBills) {
this.success = success;
this.message = message;
this.dispensedBills = dispensedBills;
}
public boolean isSuccess() { return success; }
public String getMessage() { return message; }
public Map<CashDenomination, Integer> getDispensedBills() { return dispensedBills; }
}
// ============================================================================
// Banking Domain
// ============================================================================
interface BankingService {
boolean validateCard(Card card);
boolean authenticatePIN(Card card, String pin);
Account getAccount(String accountNumber);
TransactionResult processTransaction(Transaction transaction);
void notifyCardRetained(Card card);
}
class Card {
private String cardNumber;
private String cardHolderName;
private LocalDate expiryDate;
private String encryptedPIN;
private CardType cardType;
private String accountNumber;
public Card(String cardNumber, String cardHolderName, LocalDate expiryDate,
String encryptedPIN, CardType cardType, String accountNumber) {
this.cardNumber = cardNumber;
this.cardHolderName = cardHolderName;
this.expiryDate = expiryDate;
this.encryptedPIN = encryptedPIN;
this.cardType = cardType;
this.accountNumber = accountNumber;
}
public boolean isExpired() {
return LocalDate.now().isAfter(expiryDate);
}
public boolean validatePIN(String pin) {
// In real implementation, compare encrypted values
return encryptedPIN.equals(pin);
}
public String getCardNumber() { return cardNumber; }
public String getAccountNumber() { return accountNumber; }
public CardType getCardType() { return cardType; }
}
abstract class Account {
protected String accountNumber;
protected String accountHolderName;
protected Money availableBalance;
protected Money totalBalance;
protected Money dailyWithdrawalLimit;
protected Money todayWithdrawn;
protected LocalDate lastWithdrawalDate;
public Account(String accountNumber, String accountHolderName) {
this.accountNumber = accountNumber;
this.accountHolderName = accountHolderName;
this.availableBalance = new Money(0);
this.totalBalance = new Money(0);
this.dailyWithdrawalLimit = new Money(1000); // $1000 default
this.todayWithdrawn = new Money(0);
this.lastWithdrawalDate = LocalDate.now();
}
public boolean canWithdraw(Money amount) {
// Reset daily limit if new day
if (!lastWithdrawalDate.equals(LocalDate.now())) {
todayWithdrawn = new Money(0);
lastWithdrawalDate = LocalDate.now();
}
Money newTotal = todayWithdrawn.add(amount);
return availableBalance.isGreaterThanOrEqual(amount) &&
dailyWithdrawalLimit.isGreaterThanOrEqual(newTotal);
}
public boolean withdraw(Money amount) {
if (!canWithdraw(amount)) {
return false;
}
availableBalance = availableBalance.subtract(amount);
totalBalance = totalBalance.subtract(amount);
todayWithdrawn = todayWithdrawn.add(amount);
return true;
}
public void deposit(Money amount) {
availableBalance = availableBalance.add(amount);
totalBalance = totalBalance.add(amount);
}
public abstract Money getAvailableBalance();
public abstract Money getTotalBalance();
public String getAccountNumber() { return accountNumber; }
public String getAccountHolderName() { return accountHolderName; }
}
class CheckingAccount extends Account {
private Money overdraftLimit;
private boolean hasDebitCard;
public CheckingAccount(String accountNumber, String accountHolderName, Money overdraftLimit) {
super(accountNumber, accountHolderName);
this.overdraftLimit = overdraftLimit;
this.hasDebitCard = true;
}
@Override
public Money getAvailableBalance() {
return availableBalance.add(overdraftLimit);
}
@Override
public Money getTotalBalance() {
return totalBalance;
}
}
class SavingsAccount extends Account {
private double interestRate;
private int withdrawalsThisMonth;
private static final int MAX_WITHDRAWALS_PER_MONTH = 6;
public SavingsAccount(String accountNumber, String accountHolderName, double interestRate) {
super(accountNumber, accountHolderName);
this.interestRate = interestRate;
this.withdrawalsThisMonth = 0;
}
@Override
public boolean canWithdraw(Money amount) {
return super.canWithdraw(amount) && withdrawalsThisMonth < MAX_WITHDRAWALS_PER_MONTH;
}
@Override
public boolean withdraw(Money amount) {
if (super.withdraw(amount)) {
withdrawalsThisMonth++;
return true;
}
return false;
}
@Override
public Money getAvailableBalance() {
return availableBalance;
}
@Override
public Money getTotalBalance() {
return totalBalance;
}
public double getInterestRate() { return interestRate; }
}
class Customer {
private String customerId;
private String name;
private String email;
private String phoneNumber;
private List<Account> accounts;
private CustomerStatus status;
public Customer(String customerId, String name, String email, String phoneNumber) {
this.customerId = customerId;
this.name = name;
this.email = email;
this.phoneNumber = phoneNumber;
this.accounts = new ArrayList<>();
this.status = CustomerStatus.ACTIVE;
}
public void addAccount(Account account) {
accounts.add(account);
}
public List<Account> getAccounts() { return accounts; }
}
// ============================================================================
// Transactions (Strategy Pattern with Template Method)
// ============================================================================
abstract class Transaction {
protected String transactionId;
protected LocalDateTime timestamp;
protected Account account;
protected TransactionStatus status;
protected Money amount;
public Transaction(Account account) {
this.transactionId = UUID.randomUUID().toString();
this.timestamp = LocalDateTime.now();
this.account = account;
this.status = TransactionStatus.PENDING;
}
// Template method
public final TransactionResult execute() {
if (!validate()) {
return new TransactionResult(false, "Validation failed", null, null);
}
status = TransactionStatus.IN_PROGRESS;
TransactionResult result = performTransaction();
if (result.isSuccess()) {
status = TransactionStatus.COMPLETED;
} else {
status = TransactionStatus.FAILED;
}
logTransaction();
return result;
}
protected abstract TransactionResult performTransaction();
public abstract boolean validate();
protected void logTransaction() {
// Log to audit trail
System.out.println("Transaction logged: " + transactionId);
}
public TransactionReceipt createReceipt(Money balanceAfter) {
return new TransactionReceipt(transactionId, timestamp, getTransactionType(),
amount, balanceAfter, "ATM Location");
}
protected abstract TransactionType getTransactionType();
@Override
public String toString() {
return String.format("Transaction[id=%s, type=%s, amount=%s, status=%s]",
transactionId, getTransactionType(), amount, status);
}
}
class BalanceInquiry extends Transaction {
public BalanceInquiry(Account account) {
super(account);
this.amount = new Money(0);
}
@Override
protected TransactionResult performTransaction() {
Money available = account.getAvailableBalance();
Money total = account.getTotalBalance();
String message = String.format("Available: %s, Total: %s", available, total);
TransactionReceipt receipt = createReceipt(available);
return new TransactionResult(true, message, available, receipt);
}
@Override
public boolean validate() {
return account != null;
}
@Override
protected TransactionType getTransactionType() {
return TransactionType.BALANCE_INQUIRY;
}
}
class Withdrawal extends Transaction {
private Money requestedAmount;
private CashDispenser cashDispenser;
private Map<CashDenomination, Integer> dispensedBills;
public Withdrawal(Account account, CashDispenser cashDispenser) {
super(account);
this.cashDispenser = cashDispenser;
}
public void setAmount(Money amount) {
this.requestedAmount = amount;
this.amount = amount;
}
@Override
protected TransactionResult performTransaction() {
// Check if ATM has enough cash
if (!cashDispenser.canDispense(requestedAmount)) {
return new TransactionResult(false, "Insufficient cash in ATM", null, null);
}
// Debit account
if (!account.withdraw(requestedAmount)) {
return new TransactionResult(false, "Insufficient funds or limit exceeded", null, null);
}
// Dispense cash
DispenseResult dispenseResult = cashDispenser.dispenseCash(requestedAmount);
if (!dispenseResult.isSuccess()) {
// Rollback account debit
account.deposit(requestedAmount);
return new TransactionResult(false, "Failed to dispense cash", null, null);
}
this.dispensedBills = dispenseResult.getDispensedBills();
Money balanceAfter = account.getAvailableBalance();
TransactionReceipt receipt = createReceipt(balanceAfter);
return new TransactionResult(true, "Cash dispensed successfully", balanceAfter, receipt);
}
@Override
public boolean validate() {
return account != null &&
requestedAmount != null &&
requestedAmount.isPositive() &&
account.canWithdraw(requestedAmount);
}
@Override
protected TransactionType getTransactionType() {
return TransactionType.WITHDRAWAL;
}
}
class CashDeposit extends Transaction {
private CashAcceptor cashAcceptor;
private Money totalAmount;
public CashDeposit(Account account, CashAcceptor cashAcceptor) {
super(account);
this.cashAcceptor = cashAcceptor;
}
@Override
protected TransactionResult performTransaction() {
totalAmount = cashAcceptor.getTotalAccepted();
if (!totalAmount.isPositive()) {
return new TransactionResult(false, "No cash deposited", null, null);
}
account.deposit(totalAmount);
this.amount = totalAmount;
Money balanceAfter = account.getAvailableBalance();
TransactionReceipt receipt = createReceipt(balanceAfter);
cashAcceptor.clear();
return new TransactionResult(true, "Cash deposited successfully", balanceAfter, receipt);
}
@Override
public boolean validate() {
return account != null && cashAcceptor != null;
}
@Override
protected TransactionType getTransactionType() {
return TransactionType.CASH_DEPOSIT;
}
}
class CheckDeposit extends Transaction {
private Check check;
private Money checkAmount;
public CheckDeposit(Account account, Check check) {
super(account);
this.check = check;
this.checkAmount = check.getAmount();
this.amount = checkAmount;
}
@Override
protected TransactionResult performTransaction() {
// Check deposits are typically held for clearing
// Credit account with pending status
account.deposit(checkAmount);
Money balanceAfter = account.getAvailableBalance();
TransactionReceipt receipt = createReceipt(balanceAfter);
return new TransactionResult(true, "Check deposited (pending clearance)", balanceAfter, receipt);
}
@Override
public boolean validate() {
return account != null && check != null && checkAmount.isPositive();
}
@Override
protected TransactionType getTransactionType() {
return TransactionType.CHECK_DEPOSIT;
}
}
class Transfer extends Transaction {
private Account sourceAccount;
private Account destinationAccount;
private Money transferAmount;
private TransferType transferType;
public Transfer(Account sourceAccount) {
super(sourceAccount);
this.sourceAccount = sourceAccount;
}
public void setDestinationAccount(Account dest) {
this.destinationAccount = dest;
}
public void setTransferAmount(Money amount) {
this.transferAmount = amount;
this.amount = amount;
}
public void setTransferType(TransferType type) {
this.transferType = type;
}
@Override
protected TransactionResult performTransaction() {
// Debit source account
if (!sourceAccount.withdraw(transferAmount)) {
return new TransactionResult(false, "Insufficient funds in source account", null, null);
}
// Credit destination account
try {
destinationAccount.deposit(transferAmount);
} catch (Exception e) {
// Rollback source debit
sourceAccount.deposit(transferAmount);
return new TransactionResult(false, "Failed to credit destination account", null, null);
}
Money balanceAfter = sourceAccount.getAvailableBalance();
TransactionReceipt receipt = createReceipt(balanceAfter);
return new TransactionResult(true, "Transfer completed successfully", balanceAfter, receipt);
}
@Override
public boolean validate() {
return sourceAccount != null &&
destinationAccount != null &&
transferAmount != null &&
transferAmount.isPositive() &&
sourceAccount.canWithdraw(transferAmount);
}
@Override
protected TransactionType getTransactionType() {
return TransactionType.TRANSFER;
}
}
class TransactionResult {
private boolean success;
private String message;
private Money balanceAfter;
private TransactionReceipt receipt;
public TransactionResult(boolean success, String message, Money balanceAfter, TransactionReceipt receipt) {
this.success = success;
this.message = message;
this.balanceAfter = balanceAfter;
this.receipt = receipt;
}
public boolean isSuccess() { return success; }
public String getMessage() { return message; }
public Money getBalanceAfter() { return balanceAfter; }
public TransactionReceipt getReceipt() { return receipt; }
}
class TransactionReceipt {
private String transactionId;
private LocalDateTime timestamp;
private TransactionType type;
private Money amount;
private Money balanceAfter;
private String atmLocation;
public TransactionReceipt(String transactionId, LocalDateTime timestamp, TransactionType type,
Money amount, Money balanceAfter, String atmLocation) {
this.transactionId = transactionId;
this.timestamp = timestamp;
this.type = type;
this.amount = amount;
this.balanceAfter = balanceAfter;
this.atmLocation = atmLocation;
}
public String format() {
StringBuilder sb = new StringBuilder();
sb.append("======= TRANSACTION RECEIPT =======\n");
sb.append("Transaction ID: ").append(transactionId).append("\n");
sb.append("Date/Time: ").append(timestamp).append("\n");
sb.append("Type: ").append(type).append("\n");
sb.append("Amount: ").append(amount).append("\n");
sb.append("Balance After: ").append(balanceAfter).append("\n");
sb.append("Location: ").append(atmLocation).append("\n");
sb.append("===================================\n");
return sb.toString();
}
}
// ============================================================================
// Supporting Classes
// ============================================================================
class Check {
private String checkNumber;
private String routingNumber;
private String accountNumber;
private Money amount;
private LocalDate date;
public Check(String checkNumber, String routingNumber, String accountNumber, Money amount, LocalDate date) {
this.checkNumber = checkNumber;
this.routingNumber = routingNumber;
this.accountNumber = accountNumber;
this.amount = amount;
this.date = date;
}
public Money getAmount() { return amount; }
public String getCheckNumber() { return checkNumber; }
}
class AuditLog {
private String atmId;
private List<LogEntry> entries;
public AuditLog(String atmId) {
this.atmId = atmId;
this.entries = new ArrayList<>();
}
public void log(String eventType, String details) {
LogEntry entry = new LogEntry(UUID.randomUUID().toString(), LocalDateTime.now(),
atmId, eventType, details);
entries.add(entry);
System.out.println("Audit Log: " + eventType + " - " + details);
}
private static class LogEntry {
String logId;
LocalDateTime timestamp;
String atmId;
String eventType;
String details;
LogEntry(String logId, LocalDateTime timestamp, String atmId, String eventType, String details) {
this.logId = logId;
this.timestamp = timestamp;
this.atmId = atmId;
this.eventType = eventType;
this.details = details;
}
}
}
Python
from abc import ABC, abstractmethod
from dataclasses import dataclass
from datetime import datetime, date, timedelta
from decimal import Decimal
from enum import Enum
from typing import List, Optional, Dict
from uuid import uuid4
# ============================================================================
# Enums
# ============================================================================
class TransactionType(Enum):
BALANCE_INQUIRY = "balance_inquiry"
WITHDRAWAL = "withdrawal"
CASH_DEPOSIT = "cash_deposit"
CHECK_DEPOSIT = "check_deposit"
TRANSFER = "transfer"
class TransactionStatus(Enum):
PENDING = "pending"
IN_PROGRESS = "in_progress"
COMPLETED = "completed"
FAILED = "failed"
CANCELLED = "cancelled"
ROLLED_BACK = "rolled_back"
class CardType(Enum):
DEBIT = "debit"
CREDIT = "credit"
ATM = "atm"
class CustomerStatus(Enum):
ACTIVE = "active"
BLOCKED = "blocked"
SUSPENDED = "suspended"
CLOSED = "closed"
class CardReaderStatus(Enum):
IDLE = "idle"
CARD_INSERTED = "card_inserted"
READING = "reading"
ERROR = "error"
class CashDenomination(Enum):
TWENTY = 20
FIFTY = 50
HUNDRED = 100
@property
def value_amount(self) -> int:
return self.value
class TransferType(Enum):
CHECKING_TO_SAVINGS = "checking_to_savings"
SAVINGS_TO_CHECKING = "savings_to_checking"
TO_EXTERNAL_ACCOUNT = "to_external_account"
# ============================================================================
# Value Objects
# ============================================================================
@dataclass(frozen=True)
class Money:
"""Immutable money value object"""
amount: Decimal
currency: str = "USD"
def __post_init__(self):
if self.amount < 0:
raise ValueError("Amount cannot be negative")
def add(self, other: 'Money') -> 'Money':
self._validate_currency(other)
return Money(self.amount + other.amount, self.currency)
def subtract(self, other: 'Money') -> 'Money':
self._validate_currency(other)
return Money(self.amount - other.amount, self.currency)
def multiply(self, factor: int) -> 'Money':
return Money(self.amount * factor, self.currency)
def is_greater_than(self, other: 'Money') -> bool:
self._validate_currency(other)
return self.amount > other.amount
def is_greater_than_or_equal(self, other: 'Money') -> bool:
self._validate_currency(other)
return self.amount >= other.amount
def is_positive(self) -> bool:
return self.amount > 0
def _validate_currency(self, other: 'Money') -> None:
if self.currency != other.currency:
raise ValueError("Currency mismatch")
def __str__(self) -> str:
return f"{self.currency} {self.amount:.2f}"
# ============================================================================
# ATM State Pattern
# ============================================================================
class ATMState(ABC):
@abstractmethod
def insert_card(self, atm: 'ATM', card: 'Card') -> None:
pass
@abstractmethod
def enter_pin(self, atm: 'ATM', pin: str) -> None:
pass
@abstractmethod
def select_transaction(self, atm: 'ATM', transaction_type: TransactionType) -> None:
pass
@abstractmethod
def eject_card(self, atm: 'ATM') -> None:
pass
class IdleState(ATMState):
def insert_card(self, atm: 'ATM', card: 'Card') -> None:
if card.is_expired():
atm.screen.show_error("Card expired")
atm.card_reader.eject_card()
return
atm.current_card = card
atm.set_state(CardInsertedState())
atm.screen.display_message("Please enter your PIN")
def enter_pin(self, atm: 'ATM', pin: str) -> None:
atm.screen.show_error("Please insert card first")
def select_transaction(self, atm: 'ATM', transaction_type: TransactionType) -> None:
atm.screen.show_error("Please insert card first")
def eject_card(self, atm: 'ATM') -> None:
pass # No card to eject
class CardInsertedState(ATMState):
MAX_PIN_ATTEMPTS = 3
def __init__(self) -> None:
self.pin_attempts = 0
def insert_card(self, atm: 'ATM', card: 'Card') -> None:
atm.screen.show_error("Card already inserted")
def enter_pin(self, atm: 'ATM', pin: str) -> None:
card = atm.current_card
valid = atm.banking_service.authenticate_pin(card, pin)
if valid:
account = atm.banking_service.get_account(card.account_number)
session = Session(card, account)
atm.active_session = session
atm.set_state(AuthenticatedState())
atm.screen.display_message("Authentication successful")
menu_options = [
"1. Balance Inquiry",
"2. Withdraw Cash",
"3. Deposit Cash",
"4. Transfer Funds"
]
atm.screen.display_menu(menu_options)
else:
self.pin_attempts += 1
if self.pin_attempts >= self.MAX_PIN_ATTEMPTS:
atm.card_reader.retain_card()
atm.banking_service.notify_card_retained(card)
atm.screen.show_error("Card retained due to incorrect PIN")
atm.set_state(IdleState())
else:
atm.screen.show_error(f"Incorrect PIN. Attempt {self.pin_attempts} of {self.MAX_PIN_ATTEMPTS}")
def select_transaction(self, atm: 'ATM', transaction_type: TransactionType) -> None:
atm.screen.show_error("Please enter PIN first")
def eject_card(self, atm: 'ATM') -> None:
atm.card_reader.eject_card()
atm.set_state(IdleState())
class AuthenticatedState(ATMState):
def insert_card(self, atm: 'ATM', card: 'Card') -> None:
atm.screen.show_error("Session already active")
def enter_pin(self, atm: 'ATM', pin: str) -> None:
atm.screen.show_error("Already authenticated")
def select_transaction(self, atm: 'ATM', transaction_type: TransactionType) -> None:
session = atm.active_session
if session.is_expired():
atm.screen.show_error("Session expired. Please restart.")
self.eject_card(atm)
return
session.update_activity()
transaction = self._create_transaction(transaction_type, session.account, atm)
atm.set_state(TransactionState(transaction))
atm.execute_transaction(transaction)
def eject_card(self, atm: 'ATM') -> None:
atm.end_session()
atm.card_reader.eject_card()
atm.set_state(IdleState())
atm.screen.display_message("Thank you for using our ATM")
def _create_transaction(self, transaction_type: TransactionType,
account: 'Account', atm: 'ATM') -> 'Transaction':
if transaction_type == TransactionType.BALANCE_INQUIRY:
return BalanceInquiry(account)
elif transaction_type == TransactionType.WITHDRAWAL:
return Withdrawal(account, atm.cash_dispenser)
elif transaction_type == TransactionType.CASH_DEPOSIT:
return CashDeposit(account, atm.cash_acceptor)
elif transaction_type == TransactionType.TRANSFER:
return Transfer(account)
else:
raise ValueError("Unknown transaction type")
class TransactionState(ATMState):
def __init__(self, transaction: 'Transaction') -> None:
self.current_transaction = transaction
def insert_card(self, atm: 'ATM', card: 'Card') -> None:
atm.screen.show_error("Transaction in progress")
def enter_pin(self, atm: 'ATM', pin: str) -> None:
pass # Handle PIN for transaction authorization if needed
def select_transaction(self, atm: 'ATM', transaction_type: TransactionType) -> None:
atm.screen.show_error("Please complete current transaction")
def eject_card(self, atm: 'ATM') -> None:
atm.screen.show_error("Please complete transaction first")
# ============================================================================
# Session Management
# ============================================================================
class Session:
TIMEOUT_SECONDS = 120 # 2 minutes
def __init__(self, card: 'Card', account: 'Account') -> None:
self.session_id = str(uuid4())
self.card = card
self.account = account
self.start_time = datetime.now()
self.last_activity_time = datetime.now()
def is_expired(self) -> bool:
timeout = timedelta(seconds=self.TIMEOUT_SECONDS)
return datetime.now() > self.last_activity_time + timeout
def update_activity(self) -> None:
self.last_activity_time = datetime.now()
# ============================================================================
# ATM Core
# ============================================================================
class ATM:
def __init__(self, atm_id: str, location: str) -> None:
self.atm_id = atm_id
self.location = location
self.current_state: ATMState = IdleState()
self.active_session: Optional[Session] = None
self.current_card: Optional[Card] = None
# Hardware components
self.card_reader = CardReader()
self.pin_pad = PINPad()
self.screen = Screen()
self.cash_dispenser = CashDispenser()
self.cash_acceptor = CashAcceptor()
self.check_scanner = CheckScanner()
self.receipt_printer = ReceiptPrinter()
# Services
self.banking_service: Optional['BankingService'] = None
self.audit_log = AuditLog(atm_id)
def insert_card(self, card: 'Card') -> None:
self.current_state.insert_card(self, card)
def enter_pin(self, pin: str) -> None:
self.current_state.enter_pin(self, pin)
def select_transaction(self, transaction_type: TransactionType) -> None:
self.current_state.select_transaction(self, transaction_type)
def execute_transaction(self, transaction: 'Transaction') -> None:
if not transaction.validate():
self.screen.show_error("Transaction validation failed")
return
result = transaction.execute()
if result.success:
if self.receipt_printer.has_paper():
self.receipt_printer.print_receipt(result.receipt)
self.screen.display_message("Transaction successful")
self.audit_log.log("TRANSACTION_SUCCESS", str(transaction))
else:
self.screen.show_error(f"Transaction failed: {result.message}")
self.audit_log.log("TRANSACTION_FAILED", result.message)
# Return to authenticated state
self.set_state(AuthenticatedState())
# Display menu again
menu_options = [
"1. Balance Inquiry",
"2. Withdraw Cash",
"3. Deposit Cash",
"4. Transfer Funds",
"5. Exit"
]
self.screen.display_menu(menu_options)
def eject_card(self) -> None:
self.current_state.eject_card(self)
def end_session(self) -> None:
self.active_session = None
self.current_card = None
def set_state(self, state: ATMState) -> None:
self.current_state = state
# ============================================================================
# Hardware Components
# ============================================================================
class CardReader:
def __init__(self) -> None:
self.status = CardReaderStatus.IDLE
self.inserted_card: Optional['Card'] = None
def read_card(self) -> Optional['Card']:
self.status = CardReaderStatus.READING
self.status = CardReaderStatus.CARD_INSERTED
return self.inserted_card
def eject_card(self) -> None:
self.inserted_card = None
self.status = CardReaderStatus.IDLE
def retain_card(self) -> None:
self.inserted_card = None
self.status = CardReaderStatus.IDLE
def is_card_present(self) -> bool:
return self.inserted_card is not None
class PINPad:
def get_encrypted_pin(self) -> str:
# Simulate secure PIN entry
return "encrypted_pin"
def clear(self) -> None:
pass
class Screen:
def __init__(self) -> None:
self.current_message = ""
def display_message(self, message: str) -> None:
self.current_message = message
print(f"Screen: {message}")
def display_menu(self, options: List[str]) -> None:
print("Screen Menu:")
for option in options:
print(f" {option}")
def show_balance(self, available: Money, total: Money) -> None:
print(f"Available Balance: {available}")
print(f"Total Balance: {total}")
def show_error(self, error: str) -> None:
print(f"ERROR: {error}")
def clear(self) -> None:
self.current_message = ""
class CashDispenser:
def __init__(self) -> None:
self.inventory: Dict[CashDenomination, int] = {
CashDenomination.TWENTY: 100,
CashDenomination.FIFTY: 50,
CashDenomination.HUNDRED: 30
}
# Setup chain of responsibility
hundred = HundredDollarDispenser()
fifty = FiftyDollarDispenser()
twenty = TwentyDollarDispenser()
hundred.set_next(fifty)
fifty.set_next(twenty)
self.dispenser_chain = hundred
def dispense_cash(self, amount: Money) -> 'DispenseResult':
if not self.can_dispense(amount):
return DispenseResult(False, "Insufficient cash in ATM", {})
result = self.dispenser_chain.dispense(amount, self.inventory)
if result.success:
# Update inventory
for denomination, count in result.dispensed_bills.items():
self.inventory[denomination] -= count
return result
def can_dispense(self, amount: Money) -> bool:
total_available = sum(
denom.value * count
for denom, count in self.inventory.items()
)
return total_available >= int(amount.amount)
def refill(self, bills: Dict[CashDenomination, int]) -> None:
for denomination, count in bills.items():
self.inventory[denomination] = self.inventory.get(denomination, 0) + count
def is_low_on_cash(self) -> bool:
total = sum(self.inventory.values())
return total < 50
class CashAcceptor:
def __init__(self) -> None:
self.accepted_bills: List[Money] = []
def accept_bill(self, bill: Money) -> None:
self.accepted_bills.append(bill)
def get_total_accepted(self) -> Money:
total = Money(Decimal('0'))
for bill in self.accepted_bills:
total = total.add(bill)
return total
def clear(self) -> None:
self.accepted_bills.clear()
class CheckScanner:
def scan_check(self) -> 'Check':
# Simulate check scanning
return Check("123456", "987654321", "1234567890",
Money(Decimal('100')), date.today())
def validate_check(self, check: 'Check') -> bool:
return check.amount.is_positive()
class ReceiptPrinter:
def __init__(self) -> None:
self.paper_level = 100
def print_receipt(self, receipt: 'TransactionReceipt') -> None:
if self.paper_level > 0:
print("Printing receipt:")
print(receipt.format())
self.paper_level -= 1
def has_paper(self) -> bool:
return self.paper_level > 0
def refill_paper(self) -> None:
self.paper_level = 100
# ============================================================================
# Chain of Responsibility for Denomination Dispensing
# ============================================================================
class DenominationDispenser(ABC):
def __init__(self) -> None:
self.next_dispenser: Optional['DenominationDispenser'] = None
def set_next(self, next_dispenser: 'DenominationDispenser') -> None:
self.next_dispenser = next_dispenser
@abstractmethod
def dispense(self, amount: Money, inventory: Dict[CashDenomination, int]) -> 'DispenseResult':
pass
class HundredDollarDispenser(DenominationDispenser):
def dispense(self, amount: Money, inventory: Dict[CashDenomination, int]) -> 'DispenseResult':
remaining = int(amount.amount)
available = inventory.get(CashDenomination.HUNDRED, 0)
needed = remaining // 100
to_dispense = min(needed, available)
dispensed = {}
if to_dispense > 0:
dispensed[CashDenomination.HUNDRED] = to_dispense
remaining -= to_dispense * 100
if remaining > 0 and self.next_dispenser:
next_result = self.next_dispenser.dispense(Money(Decimal(remaining)), inventory)
if next_result.success:
dispensed.update(next_result.dispensed_bills)
return DispenseResult(True, "Cash dispensed", dispensed)
if remaining == 0:
return DispenseResult(True, "Cash dispensed", dispensed)
return DispenseResult(False, "Cannot dispense exact amount", {})
class FiftyDollarDispenser(DenominationDispenser):
def dispense(self, amount: Money, inventory: Dict[CashDenomination, int]) -> 'DispenseResult':
remaining = int(amount.amount)
available = inventory.get(CashDenomination.FIFTY, 0)
needed = remaining // 50
to_dispense = min(needed, available)
dispensed = {}
if to_dispense > 0:
dispensed[CashDenomination.FIFTY] = to_dispense
remaining -= to_dispense * 50
if remaining > 0 and self.next_dispenser:
next_result = self.next_dispenser.dispense(Money(Decimal(remaining)), inventory)
if next_result.success:
dispensed.update(next_result.dispensed_bills)
return DispenseResult(True, "Cash dispensed", dispensed)
if remaining == 0:
return DispenseResult(True, "Cash dispensed", dispensed)
return DispenseResult(False, "Cannot dispense exact amount", {})
class TwentyDollarDispenser(DenominationDispenser):
def dispense(self, amount: Money, inventory: Dict[CashDenomination, int]) -> 'DispenseResult':
remaining = int(amount.amount)
available = inventory.get(CashDenomination.TWENTY, 0)
needed = remaining // 20
to_dispense = min(needed, available)
dispensed = {}
if to_dispense > 0:
dispensed[CashDenomination.TWENTY] = to_dispense
remaining -= to_dispense * 20
if remaining == 0:
return DispenseResult(True, "Cash dispensed", dispensed)
return DispenseResult(False, "Cannot dispense exact amount", {})
@dataclass
class DispenseResult:
success: bool
message: str
dispensed_bills: Dict[CashDenomination, int]
# ============================================================================
# Banking Domain
# ============================================================================
class BankingService(ABC):
@abstractmethod
def validate_card(self, card: 'Card') -> bool:
pass
@abstractmethod
def authenticate_pin(self, card: 'Card', pin: str) -> bool:
pass
@abstractmethod
def get_account(self, account_number: str) -> 'Account':
pass
@abstractmethod
def process_transaction(self, transaction: 'Transaction') -> 'TransactionResult':
pass
@abstractmethod
def notify_card_retained(self, card: 'Card') -> None:
pass
class Card:
def __init__(self, card_number: str, card_holder_name: str, expiry_date: date,
encrypted_pin: str, card_type: CardType, account_number: str) -> None:
self.card_number = card_number
self.card_holder_name = card_holder_name
self.expiry_date = expiry_date
self.encrypted_pin = encrypted_pin
self.card_type = card_type
self.account_number = account_number
def is_expired(self) -> bool:
return date.today() > self.expiry_date
def validate_pin(self, pin: str) -> bool:
return self.encrypted_pin == pin
class Account(ABC):
def __init__(self, account_number: str, account_holder_name: str) -> None:
self.account_number = account_number
self.account_holder_name = account_holder_name
self.available_balance = Money(Decimal('0'))
self.total_balance = Money(Decimal('0'))
self.daily_withdrawal_limit = Money(Decimal('1000'))
self.today_withdrawn = Money(Decimal('0'))
self.last_withdrawal_date = date.today()
def can_withdraw(self, amount: Money) -> bool:
# Reset daily limit if new day
if self.last_withdrawal_date != date.today():
self.today_withdrawn = Money(Decimal('0'))
self.last_withdrawal_date = date.today()
new_total = self.today_withdrawn.add(amount)
return (self.available_balance.is_greater_than_or_equal(amount) and
self.daily_withdrawal_limit.is_greater_than_or_equal(new_total))
def withdraw(self, amount: Money) -> bool:
if not self.can_withdraw(amount):
return False
self.available_balance = self.available_balance.subtract(amount)
self.total_balance = self.total_balance.subtract(amount)
self.today_withdrawn = self.today_withdrawn.add(amount)
return True
def deposit(self, amount: Money) -> None:
self.available_balance = self.available_balance.add(amount)
self.total_balance = self.total_balance.add(amount)
@abstractmethod
def get_available_balance(self) -> Money:
pass
@abstractmethod
def get_total_balance(self) -> Money:
pass
class CheckingAccount(Account):
def __init__(self, account_number: str, account_holder_name: str,
overdraft_limit: Money) -> None:
super().__init__(account_number, account_holder_name)
self.overdraft_limit = overdraft_limit
self.has_debit_card = True
def get_available_balance(self) -> Money:
return self.available_balance.add(self.overdraft_limit)
def get_total_balance(self) -> Money:
return self.total_balance
class SavingsAccount(Account):
MAX_WITHDRAWALS_PER_MONTH = 6
def __init__(self, account_number: str, account_holder_name: str,
interest_rate: float) -> None:
super().__init__(account_number, account_holder_name)
self.interest_rate = interest_rate
self.withdrawals_this_month = 0
def can_withdraw(self, amount: Money) -> bool:
return (super().can_withdraw(amount) and
self.withdrawals_this_month < self.MAX_WITHDRAWALS_PER_MONTH)
def withdraw(self, amount: Money) -> bool:
if super().withdraw(amount):
self.withdrawals_this_month += 1
return True
return False
def get_available_balance(self) -> Money:
return self.available_balance
def get_total_balance(self) -> Money:
return self.total_balance
class Customer:
def __init__(self, customer_id: str, name: str, email: str, phone_number: str) -> None:
self.customer_id = customer_id
self.name = name
self.email = email
self.phone_number = phone_number
self.accounts: List[Account] = []
self.status = CustomerStatus.ACTIVE
def add_account(self, account: Account) -> None:
self.accounts.append(account)
# ============================================================================
# Transactions (Strategy Pattern with Template Method)
# ============================================================================
class Transaction(ABC):
def __init__(self, account: Account) -> None:
self.transaction_id = str(uuid4())
self.timestamp = datetime.now()
self.account = account
self.status = TransactionStatus.PENDING
self.amount = Money(Decimal('0'))
def execute(self) -> 'TransactionResult':
"""Template method"""
if not self.validate():
return TransactionResult(False, "Validation failed", None, None)
self.status = TransactionStatus.IN_PROGRESS
result = self.perform_transaction()
if result.success:
self.status = TransactionStatus.COMPLETED
else:
self.status = TransactionStatus.FAILED
self.log_transaction()
return result
@abstractmethod
def perform_transaction(self) -> 'TransactionResult':
pass
@abstractmethod
def validate(self) -> bool:
pass
def log_transaction(self) -> None:
print(f"Transaction logged: {self.transaction_id}")
def create_receipt(self, balance_after: Money) -> 'TransactionReceipt':
return TransactionReceipt(
self.transaction_id,
self.timestamp,
self.get_transaction_type(),
self.amount,
balance_after,
"ATM Location"
)
@abstractmethod
def get_transaction_type(self) -> TransactionType:
pass
def __str__(self) -> str:
return f"Transaction[id={self.transaction_id}, type={self.get_transaction_type()}, " \
f"amount={self.amount}, status={self.status}]"
class BalanceInquiry(Transaction):
def __init__(self, account: Account) -> None:
super().__init__(account)
self.amount = Money(Decimal('0'))
def perform_transaction(self) -> 'TransactionResult':
available = self.account.get_available_balance()
total = self.account.get_total_balance()
message = f"Available: {available}, Total: {total}"
receipt = self.create_receipt(available)
return TransactionResult(True, message, available, receipt)
def validate(self) -> bool:
return self.account is not None
def get_transaction_type(self) -> TransactionType:
return TransactionType.BALANCE_INQUIRY
class Withdrawal(Transaction):
def __init__(self, account: Account, cash_dispenser: CashDispenser) -> None:
super().__init__(account)
self.cash_dispenser = cash_dispenser
self.requested_amount: Optional[Money] = None
self.dispensed_bills: Dict[CashDenomination, int] = {}
def set_amount(self, amount: Money) -> None:
self.requested_amount = amount
self.amount = amount
def perform_transaction(self) -> 'TransactionResult':
# Check if ATM has enough cash
if not self.cash_dispenser.can_dispense(self.requested_amount):
return TransactionResult(False, "Insufficient cash in ATM", None, None)
# Debit account
if not self.account.withdraw(self.requested_amount):
return TransactionResult(False, "Insufficient funds or limit exceeded", None, None)
# Dispense cash
dispense_result = self.cash_dispenser.dispense_cash(self.requested_amount)
if not dispense_result.success:
# Rollback account debit
self.account.deposit(self.requested_amount)
return TransactionResult(False, "Failed to dispense cash", None, None)
self.dispensed_bills = dispense_result.dispensed_bills
balance_after = self.account.get_available_balance()
receipt = self.create_receipt(balance_after)
return TransactionResult(True, "Cash dispensed successfully", balance_after, receipt)
def validate(self) -> bool:
return (self.account is not None and
self.requested_amount is not None and
self.requested_amount.is_positive() and
self.account.can_withdraw(self.requested_amount))
def get_transaction_type(self) -> TransactionType:
return TransactionType.WITHDRAWAL
class CashDeposit(Transaction):
def __init__(self, account: Account, cash_acceptor: CashAcceptor) -> None:
super().__init__(account)
self.cash_acceptor = cash_acceptor
self.total_amount: Optional[Money] = None
def perform_transaction(self) -> 'TransactionResult':
self.total_amount = self.cash_acceptor.get_total_accepted()
if not self.total_amount.is_positive():
return TransactionResult(False, "No cash deposited", None, None)
self.account.deposit(self.total_amount)
self.amount = self.total_amount
balance_after = self.account.get_available_balance()
receipt = self.create_receipt(balance_after)
self.cash_acceptor.clear()
return TransactionResult(True, "Cash deposited successfully", balance_after, receipt)
def validate(self) -> bool:
return self.account is not None and self.cash_acceptor is not None
def get_transaction_type(self) -> TransactionType:
return TransactionType.CASH_DEPOSIT
class CheckDeposit(Transaction):
def __init__(self, account: Account, check: 'Check') -> None:
super().__init__(account)
self.check = check
self.check_amount = check.amount
self.amount = self.check_amount
def perform_transaction(self) -> 'TransactionResult':
# Check deposits are typically held for clearing
self.account.deposit(self.check_amount)
balance_after = self.account.get_available_balance()
receipt = self.create_receipt(balance_after)
return TransactionResult(True, "Check deposited (pending clearance)", balance_after, receipt)
def validate(self) -> bool:
return (self.account is not None and
self.check is not None and
self.check_amount.is_positive())
def get_transaction_type(self) -> TransactionType:
return TransactionType.CHECK_DEPOSIT
class Transfer(Transaction):
def __init__(self, source_account: Account) -> None:
super().__init__(source_account)
self.source_account = source_account
self.destination_account: Optional[Account] = None
self.transfer_amount: Optional[Money] = None
self.transfer_type: Optional[TransferType] = None
def set_destination_account(self, dest: Account) -> None:
self.destination_account = dest
def set_transfer_amount(self, amount: Money) -> None:
self.transfer_amount = amount
self.amount = amount
def set_transfer_type(self, transfer_type: TransferType) -> None:
self.transfer_type = transfer_type
def perform_transaction(self) -> 'TransactionResult':
# Debit source account
if not self.source_account.withdraw(self.transfer_amount):
return TransactionResult(False, "Insufficient funds in source account", None, None)
# Credit destination account
try:
self.destination_account.deposit(self.transfer_amount)
except Exception as e:
# Rollback source debit
self.source_account.deposit(self.transfer_amount)
return TransactionResult(False, "Failed to credit destination account", None, None)
balance_after = self.source_account.get_available_balance()
receipt = self.create_receipt(balance_after)
return TransactionResult(True, "Transfer completed successfully", balance_after, receipt)
def validate(self) -> bool:
return (self.source_account is not None and
self.destination_account is not None and
self.transfer_amount is not None and
self.transfer_amount.is_positive() and
self.source_account.can_withdraw(self.transfer_amount))
def get_transaction_type(self) -> TransactionType:
return TransactionType.TRANSFER
@dataclass
class TransactionResult:
success: bool
message: str
balance_after: Optional[Money]
receipt: Optional['TransactionReceipt']
class TransactionReceipt:
def __init__(self, transaction_id: str, timestamp: datetime,
transaction_type: TransactionType, amount: Money,
balance_after: Money, atm_location: str) -> None:
self.transaction_id = transaction_id
self.timestamp = timestamp
self.type = transaction_type
self.amount = amount
self.balance_after = balance_after
self.atm_location = atm_location
def format(self) -> str:
lines = [
"======= TRANSACTION RECEIPT =======",
f"Transaction ID: {self.transaction_id}",
f"Date/Time: {self.timestamp}",
f"Type: {self.type.value}",
f"Amount: {self.amount}",
f"Balance After: {self.balance_after}",
f"Location: {self.atm_location}",
"==================================="
]
return "\n".join(lines)
# ============================================================================
# Supporting Classes
# ============================================================================
@dataclass
class Check:
check_number: str
routing_number: str
account_number: str
amount: Money
date: date
class AuditLog:
def __init__(self, atm_id: str) -> None:
self.atm_id = atm_id
self.entries: List['LogEntry'] = []
def log(self, event_type: str, details: str) -> None:
entry = LogEntry(str(uuid4()), datetime.now(), self.atm_id, event_type, details)
self.entries.append(entry)
print(f"Audit Log: {event_type} - {details}")
@dataclass
class LogEntry:
log_id: str
timestamp: datetime
atm_id: str
event_type: str
details: str
This implementation demonstrates:
- State Pattern for ATM operational states (Idle, CardInserted, Authenticated, Transaction)
- Strategy Pattern for different transaction types with polymorphism
- Template Method Pattern for transaction execution flow
- Chain of Responsibility for optimal cash denomination dispensing
- Value Object Pattern for Money with immutability and currency safety
- Session management with timeout handling for security
- SOLID Principles applied throughout
- Complete hardware abstraction for all ATM components
- Comprehensive error handling and rollback mechanisms
- Audit logging for all transactions and events
- Security features including PIN validation, card retention, and session timeouts
- Multi-account support with different rules for Checking and Savings
- Transaction atomicity with proper rollback on failures