Skip to content

ATM System

Design an ATM system that allows users to perform banking operations like cash withdrawal, deposit, balance inquiry, and fund transfers. The system should authenticate users, communicate with the bank’s backend, handle cash dispensing, and maintain transaction logs.

  1. Authenticate users with card and PIN
  2. Check account balance
  3. Withdraw cash with denomination selection
  4. Deposit cash and checks
  5. Transfer funds between accounts
  6. Print transaction receipts
  7. Handle multiple account types (Savings, Checking)
  8. Change PIN
  9. Maintain transaction history
  10. Handle insufficient funds and daily limits
  1. Secure PIN handling and encryption
  2. Handle concurrent user sessions
  3. Reliable cash dispensing mechanism
  4. Transaction rollback on failures
  5. Audit logging for all operations
  6. Network resilience for bank communication
PlantUML Diagram
PlantUML Diagram
PlantUML Diagram
  1. State Pattern: ATM states (Idle, CardInserted, Authenticated)
  2. Strategy Pattern: Different transaction types
  3. Singleton Pattern: ATM instance management
  4. Factory Pattern: Create different transaction types
  5. Proxy Pattern: BankService as proxy to backend
PlantUML Diagram
PlantUML Diagram
PlantUML Diagram
ATM.java
public class ATM {
public void withdrawCash(Account account, double amount) throws ATMException {
// Validate amount
if (amount <= 0 || amount % 10 != 0) {
throw new ATMException("Invalid amount. Must be multiple of 10");
}
// Check ATM has sufficient cash
if (!cashDispenser.canDispense(amount)) {
throw new ATMException("Insufficient cash in ATM");
}
// Check account balance and limits
if (!account.canWithdraw(amount)) {
throw new ATMException("Insufficient funds or daily limit exceeded");
}
// Create transaction
WithdrawalTransaction transaction = new WithdrawalTransaction(account, amount);
try {
// Process with bank
if (bankService.processTransaction(transaction)) {
// Dispense cash
Map<Integer, Integer> denominations = cashDispenser.dispenseCash(amount);
cashSlot.dispenseCash(denominations);
// Update account
account.debit(amount);
transaction.setStatus(TransactionStatus.SUCCESS);
// Print receipt
printer.printReceipt(transaction);
currentSession.addTransaction(transaction);
screen.displayMessage("Please collect your cash");
} else {
throw new ATMException("Transaction failed");
}
} catch (Exception e) {
transaction.rollback();
transaction.setStatus(TransactionStatus.FAILED);
throw new ATMException("Transaction failed: " + e.getMessage());
}
}
}
CashDispenser.java
public class CashDispenser {
private Map<Integer, Integer> denominations; // denomination -> count
public Map<Integer, Integer> dispenseCash(double amount) throws ATMException {
Map<Integer, Integer> result = calculateDenominations(amount);
if (result == null) {
throw new ATMException("Cannot dispense exact amount");
}
// Deduct from inventory
for (Map.Entry<Integer, Integer> entry : result.entrySet()) {
int denom = entry.getKey();
int count = entry.getValue();
denominations.put(denom, denominations.get(denom) - count);
}
totalCash -= amount;
return result;
}
private Map<Integer, Integer> calculateDenominations(double amount) {
Map<Integer, Integer> result = new HashMap<>();
int remaining = (int) amount;
// Try to dispense using available denominations (100, 50, 20, 10)
int[] denoms = {100, 50, 20, 10};
for (int denom : denoms) {
if (remaining >= denom && denominations.get(denom) > 0) {
int count = Math.min(remaining / denom, denominations.get(denom));
if (count > 0) {
result.put(denom, count);
remaining -= denom * count;
}
}
}
return remaining == 0 ? result : null;
}
}
public class ATM {
public Customer authenticateUser() throws ATMException {
screen.displayMessage("Please insert your card");
Card card = cardReader.readCard();
if (card == null) {
throw new ATMException("Card read error");
}
if (card.isExpired()) {
cardReader.ejectCard();
throw new ATMException("Card has expired");
}
if (card.isBlocked()) {
cardReader.ejectCard();
throw new ATMException("Card is blocked");
}
// Get PIN
screen.displayMessage("Please enter your PIN");
String pin = keypad.getPIN();
if (!card.validatePIN(pin)) {
card.incrementFailedAttempts();
if (card.getFailedAttempts() >= 3) {
card.setStatus(CardStatus.BLOCKED);
cardReader.ejectCard();
throw new ATMException("Card blocked due to multiple failed attempts");
}
cardReader.ejectCard();
throw new ATMException("Invalid PIN");
}
// Authenticate with bank
Customer customer = bankService.authenticateUser(card.getCardNumber(), pin);
if (customer == null) {
cardReader.ejectCard();
throw new ATMException("Authentication failed");
}
card.resetFailedAttempts();
currentSession = new ATMSession(customer);
return customer;
}
}
public class TransferTransaction extends Transaction {
private Account fromAccount;
private Account toAccount;
@Override
public boolean execute() {
try {
// Validate accounts
if (fromAccount == null || toAccount == null) {
status = TransactionStatus.FAILED;
return false;
}
// Check balance
if (!fromAccount.canWithdraw(amount)) {
status = TransactionStatus.FAILED;
description = "Insufficient funds";
return false;
}
// Debit from source
if (!fromAccount.debit(amount)) {
status = TransactionStatus.FAILED;
return false;
}
// Credit to destination
toAccount.credit(amount);
status = TransactionStatus.SUCCESS;
description = "Transfer successful";
return true;
} catch (Exception e) {
status = TransactionStatus.FAILED;
rollback();
return false;
}
}
@Override
public void rollback() {
if (fromAccount != null) {
fromAccount.credit(amount); // Refund
}
}
}
  1. Add biometric authentication (fingerprint, face recognition)
  2. Implement cardless withdrawal using mobile QR codes
  3. Add multi-currency support
  4. Implement check imaging for deposits
  5. Add bill payment functionality
  6. Support mini-statement printing
  7. Implement dynamic cash optimization
  8. Add remote monitoring and maintenance
  9. Support contactless card reading (NFC)
  10. Implement fraud detection mechanisms