ESP32 RFID Cashless Payment System- A new card being scanned
Cashless payment technology has become an essential part of modern society. From public transportation and school cafeterias to supermarkets, offices, libraries, and access control systems, RFID-based payment solutions continue to replace traditional cash transactions because they are faster, safer, and easier to manage.
Most commercial RFID payment systems rely on cloud servers, internet connectivity, and expensive subscription services. While these solutions work well for large organizations, they are often unnecessary for small businesses, schools, transport operators, research laboratories, and educational projects that simply need a secure, reliable, and affordable local payment system.
In this project, I designed and built a complete ESP32 RFID Cashless Payment System capable of storing user information, processing account top-ups, deducting payments, checking balances, recording every transaction, and allowing an administrator to manage users through a local wireless dashboard—all without requiring an internet connection.
The entire system runs on the powerful ESP32 Development Board, uses the popular MFRC522 RFID Reader/Writer, stores information inside the ESP32 using SPIFFS, records accurate timestamps with the DS3231 Real-Time Clock (RTC), displays user information on a 16×2 I2C LCD, and creates its own Wi-Fi network where administrators can monitor accounts and export transaction history.
Unlike many RFID tutorials available online, this project goes far beyond simply reading an RFID card. It demonstrates how multiple embedded technologies can work together to build a practical electronic payment solution that closely resembles real-world systems used in transportation, campuses, event centers, and businesses.
Whether you are an engineering student, electronics enthusiast, IoT developer, embedded systems engineer, or final-year project researcher, this guide will provide valuable insight into designing a standalone RFID payment platform that is expandable and ready for further development.
Throughout this article, I will explain the complete design philosophy, the hardware used, how the software operates, how the local database works, how transaction records are stored, and how the ESP32 serves an administrator dashboard over a Local Area Network (LAN). I will also discuss how the Arduino code coordinates all of these components to create a responsive and reliable cashless payment system.
If you are looking for a practical ESP32 RFID project that combines embedded systems, data storage, web technologies, and wireless networking into one complete application, this project is an excellent place to start.
The primary objective of this project was to design a fully functional RFID-based electronic payment terminal capable of operating independently on a local wireless network without relying on cloud services or external databases.
The system allows authorized administrators to register RFID cards, remove users, recharge balances, deduct payments, verify account balances, and maintain a complete transaction history. Every operation is timestamped using a real-time clock and stored inside the ESP32’s internal flash memory for future reference.
To make the system user-friendly, all interactions are displayed on a 16×2 LCD screen, while three navigation buttons provide an intuitive menu interface for selecting different administrative functions.
Unlike conventional Arduino RFID demonstrations that simply display a card’s UID on the Serial Monitor, this project introduces persistent storage, user authentication, account management, transaction logging, and a browser-based administrator interface.
One of the project’s most useful features is its built-in Wi-Fi Access Point. Instead of connecting to an existing router, the ESP32 creates its own wireless network. Once connected, an administrator can log into a password-protected web dashboard to view registered users, monitor balances, inspect transaction history, and export records in CSV format for further analysis using Microsoft Excel or other spreadsheet software.
The entire system was designed to operate as a portable embedded device powered by a rechargeable 3.7V lithium-polymer battery. A dedicated charging module continuously charges the battery whenever external 5V power is available, allowing uninterrupted operation even during charging. This makes the project suitable for portable installations where mobility and continuous uptime are important.
Because all information is stored locally within the ESP32 using SPIFFS, the system remains fully functional even when internet access is unavailable. This significantly improves reliability while reducing operational costs associated with cloud hosting and remote databases.
This project incorporates several advanced features that make it significantly more capable than a standard RFID card reader.
Every RFID tag or card possesses a unique identification number (UID). When a user scans a card, the ESP32 compares the UID against its locally stored database to determine whether the card is registered. Unauthorized cards are rejected immediately, helping to prevent unauthorized access.
To enhance security, sensitive operations such as adding users, removing cards, topping up balances, debiting accounts, and checking balances require an administrator RFID card before access is granted.
This additional authentication layer helps protect the database from unauthorized modifications.
New RFID cards can be enrolled directly from the device without connecting to a computer. Once authenticated as an administrator, a new card can be scanned and stored inside the onboard database with its own account information.
Each registered RFID card functions like a digital wallet.
Administrators can:
This functionality makes the project suitable for prepaid payment systems.
Instead of relying on an online SQL database or cloud platform, all user information is stored inside the ESP32 using SPIFFS in JSON format.
This offers several advantages:
Every successful debit or top-up operation is automatically recorded.
The stored information includes:
Maintaining a transaction log improves accountability and simplifies auditing.
The DS3231 Real-Time Clock ensures that every transaction receives an accurate timestamp even if the ESP32 restarts. Accurate timekeeping is especially important for payment systems because transaction records should always contain reliable dates and times.
A 16×2 I2C LCD provides immediate feedback during every operation.
Examples include:
This improves the user experience while reducing confusion during operation.
Rather than requiring a computer or mobile application, the device includes three physical buttons for navigating the on-device menu.
The buttons perform the following functions:
This simple interface makes the system easy to operate even for users with little technical experience.
The ESP32 creates its own Wi-Fi hotspot, allowing administrators to connect directly using a smartphone, tablet, or laptop.
After logging in with administrator credentials, users can:
This built-in dashboard transforms the ESP32 from a simple embedded controller into a compact standalone management server.
The hardware was designed for portability and uninterrupted operation.
The system combines:
This arrangement enables the battery to charge while simultaneously powering the system, allowing continuous operation without shutting down during charging.
Such a design is ideal for portable ticketing terminals, mobile payment devices, temporary event installations, educational demonstrations, and remote deployments.
The following hardware components were used to build the complete RFID payment system.
| Component | Purpose |
|---|---|
| ESP32 Development Board | Main controller and Wi-Fi server |
| MFRC522 RFID Reader/Writer | Reads RFID cards and tags |
| RFID Cards/Tags | User identification and payment medium |
| 16×2 I2C LCD Module | Displays menus and transaction information |
| DS3231 RTC Module | Provides accurate date and time |
| Three Push Buttons | Menu navigation and user input |
| 3.7V LiPo Battery | Portable power source |
| LiPo Charging Module | Charges the battery safely |
| 5V 0.6A Power Supply Module | External power and charging source |
| Jumper Wires and PCB | Electrical connections and assembly |
The above image is the front view of the finished device. The 1602 LCD screen is palce at the transpparent cover top of the enclosure of the ESP32 RFID Cashless Payment System. As well as the RFID reader. This is most convinient to quickly scan the RFID cards or tags of the users. There is also an indicator LED that blinks for each action carried out byh the design.
A close up picture of the RFID card being scanned is shown above. Before a new tag or existing card is being used in the design to carry out any transaction, the admin has to verify such a transaction by using his or her own tag. The image above is a new card being added to the system for a user. After this, the user’s card be topped up a certain amount.
The LCD displaying transaction messages. As soon as the trascations has been carried out, the result is shown on the LCd screen of the project design.
Menu navigation using the push buttons on the project allowed the admin to safely and conviniently select with option of decions he or she wants to make. In the image above, the nav button allowed the admin to debit a sum of 50 from the user’s card.
The image above shows the Administrator dashboard open in a web browser. We can see the tabulated summary of every transactions performed by the project design. These include the how many users are registered in the system, their respeictive user ID, When they performed a transactions, what type of transaction was actually performed, their available balance and the date and time when the transaction occured.
The above is the schematic diagram of the project. It is buit around the ESP32 development (dev.) board as the brain of the design. The RFID reader, MFRC522 is connected to the ESP32 using SPI communication.
The pushbuttons that served as the nav buttons were connected through push-up technique. This allowed us to make use of 3 pushbuttons with three (3) pieces of 10k-Ohms resistors to achieve this.
The LCD module of course was connected to the ESP32 using the I2C communication protocol. With the help of the I2C LCD module, we were able to reduce the number of the communication wires from 6 data wires to only 2. Using the SCL and the SDA this time.
Since the RTC module, DS3231 module also used the I2C communication protocol, it was connected likewise. However, since each I2C device has its addressable bus, this was easier to find and program it.
THe power to the system design came two ways: the utility source from the 5V Hi-link Power supply and the 3.7V 2000mAh rechargeable battery. The connection was done is such a way that the power supply can charge the battery by using the LiPO Battery charger module.
Lastly, a switch was added to ensure that the power is isolated from the design when needed. This is the pictorial view of the schematic diagram. The PCB is already completed and you can order it by following the link here.
At a high level, the system follows a simple but robust workflow designed to ensure secure and traceable transactions.
When the device powers on, the ESP32 initializes all connected peripherals, including the RFID reader, LCD, RTC, Wi-Fi Access Point, SPIFFS storage, and local web server. Existing user records and transaction history are loaded from internal flash memory into RAM, allowing the system to resume operation with previously stored data.
In its idle state, the LCD continuously displays the current date and time obtained from the DS3231 RTC module. This provides visual confirmation that the device is active and that the internal clock is functioning correctly.
To perform administrative tasks, the administrator presses the menu button to enter the navigation interface. Using the Up and Down buttons, the desired operation—such as checking a balance, adding a new card, removing a card, topping up funds, or debiting an account—is selected and confirmed.
Before any sensitive operation proceeds, the system requires authentication using a predefined administrator RFID tag. This security step helps prevent unauthorized users from modifying account information or performing financial transactions.
Once authenticated, the ESP32 prompts for the appropriate RFID card. Depending on the selected operation, the software updates the user’s balance, saves the changes to the local JSON database stored in SPIFFS, records the transaction with an accurate timestamp from the RTC, and refreshes the LCD with confirmation messages.
At the same time, the ESP32 hosts a password-protected web dashboard over its built-in Wi-Fi Access Point. Administrators connected to this network can review registered users, inspect transaction history, and export all records in CSV format for reporting or backup purposes.
This combination of embedded hardware, local storage, and wireless administration results in a compact, standalone cashless payment solution that is practical, reliable, and easily expandable for real-world applications.
Building an RFID cashless payment system is not simply a matter of connecting an RFID reader to an ESP32 and reading a card UID. To make the project useful as a complete embedded system, several different hardware modules have to work together.
The RFID reader identifies the user, the ESP32 processes the transaction, the LCD provides visual feedback, the push buttons provide a physical user interface, the RTC provides accurate timestamps, and the ESP32’s internal flash storage preserves account and transaction information.
The power system is equally important because this project was designed to operate as a portable rechargeable device while also supporting operation from an external 5V supply.
This section explains the major hardware components used in the project and how they fit into the overall system architecture.
At the center of the project is the ESP32 development board.
The ESP32 was selected as the main controller because it provides considerably more functionality than a basic Arduino board. In this project, the microcontroller is responsible for processing RFID cards, controlling the LCD, reading the push buttons, communicating with the DS3231 RTC, managing internal storage, hosting the local Wi-Fi network, and running the web server.
In other words, the ESP32 is responsible for both the embedded control system and the local network application.
This makes the ESP32 particularly suitable for projects that combine hardware and software.
One of the biggest advantages of the ESP32 in this project is its integrated Wi-Fi capability. A conventional Arduino Uno or Arduino Nano would require an additional Wi-Fi module if the project needed a wireless administrator dashboard. The ESP32 already contains Wi-Fi functionality, reducing the number of external components and simplifying the overall design.
The ESP32 also provides:
These features make it possible to combine several technologies into a single embedded platform.
The MFRC522 RFID module is responsible for communicating with the RFID cards and tags used by the system. RFID stands for Radio Frequency Identification. It is a technology that allows information to be exchanged wirelessly between a reader and a compatible RFID tag or card.
In this project, the RFID card acts primarily as an identification token. When a card is placed close to the MFRC522 reader, the reader detects it and communicates with the card. The ESP32 then obtains the card’s UID and uses that identifier to determine which user account is associated with the card.
The important concept here is that the system does not need to store the user’s entire account information on the RFID card. Instead, the RFID card provides an identifier, while the actual account information is stored in the ESP32’s local database.
The basic relationship is therefore:
RFID Card → UID → ESP32 → Local User Database → Account Information
For example, the system might read a card UID such as:
5305F803
The ESP32 searches its locally stored user records for that UID. If the UID exists, the corresponding account can be accessed. If it does not exist, the system treats the card as unregistered. This approach makes the RFID card function similarly to an identification key.
The RFID cards used in the project are associated with individual users. During registration, the administrator scans a new RFID card. The ESP32 reads its UID and stores that UID in the user database. The project uses a simple user structure:
struct User {
String id;
String name;
int balance;
};
This structure associates three pieces of information with each user:
id — the RFID card UIDname — the user’s assigned namebalance — the user’s current account balanceFor example, a record can conceptually look like this:
| RFID UID | Name | Balance |
|---|---|---|
| 5305F803 | User1 | 500 |
When that card is scanned later, the ESP32 can locate the corresponding user and retrieve the balance. This separation between the RFID identifier and the account data is an important part of the project’s architecture.
The project uses a special RFID card as the administrator credential.
The administrator card has a predefined UID in the program:
String admin = "1C13FF38";
Sensitive operations require the administrator to scan this card before the operation can continue.
For example, adding a new card follows a workflow similar to:
Select Add Card → Scan Administrator Card → Verify Admin → Scan New Card → Confirm → Save
The same principle is used for:
This provides an additional authentication layer at the physical device.
The administrator UID is currently hard-coded into the Arduino program. That is acceptable for a prototype or educational project, but it should not be considered sufficient security for a commercial payment system.
A production implementation should consider stronger authentication mechanisms, secure credential storage, encrypted communication, protected administrative credentials, and potentially cryptographic authentication rather than relying only on a card UID. This distinction is important because a prototype can demonstrate the concept successfully while still requiring additional security engineering before handling real money or sensitive financial information.
The project uses a 16×2 LCD module with an I2C interface as the primary local display. The LCD contains two rows, with each row capable of displaying up to 16 characters. The LCD is used to provide immediate feedback to the person operating the device.
For example, it can display messages such as:
Admin Auth NeededScan Admin Card...Admin Verified!Scan User Card...Top-Up Success!Debit Success!Insufficient Funds!Card Not FoundCard Removed!This is particularly useful because the device does not need to remain connected to a computer for basic operation.
The I2C interface significantly reduces the number of ESP32 GPIO pins required by the LCD.
Instead of connecting many individual LCD control and data lines, the I2C backpack allows communication using the I2C bus.
The code initializes the LCD using:
LiquidCrystal_I2C lcd(0x27, 16, 2);
Here:
0x27 is the I2C address configured for the LCD module.16 represents the number of columns.2 represents the number of rows.The ESP32 initializes the I2C interface with:
Wire.begin(SDA_PIN, SCL_PIN);
In this project:
#define SDA_PIN 5
#define SCL_PIN 4
Therefore, GPIO 5 is used for SDA and GPIO 4 for SCL.
The I2C bus allows the ESP32 to communicate with the LCD and other compatible I2C devices.
The DS3231 RTC module is responsible for keeping track of the date and time.
This is particularly important for a payment or transaction-recording system because knowing what happened is not enough. The system should also know when it happened.
Every transaction recorded by the project contains:
The RTC makes this possible.
The project creates the RTC object with:
RTC_DS3231 rtc;
During initialization, the program checks whether the RTC is available:
if (!rtc.begin()) {
Serial.println("Couldn't find RTC");
while (1);
}
The system also checks whether the RTC has lost power:
if (rtc.lostPower()) {
rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
}
This is useful when initially setting up the device, although a production system would ideally provide a more deliberate clock-setting mechanism.
The project uses the following function to obtain the current date and time:
void getCurrentDateTime(String &dateStr, String &timeStr)
The function reads the current time from the RTC:
DateTime now = rtc.now();
It then creates formatted date and time strings.
For example, a transaction might eventually be represented as:
Date: 2026-09-27
Time: 13:06:25
This information is then stored with the transaction.
The result is a much more useful transaction history than simply recording the amount deducted.
The device includes three physical push buttons.
They are connected to:
#define upPin 12
#define dwPin 14
#define sePin 27
The three buttons have different responsibilities.
The Up button allows the operator to:
The Down button allows the operator to:
The Select button is used to:
Together, these three buttons create a simple embedded user interface without requiring a touchscreen.
The menu is one of the features that turns the project from a basic RFID reader into a complete embedded application.
The software maintains a menu position using:
int menuOption = 0;
The available menu options are:
The LCD displays two menu items at a time.
For example:
>Check Balance
Add Card
Pressing the Up or Down button moves through the options. The greater-than symbol indicates the currently selected option. This creates a basic menu navigation system similar to what can be found in dedicated electronic terminals.
The MFRC522 RFID reader communicates with the ESP32 using the SPI communication protocol.
The project initializes SPI with:
SPI.begin();
The RFID reader is then initialized using:
rfid.PCD_Init();
The MFRC522 object is created using:
MFRC522 rfid(SS_PIN, RST_PIN);
with:
#define SS_PIN 21
#define RST_PIN 22
This allows the ESP32 to communicate with the RFID reader and retrieve card information.
The project therefore uses two important communication protocols:
I2C
Used for:
SPI
Used for:
This is a good example of how an ESP32 can coordinate several peripheral devices using different communication protocols.
One of the most interesting parts of this project is that it does not depend on an external database server to store its information. Instead, the ESP32 uses SPIFFS, which stands for Serial Peripheral Interface Flash File System. SPIFFS allows the ESP32 to use part of its internal flash memory as a file system.
The project uses SPIFFS for two major purposes:
The system creates two important files:
/users.json
/transactions.json
This gives the project persistent storage. Without persistent storage, information stored only in RAM would normally disappear when the ESP32 loses power or restarts. By saving the data to flash, the system can reload the information when it starts again.
The project uses JSON to organize its stored information. JSON stands for JavaScript Object Notation. It is a lightweight text-based format commonly used for exchanging and storing structured data.
The ArduinoJson library is included in the project:
#include <ArduinoJson.h>
The user database is stored in a structure similar to:
{
"users": [
{
"id": "5305F803",
"name": "User1",
"balance": 500
}
]
}
This is particularly useful because the data is human-readable and easy to process programmatically. The transaction database follows a similar concept.
For example, a transaction record contains:
{
"date": "2026-09-27",
"time": "13:06:25",
"userId": "5305F803",
"type": "DEBIT",
"amount": 50,
"newBalance": 450
}
The combination of ESP32 + SPIFFS + JSON effectively creates a small embedded local database system.
The project defines:
const int MAX_USERS = 3;
This means the prototype can store up to three registered users.
The initial user database is defined as:
User users[MAX_USERS] = {
{"5305F803", "User1", 0},
{"", "", 0},
{"", "", 0}
};
The first user is already defined, while the other two positions are empty. This limit was intentionally kept small for the prototype. A future version could replace the fixed-size array with a more scalable storage architecture.
For example, a production system could use:
The current implementation is therefore best understood as a local embedded database prototype rather than a commercial banking database.
The project also defines:
const int MAX_TRANSACTIONS = 50;
This means the prototype can store up to 50 transaction records in its in-memory transaction array.
Each transaction is represented by:
struct Transaction {
String date;
String time;
String userId;
String type;
int amount;
int newBalance;
};
The transaction type can be:
TOPUP
or:
DEBIT
This allows the administrator to determine whether money was added to or deducted from an account.
A top-up increases the user’s account balance.
The workflow is approximately:
Administrator Authentication
↓
Scan User RFID Card
↓
Locate User Account
↓
Select Top-Up Amount
↓
Confirm Transaction
↓
Increase Balance
↓
Create Transaction Record
↓
Save User Database
↓
Save Transaction History
↓
Display Success Message
The top-up amount starts at:
int amount = 50;
The Up button increases the amount by 50:
amount += 50;
The Down button decreases it by 50, provided the amount remains above the minimum:
if (amount > 50) {
amount -= 50;
}
This gives the operator a simple way to select the transaction amount.
A debit transaction performs the opposite operation. The system first authenticates the administrator and identifies the user. It then asks the operator to select the amount to deduct.
Before completing the transaction, the software checks the user’s balance:
if (users[userIndex].balance < amount)
If the balance is insufficient, the system displays:
Insufficient
Funds!
and cancels the transaction. This is an important part of the payment logic because it prevents the account from being reduced below zero.
If sufficient funds are available, the system subtracts the amount:
users[userIndex].balance -= amount;
The transaction is then added to the transaction history and both databases are saved.
One of the most useful features of this project is the ESP32’s ability to create its own Wi-Fi network.
The project uses:
WiFi.softAP(ssid, password);
The network is configured using:
const char* ssid = "ESP32-Admin";
const char* password = "admin1234";
This means an administrator does not need to connect the device to an existing home or office Wi-Fi router. Instead, the ESP32 itself becomes the access point.
A typical workflow is:
ESP32 powers on
↓
ESP32 creates Wi-Fi network
↓
Administrator connects phone/laptop to ESP32-Admin
↓
Administrator opens the ESP32 web interface
↓
Administrator logs in
↓
Dashboard displays account and transaction information
This is particularly useful for portable installations.
The project uses the ESP32’s built-in networking capabilities to host a web server.
The following object creates the server:
WebServer server(80);
Port 80 is the standard HTTP port. The program then defines several routes.
For example:
server.on("/", handleRoot);
The root page redirects the administrator to the login page.
The login route is:
server.on("/login", HTTP_GET, []() {
handleLogin();
});
The dashboard is served through:
server.on("/dashboard.html", handleDashboard);
The system also provides an export route:
server.on("/export", handleExport);
These routes allow the ESP32 to behave like a small standalone web server.
The web dashboard provides another way of interacting with the system. While the physical buttons and LCD are used to operate the embedded device, the web dashboard is intended for administration and monitoring.
The dashboard displays a user table containing:
It also displays the transaction history containing:
This provides a much larger view of the information than the 16×2 LCD can provide. The dashboard therefore complements rather than replaces the physical interface.
The project also includes an export function. When the administrator selects the export option, the ESP32 creates CSV-formatted data.
CSV stands for Comma-Separated Values. The exported information can be opened using applications such as:
The code creates the CSV header:
User ID,Name,Balance
followed by the account records.
The transaction section contains:
Date,Time,User ID,Type,Amount,New Balance
This is a useful feature because it allows information stored inside the embedded device to be transferred into a more powerful data-analysis environment.
For example, an administrator could export a month’s transaction records and use Excel to calculate:
Portability was another important consideration in the design. The project uses a 3.7V LiPo rechargeable battery as its portable power source. A LiPo battery provides a relatively compact source of rechargeable energy, making it suitable for portable electronics.
However, a LiPo cell should not simply be connected directly to every part of an embedded system. The battery requires appropriate charging and power regulation. That is why the project incorporates a LiPo charging module and a regulated external 5V supply.
The charging module is responsible for managing the charging process of the 3.7V LiPo battery. When an external 5V supply is connected, the charging circuit can charge the battery while the system is powered. This creates a convenient operating mode in which the device can remain connected to utility power while simultaneously maintaining the battery.
The general power flow is:
5V External Supply
↓
LiPo Charging Module
↓
3.7V LiPo Battery
↓
Power Regulation / System Supply
↓
ESP32 + Peripherals
The exact electrical arrangement should follow the specifications of the charging and regulation modules actually used in the hardware.
The project also includes a 5V 0.6A power supply. This allows the system to operate from an external utility-powered source instead of depending exclusively on the battery. When external power is available, the system can operate continuously while the battery is charged by the charging circuit.
This provides two important operating modes:
The system operates from the LiPo battery.
The system operates from the external 5V supply while the rechargeable battery is maintained through the charging circuit. This makes the device more flexible than a battery-only design.
The complete operating sequence can be summarized as follows.
The ESP32 starts and initializes the connected hardware.
SPIFFS is mounted and existing user and transaction files are loaded.
The MFRC522 reader is initialized so that cards can be detected.
The DS3231 is checked and prepared for timekeeping.
The ESP32 creates its own wireless access point.
The local administration interface becomes available.
The LCD enters its normal idle state.
The administrator presses the Select button.
The Up and Down buttons are used to navigate through the menu.
The administrator scans the authorized RFID card.
Depending on the selected menu option, the administrator can:
The user account is updated.
For financial operations, the system records the transaction date, time, amount, type, and resulting balance.
The updated information is written to SPIFFS.
The LCD displays the result of the operation.
At the same time, the administrator can connect to the ESP32’s local Wi-Fi network and inspect the stored information from the web dashboard.
The architecture of the project can be represented conceptually as:
This architecture demonstrates how the project combines RFID, embedded processing, local storage, real-time clock functionality, wireless networking, web technology, and rechargeable power management into one system.
One of the major strengths of this project is that the different technologies have clearly defined responsibilities. The RFID reader does not need to know anything about account balances. The LCD does not need to know how transactions are stored. The RTC does not need to know how the web dashboard works. The web server does not directly control the RFID hardware.
Instead, the ESP32 acts as the central coordinator. This separation makes the project easier to understand and provides a foundation for future improvements. For example, the RFID component could eventually be replaced with another identification technology while keeping much of the account-management software intact. Similarly, SPIFFS could eventually be replaced with an external database without completely redesigning the user interface.
The hardware design can therefore be summarized as follows:
| Hardware | Main Function |
|---|---|
| ESP32 | Central controller, Wi-Fi and web server |
| MFRC522 | RFID card/tag reader |
| RFID Card | User identification |
| 16×2 I2C LCD | Local display |
| DS3231 | Real-time clock |
| Up Button | Navigation/increment |
| Down Button | Navigation/decrement |
| Select Button | Menu selection/confirmation |
| SPIFFS | Local flash file storage |
| LiPo Battery | Portable power |
| LiPo Charger | Battery charging |
| 5V Supply | External power source |
The result is a compact embedded platform capable of performing significantly more than basic RFID card detection.
The Arduino program can be divided into several major software sections.
These include:
Each section has a specific responsibility, while the ESP32 coordinates the entire application.
The project uses several Arduino libraries to communicate with the different hardware and software components.
The main libraries include:
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include "RTClib.h"
#include <SPI.h>
#include <MFRC522.h>
#include <SPIFFS.h>
#include <ArduinoJson.h>
#include <WiFi.h>
#include <WebServer.h>
Each library has a specific purpose.
The Wire library provides I2C communication. It is used for communicating with the LCD and DS3231 RTC.
This library provides control of the 16×2 I2C LCD.
This library provides functions for communicating with the DS3231 real-time clock.
The SPI library provides the communication interface required by the MFRC522 RFID module.
This library makes it easier to detect RFID cards and retrieve their UIDs.
SPIFFS provides access to the ESP32’s internal flash file system.
ArduinoJson is used to create, save, read, and parse JSON-formatted user and transaction data.
This library provides the ESP32’s Wi-Fi functionality.
This library allows the ESP32 to host the local administrator web interface. Together, these libraries provide the software foundation for the entire project.
The setup() function is executed once whenever the ESP32 starts.
The program initializes the I2C interface:
Wire.begin(SDA_PIN, SCL_PIN);
The Serial Monitor is also started:
Serial.begin(9600);
The LCD is initialized:
lcd.init();
SPI communication is started:
SPI.begin();
The RFID reader is initialized:
rfid.PCD_Init();
The push-button pins are then configured. The program also initializes SPIFFS and starts the web server. Finally, it loads the previously saved user and transaction databases. This initialization process is important because all of the system’s components must be ready before the device begins processing transactions.
One of the most important functions in the project is the RFID reading process.
The program first checks whether a new RFID card is present:
rfid.PICC_IsNewCardPresent()
It then attempts to read the card:
rfid.PICC_ReadCardSerial()
Once the card has been successfully detected, the program extracts its UID. The readCardUID() function converts the RFID UID into a readable hexadecimal string.
For example:
5305F803
The UID is converted to uppercase before being returned. This UID becomes the primary identifier used by the local user database.
Once the ESP32 obtains the RFID UID, the program searches through the user array.
Conceptually, the process is:
RFID Card
↓
Read UID
↓
Search User Database
↓
UID Found?
↙ ↘
YES NO
↓ ↓
Load Reject
Account Card
If the UID matches a registered user, the system can retrieve the person’s name and current balance.
If there is no matching UID, the system displays a message such as:
User Not Found!
This prevents an unknown RFID card from being used as a registered account.
The addToDataBase() function handles user registration. Before adding a card, the system requires administrator authentication.
The LCD displays:
Admin Auth Needed
Scan Admin Card...
The administrator then scans the authorized RFID card.
If the UID matches the stored administrator UID, the system displays:
Admin Verified!
The new RFID card can then be scanned. The software checks whether that card already exists in the database. If it already exists, the registration process is cancelled. If the card is new, the administrator is asked to confirm the registration. Once confirmed, the card is assigned a user record and its initial balance is set to zero.
The updated user database is then saved to SPIFFS.
The removeFromDataBase() function performs the opposite operation. Again, administrator authentication is required before the operation can proceed. The administrator scans the card that should be removed. The program searches the database for the corresponding UID. If the card exists, its user record is cleared and the updated database is saved. The program also contains an important protection mechanism that prevents the administrator card from being removed as an ordinary user card. This is a simple but useful security measure in the prototype.
The checkAccountBalance() function allows an administrator to check the balance associated with an RFID card.
The process is:
Authenticate Administrator
↓
Scan User Card
↓
Find User
↓
Read Balance
↓
Display Balance
The LCD displays the user’s name and current balance.
For example:
User: User1
Balance: 500
This provides a convenient way to verify an account without accessing the web dashboard.
The topUpAccount() function increases a user’s account balance. The administrator must first authenticate using the administrator RFID card. The user card is then scanned. After the account is located, the operator selects the amount to add. The prototype starts with an amount of 50 and allows the Up and Down buttons to increase or decrease the amount in increments of 50. The Select button confirms the amount. The system then asks for a final confirmation before changing the account balance.
If confirmed, the program executes:
users[userIndex].balance += amount;
The new balance is then saved. A transaction record is also created.
The debitAmount() function is responsible for deducting money from a user’s account. The administrator is authenticated first, followed by the user’s RFID card. The operator then selects the amount to deduct. Before completing the transaction, the program checks whether the user has enough balance.
The important check is:
if (users[userIndex].balance < amount)
If the balance is insufficient, the system displays:
Insufficient
Funds!
and does not perform the transaction.
If there is sufficient balance, the amount is deducted:
users[userIndex].balance -= amount;
The new balance is then saved together with the transaction record.
This simple balance validation is one of the most important parts of the payment logic.
The system does not only update balances. It also keeps a record of financial operations.
Each transaction contains:
struct Transaction {
String date;
String time;
String userId;
String type;
int amount;
int newBalance;
};
This means the system can determine:
This makes the project more useful for auditing and record keeping.
After an important database operation, the software saves the information to ESP32 flash storage.
The user database is stored as:
/users.json
while transaction history is stored as:
/transactions.json
The saveUserDatabase() function converts the user information into JSON and writes it to the file. The loadUserDatabase() function performs the reverse process when the ESP32 starts. The same principle is applied to transaction history. This means that the information is not dependent solely on RAM.
If the ESP32 is restarted, the program can reload the previously stored information.
Imagine topping up an account and then switching off the ESP32. If the balance existed only in RAM, the information could be lost. By saving the information to SPIFFS, the system can restore the account information after restarting.
The basic process is:
Transaction
↓
Update RAM
↓
Save JSON
↓
SPIFFS Flash
↓
ESP32 Restart
↓
Read JSON
↓
Restore Database
This is one of the features that separates this project from a basic RFID demonstration.
Whenever a top-up or debit occurs, the program calls:
getCurrentDateTime(date, time);
The function obtains the current date and time from the DS3231 RTC. The information is then stored with the transaction.
For example:
Date: 2026-09-27
Time: 13:06:25
User ID: 5305F803
Type: DEBIT
Amount: 50
New Balance: 450
Having timestamps makes the transaction history much more meaningful.
Another major part of the project is the local web server.
The ESP32 creates a Wi-Fi Access Point using:
WiFi.softAP(ssid, password);
The administrator can connect directly to this network.
The ESP32 then hosts the administration interface using:
WebServer server(80);
This means the system does not require an external internet connection to access the dashboard. The network is essentially a small private network created by the payment terminal itself.
The web interface includes administrator authentication. The program defines administrator credentials and checks them before providing access to protected information.
The dashboard itself also uses:
server.authenticate(webUsername, webPassword)
To protect the dashboard and data export functions. This prevents an ordinary user connected to the ESP32’s Wi-Fi network from immediately accessing the transaction database. Again, this is appropriate for a prototype, but a production implementation should use stronger authentication and secure credential management.
After authentication, the administrator can access the dashboard. The dashboard presents two important categories of information.
The system displays:
The system displays:
This provides a convenient overview of the system without requiring the administrator to interact with the LCD and physical buttons for every administrative task.
The project also provides an export function. The administrator can select the export option from the web dashboard. The ESP32 generates a CSV file containing the user accounts and transaction history. The exported file can then be opened in spreadsheet software.
This is particularly useful for:
It also provides a bridge between the embedded system and desktop data-processing tools.
Although this particular implementation was developed as a prototype, the underlying concept can be adapted to several applications.
The system can be developed into a prepaid bus or transport ticketing platform where passengers use RFID cards to pay fares. A conductor or transport administrator could manage cards and balances locally.
RFID cards could potentially be used for controlled payments in:
Companies could issue RFID cards to employees and use a prepaid account system for cafeteria purchases.
Temporary events could use RFID cards to manage access or prepaid purchases without requiring a permanent internet connection.
Small businesses could potentially use a similar architecture for closed-loop prepaid transactions.
The project is also highly suitable for teaching:
The design provides several advantages.
The basic payment and database functions do not depend on the internet.
The rechargeable battery allows the device to operate away from a fixed power source.
User accounts and transaction records are stored directly on the ESP32.
Administrators can access the system through Wi-Fi.
No dedicated desktop application is required to view the records.
Transaction data can be transferred to spreadsheet applications.
The LCD and three push buttons provide direct interaction with the device.
The prototype can be extended with additional hardware and software features.
Every prototype has limitations, and documenting them is important. The current implementation is intentionally limited to a small number of users and transactions.
The code defines:
const int MAX_USERS = 3;
and:
const int MAX_TRANSACTIONS = 50;
These limits make the prototype easier to manage but would need to be redesigned for a larger deployment. The system also uses relatively simple administrator authentication. The web credentials and administrator RFID UID are defined directly in the program. This is convenient during development but is not the recommended approach for a commercial financial system.
Another consideration is the use of ESP32 flash storage for transaction data. Flash memory has finite write endurance, so a larger production system should carefully manage how frequently data is written. The project also does not currently provide a sophisticated mechanism for recovering from corrupted database files or interrupted writes. These limitations do not diminish the value of the project as an embedded-system prototype. Instead, they provide clear opportunities for future development.
There are many ways this project could be taken further.
The fixed three-user database could be replaced with a scalable database architecture.
The transaction history could be moved to an SD card or external database.
A future version could synchronize transactions with a remote server whenever internet connectivity is available.
A mobile application could be developed for monitoring balances and transaction history.
Future versions could implement stronger authentication and cryptographic security.
Several ESP32 terminals could potentially communicate with a central server.
A distributed system could synchronize local transactions when network connectivity becomes available.
QR codes could be integrated alongside RFID cards.
The system could potentially be extended to work with compatible NFC devices.
A thermal printer could be added to produce physical transaction receipts.
The ESP32 could monitor battery voltage and display remaining battery status.
Automatic backups could be implemented to protect transaction records. These improvements could transform the prototype into a much more sophisticated IoT payment platform.
This project demonstrates the technical concept of an RFID-based closed-loop cashless transaction system. It should not be confused with a commercial bank-card payment terminal. A production financial payment system would require substantially more security, reliability, compliance, authentication, encryption, auditing, and financial infrastructure.
For that reason, this project is best viewed as an educational and engineering prototype that demonstrates how RFID identification can be combined with local account management and transaction recording.
This ESP32 RFID cashless payment system demonstrates how several technologies can be integrated into a single practical embedded project. At the heart of the system is the ESP32, which acts as the central controller and local wireless server. The MFRC522 RFID reader provides card identification, while the RFID UID is used to locate the corresponding user account. The 16×2 I2C LCD provides immediate feedback, and the three push buttons create a simple menu-driven interface. The DS3231 RTC supplies accurate timestamps for transaction records, while SPIFFS and JSON provide a lightweight local storage mechanism for user accounts and transaction history.
The ESP32’s built-in Wi-Fi functionality adds another important dimension to the project. Instead of requiring an external router or internet connection, the ESP32 creates its own Wi-Fi network and hosts an administrator dashboard. Through this dashboard, an authorized administrator can view registered users, monitor balances, inspect transaction history, and export records as CSV data. The rechargeable 3.7V LiPo battery and external 5V power arrangement also make the design suitable for portable operation.
What makes this project particularly valuable is not any single component. The real value comes from the integration of hardware, software, storage, networking, and user-interface design. A simple RFID reader has been transformed into a complete embedded transaction-management platform. The project also provides a strong foundation for future development. With improved database architecture, stronger security, expanded storage, mobile applications, cloud synchronization, battery monitoring, multiple terminals, and more sophisticated authentication, the same fundamental concept could evolve into a considerably more advanced system.
For anyone learning embedded systems, ESP32 programming, RFID technology, IoT development, or hardware-software integration, this project demonstrates an important lesson:
A successful embedded project is not just about making individual components work. It is about making those components work together to solve a practical problem.
That is the central idea behind this ESP32 RFID cashless payment system.
Yes. The core RFID, balance-management, transaction-recording, LCD, RTC, and local database functions operate locally on the ESP32. The device also creates its own Wi-Fi Access Point for local administration.
The project uses the MFRC522 RFID reader/writer module.
The main controller is an ESP32 development board.
User accounts and transaction history are stored locally in the ESP32’s flash storage using SPIFFS and JSON files.
Yes. The administrator dashboard provides a CSV export function.
Yes. The design incorporates a rechargeable 3.7V LiPo battery and charging circuit.
The project was designed to support operation from an external 5V supply while the LiPo battery is being charged, subject to the specifications and wiring of the particular charging and power-management modules used.
The prototype defines a maximum of three users:
const int MAX_USERS = 3;
This can be increased or redesigned for a larger database.
The current prototype defines:
const int MAX_TRANSACTIONS = 50;
A future version could use external storage or a server-based database for significantly larger transaction histories.
The current design should be regarded as a prototype and educational project. A commercial system handling real financial transactions would require substantially stronger security, data integrity, authentication, reliability, and regulatory considerations.
This project started with a simple idea: use an RFID card to identify a user and allow that user to perform cashless transactions. By combining the MFRC522 RFID reader, ESP32, LCD, push buttons, DS3231 RTC, SPIFFS, JSON, Wi-Fi, and a local web server, that idea became a complete working embedded-system prototype. It demonstrates how modern embedded platforms can combine physical hardware with software and networking to create practical solutions.
The project can also serve as a foundation for much larger systems, including RFID bus ticketing, campus payment systems, event management, employee cafeteria systems, prepaid services, and other closed-loop payment applications. Most importantly, it demonstrates the process of moving from individual components to a complete product concept—one of the most important skills in embedded systems and IoT development.
https://youtu.be/rnfi3aCiNXk If you grew up in Igboland, you already know that the week doesn't always…
Introduction: Can Fungi Really Feed the Future? Mycoprotein When people first hear about protein made…
Introduction Imagine this: your neighbor needs a 3D printer for a weekend project, while you’ve…
Introduction Imagine walking onto a small farm or a nature reserve, where tiny devices quietly…
Introduction Imagine handing your child an Interactive Personalized Storybook where they are the hero of…
Introduction Imagine waking up one morning to find your company’s entire IT infrastructure compromised—servers down,…
This website uses cookies.