Skip to content

About

2021 C# WinForms banking simulator with transactional transfers, bill payments, receipts, alerts & admin tooling.

Topics

Resources

Stars

0 stars

Watchers

1 watching

Forks

Latest commit

Β 

History

37 Commits

Folders and files

NameName
Last commit message
Last commit date
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

ZEMO ATM 🏧

a 2021 C# WinForms banking simulator that kept growing: deposits, withdrawals, transfers, bill payments, printable receipts, email alerts and an admin console.

ZEMO ATM is an old Windows Forms + SQL Server project built while I was learning how stateful desktop applications, database writes and multi-screen workflows fit together.

It is not real banking software and should not be treated as one. The useful part of keeping it public is the engineering story: a small ATM exercise gradually turned into a fairly large transaction-oriented desktop app, complete with all the architectural lessons that come with that.

screenshots

These are the original screenshots from the project-era portfolio archive, now stored with this public repository.

ZEMO ATM screenshot 1 ZEMO ATM screenshot 2

ZEMO ATM screenshot 3 ZEMO ATM screenshot 4

ZEMO ATM screenshot 5 ZEMO ATM screenshot 6

more screens

ZEMO ATM screenshot 7 ZEMO ATM screenshot 8

ZEMO ATM screenshot 9 ZEMO ATM screenshot 10

ZEMO ATM screenshot 11 ZEMO ATM screenshot 12

ZEMO ATM screenshot 13 ZEMO ATM screenshot 14

what it does

There are two broad sides to the application.

customer side

A signed-in customer can:

  • view the current account balance
  • deposit money
  • withdraw money
  • use fixed-value fast-cash buttons
  • transfer money to another simulated account
  • browse recent transactions
  • pay electricity bills
  • pay water bills
  • print payment receipts
  • optionally receive email receipts
  • enable/disable the historical e-receipt preference
  • browse the bundled nearby-ATM directory
  • change the account PIN
  • receive a simulated security alert for large withdrawals

management side

The separate management login opens an admin console for browsing and printing:

  • account metadata
  • recent transactions
  • fund transfers
  • bill payments
  • e-receipt preferences
  • security alerts
  • nearby ATM records

The cleaned account view deliberately does not expose stored PIN/password values.

the interesting part: money movement

The original project changed balances and then wrote history records as separate SQL operations.

That is a dangerous shape for anything pretending to model financial state:

debit balance        βœ“
write transaction    βœ—

=> balance changed with no matching history

The current core flows move those related writes into SQL transactions.

deposit

begin transaction
      β”‚
      β”œβ”€β”€ balance += amount
      β”œβ”€β”€ insert RecentT
      └── read resulting balance
      β”‚
   commit

withdrawal / fast cash

begin serializable transaction
      β”‚
      β”œβ”€β”€ UPDATE ... WHERE Balance >= amount
      β”œβ”€β”€ insert RecentT
      └── read resulting balance
      β”‚
   commit

The guarded update means the debit itself checks the available balance instead of trusting an earlier read.

fund transfer

This flow had the most important correctness bug in the original code.

Historically the sender was debited and the transfer was logged, but the recipient balance was never credited.

The current flow is:

begin serializable transaction
      β”‚
      β”œβ”€β”€ verify recipient exists
      β”œβ”€β”€ debit sender if funds are available
      β”œβ”€β”€ credit recipient
      β”œβ”€β”€ log sender transaction
      β”œβ”€β”€ log recipient transaction
      └── insert FundTransfer record
      β”‚
   commit

All of it succeeds together or rolls back together.

bill payment

Electricity and water payments now also group the balance debit, recent-transaction entry and bill-payment record in one transaction.

Email/printing happens after the database transaction. A mail failure does not undo a successful local payment or leave its accounting half-written.

architecture after the cleanup

The project is still recognizably the old WinForms application, but the most sensitive shared behavior now has explicit boundaries.

WinForms screens
      β”‚
      β”œβ”€β”€ BankingService
      β”‚      β”œβ”€β”€ balances
      β”‚      β”œβ”€β”€ deposits
      β”‚      β”œβ”€β”€ withdrawals
      β”‚      β”œβ”€β”€ transfers
      β”‚      β”œβ”€β”€ bill payments
      β”‚      └── e-receipt state
      β”‚
      β”œβ”€β”€ GridData
      β”‚      └── admin / lookup queries
      β”‚
      β”œβ”€β”€ EmailService
      β”‚      └── optional SMTP + local HTML templates
      β”‚
      └── CredentialHasher
             └── PBKDF2-SHA256
      β”‚
      β–Ό
Database
      β”‚
      β–Ό
SQL Server / LocalDB

That is still far from how I would build a real banking system, but it removes a lot of duplicated database plumbing from button handlers and makes the important transaction boundaries visible.

authentication cleanup

The old version compared customer PINs and management passwords directly against plaintext database values.

Current sign-in does this instead:

account name
    β”‚
    β–Ό
parameterized lookup
    β”‚
    β–Ό
stored credential
    β”‚
    β”œβ”€β”€ PBKDF2 value β†’ verify hash
    β”‚
    └── old local plaintext value
            β”‚
            β”œβ”€β”€ compare once
            └── successful login β†’ replace with PBKDF2 hash

The compatibility branch is only there so an old development database can migrate after first login.

This is still not a production authentication design. A real banking product would not make a desktop client responsible for credential verification against a directly reachable database.

database + SQL cleanup

Database access now goes through one helper and can be configured with:

ZEMO_DB_CONNECTION_STRING

Without it, the project uses a local development database named ZEMO_Bank.

Queries touched by the overhaul use SQL parameters instead of concatenating user-controlled input into SQL strings.

The replacement Database.sql also fixes the historical schema problems:

  • foreign keys point to the correct account table
  • money tables have stable primary keys
  • balances cannot be negative
  • transfer senders and recipients are both account foreign keys
  • self-transfers are rejected
  • transaction-heavy tables have account/date indexes
  • credential columns can hold hashes
  • the admin account grid no longer needs to select credential fields
  • the script contains only obvious disposable demo data

receipts + email alerts

The old code embedded SMTP configuration directly in the desktop source and loaded templates from a developer-specific absolute Windows path.

That has been removed.

Email is now optional and configured at runtime:

ZEMO_SMTP_HOST
ZEMO_SMTP_PORT
ZEMO_SMTP_USERNAME
ZEMO_SMTP_PASSWORD
ZEMO_SMTP_FROM

Templates are loaded relative to the application directory:

index.html
TransactionAlert.html
ProfileAlert.html

If SMTP is not configured, the banking simulation still works.

For bill payments, the app falls back to the original printable DataGridView receipt flow when an email receipt cannot be sent.

historical e-receipt rule

One of the more specific product ideas in the original app was an e-receipt preference.

After enabling it, the UI intentionally prevented disabling it for 30 days.

That behavior is preserved because it is part of the original project rather than something I would invent in a cleanup.

The current code just moves the preference reads/writes out of raw button-handler SQL.

large-withdrawal alert

Withdrawals above the historical threshold create a SecurityAlerts record.

If SMTP is configured, the app also attempts to send the account email address an alert using the bundled transaction template.

The actual money movement does not depend on email delivery succeeding.

stack

Area Technology
UI C# Windows Forms
Runtime .NET Framework 4.7.2
Database SQL Server / LocalDB
Data access System.Data.SqlClient
Password/PIN storage PBKDF2-SHA256
Receipts DataGridView + DGVPrinter
Optional mail System.Net.Mail
Dependencies NuGet / packages.config
Build Visual Studio / MSBuild

project structure

atm-system/
β”œβ”€β”€ ATM.sln
β”œβ”€β”€ Database.sql
β”œβ”€β”€ ATM/
β”‚   β”œβ”€β”€ Infrastructure/
β”‚   β”‚   β”œβ”€β”€ Database.cs
β”‚   β”‚   └── GridData.cs
β”‚   β”œβ”€β”€ Security/
β”‚   β”‚   └── CredentialHasher.cs
β”‚   β”œβ”€β”€ Services/
β”‚   β”‚   β”œβ”€β”€ BankingService.cs
β”‚   β”‚   └── EmailService.cs
β”‚   β”œβ”€β”€ SignIn.cs
β”‚   β”œβ”€β”€ Dash.cs
β”‚   β”œβ”€β”€ Deposit.cs
β”‚   β”œβ”€β”€ Withdrawal.cs
β”‚   β”œβ”€β”€ FastCash.cs
β”‚   β”œβ”€β”€ FundTransfer.cs
β”‚   β”œβ”€β”€ BillPayment.cs
β”‚   β”œβ”€β”€ electricity.cs
β”‚   β”œβ”€β”€ water.cs
β”‚   β”œβ”€β”€ Settings.cs
β”‚   β”œβ”€β”€ ChangePIN.cs
β”‚   β”œβ”€β”€ NearbyATM.cs
β”‚   β”œβ”€β”€ Admin*.cs
β”‚   β”œβ”€β”€ index.html
β”‚   β”œβ”€β”€ TransactionAlert.html
β”‚   └── ProfileAlert.html
└── docs/
    β”œβ”€β”€ engineering.md
    └── screenshots/

The .Designer.cs and .resx files are intentionally part of the repository. They are the actual WinForms layout/resources, not build output.

running it

This is a Windows / .NET Framework project.

  1. Install Visual Studio with .NET desktop development and .NET Framework 4.7.2 targeting support.
  2. Restore NuGet packages for ATM.sln.
  3. Run Database.sql against SQL Server or LocalDB.
  4. Set ZEMO_DB_CONNECTION_STRING if the default LocalDB setup is not suitable.
  5. Open ATM.sln and build/run the project.

Example PowerShell configuration:

$env:ZEMO_DB_CONNECTION_STRING = "Data Source=(LocalDB)\MSSQLLocalDB;Initial Catalog=ZEMO_Bank;Integrated Security=True"

Optional SMTP setup:

$env:ZEMO_SMTP_HOST = "smtp.example.com"
$env:ZEMO_SMTP_PORT = "587"
$env:ZEMO_SMTP_USERNAME = "demo-user"
$env:ZEMO_SMTP_PASSWORD = "demo-password"
$env:ZEMO_SMTP_FROM = "demo@example.com"

The development SQL script includes obvious disposable demo accounts. Their old-style local credentials are intentionally migrated to PBKDF2 by the current login path after a successful first sign-in.

what this still is not

The cleanup makes the repository safer to publish and easier to reason about. It does not turn the application into production banking infrastructure.

A real financial product would need, among other things:

  • a trusted server-side API instead of a desktop client talking directly to SQL Server
  • strong identity, MFA and session controls
  • an immutable double-entry ledger rather than a mutable balance column as the primary source of truth
  • idempotency for financial commands
  • authorization at every server boundary
  • audit/event retention
  • encrypted secret management
  • rate limiting and fraud controls
  • deliberate money/currency types and rounding rules
  • robust concurrency tests
  • observability and recovery workflows
  • compliance and security review far beyond a coursework project

what I would build differently now

The biggest change would be moving all trust off the desktop client.

desktop / web / mobile client
          β”‚
          β–Ό
authenticated backend API
          β”‚
          β”œβ”€β”€ Accounts
          β”œβ”€β”€ Transfers
          β”œβ”€β”€ Payments
          β”œβ”€β”€ Receipt delivery
          └── Admin authorization
          β”‚
          β–Ό
ledger + account read models
          β”‚
          β–Ό
SQL / event storage

The UI would submit commands like β€œtransfer 25.00 from A to B”; it would never receive database credentials or directly mutate balances.

I would also model money and transaction identifiers explicitly, make retries idempotent, use one signed-in-session abstraction instead of static fields, put email behind the server, and test all balance-changing operations independently from the WinForms UI.

There is a deeper breakdown in docs/engineering.md.

why keep this repo?

Because it shows a very different part of the journey from the mobile apps I build now.

It is messy in the useful way old projects are messy: lots of screens, lots of state, a database behind everything, several things I would never design the same way twice β€” and enough real behavior that revisiting it years later exposed interesting transaction, security and architecture problems worth fixing.


a 2021 desktop side quest; definitely not your bank.

About

2021 C# WinForms banking simulator with transactional transfers, bill payments, receipts, alerts & admin tooling.

Topics

Resources

Stars

0 stars

Watchers

1 watching

Forks

Contributors

Languages