Designing a QR-Based Metro Ticket Booking System
Metro ticketing systems are evolving rapidly, with online booking and QR code technology becoming standard in cities like Delhi and Noida. This post provides a detailed guide to designing a simplified QR-based metro ticket booking system with a backend powered by Spring Boot and a React-based frontend. For testing purposes, we explore alternatives to physical QR scanners and the best practices for generating and simulating QR codes.
I have created this in such a way you can use it as requirement/architecture document to implement it, so give it a try and let me know if facing any issue.
Requirements
Functional Requirements
- User registration and login.
- Online ticket booking →
— Select origin and destination.
— Calculate fare. - Payment processing.
- QR code generation for tickets.
- Validation of QR codes at metro gates.
- Notifications for booking confirmation.
- Admin functionalities →
— Manage metro stations.
— Configure fare rules.
Non-Functional Requirements
- Scalability: Support for high traffic as user base grows.
- Security: Encrypt sensitive data and secure APIs.
- Reliability: Ensure 99.9% uptime for ticket booking.
- Performance: Fast QR code validation (<2 seconds).
- Maintainability: Modular design for future expansions.
Simplified Architecture
I have tried to use C4 representation, lets see the each stage one by one
User Management Service
- Handles user registration and authentication
- User Controller: REST API endpoints for user operations
- User Service: Business logic for user management
- User Repository: Data access layer for user information
- Event Publisher: Publishes user-related events (e.g., registration)
- Database: Stores user profiles and credentials
Ticket Service
- Manages ticket booking and validation
- Ticket Controller: REST API endpoints for ticket operations
- Ticket Service: Business logic for ticket management
- Ticket Repository: Data access layer for tickets
- Payment Client: Integration with Payment Service
- Event Publisher: Publishes ticket-related events
- Database: Stores tickets and station information
Payment Service
- Handles payment processing
- Payment Controller: REST API endpoints for payments
- Payment Service: Business logic for payment processing
- Payment Repository: Data access layer for transactions
- Payment Gateway: Integration with external payment providers
- Event Publisher: Publishes payment-related events
- Database: Stores transaction records
Notification Service
- Manages communication with users
- Notification Listner: Kafka Consumer to process notification events
- Notification Service: Business logic for sending notifications
- Email Sender: Handles email notifications
- SMS Sender: Handles SMS notifications
- Event Listeners: Consumes events from other services
Infrastructure Components
Message Queues (Kafka Topics)
- User Topic: User-related events
- Ticket Topic: Ticket booking and status events
- Payment Topic: Payment status events
Databases
- Users Database: User profiles and authentication data
- Tickets Database: Ticket records and station information
- Transactions Database: Payment transaction records
Key Features
- Event-driven architecture using Kafka
- Microservices pattern with clear separation of concerns
- RESTful APIs for service communication
- Asynchronous notification system
- Integration with external services (payment gateway, SMS)
- Persistent data storage with PostgreSQL
This architecture follows modern microservices best practices with clear boundaries between services and asynchronous communication patterns for better scalability and maintainability.
Now Lets see the C4 diagram representation
Context — Level 1
Shows the overall system and its users at a high level
Containers — Level 2
Shows the major services and how they interact
Component — Level 3
Shows the internal components of each service
Database Schema
Tables:
- Users: User details (e.g., name, email, hashed password).
- Tickets: Booking information (origin, destination, fare, QR code data).
- Stations: Metro station details.
- Transactions: Payment transaction history.
API Contracts
1. User Registration
EndpointPOST /api/v1/users/register
Registers a new user into the system.
Request Headers:
Content-Type: application/json
Request Body:
{
"name": "Arvind Maurya",
"email": "arvind@codefarm.com",
"password": "password123"
}
Response Body:
{
"message": "User registered successfully",
"userId": "b71c4568-923c-4f0f-9a4b-1234abcd5678"
}
Notes:
- Passwords must be hashed before storing in the database.
- Email should be validated for uniqueness.
- No authentication token required for this endpoint.
2. User Login
Endpoint: POST /api/v1/users/login
Authenticates a user and returns a JWT for subsequent requests.
Request Headers:
Content-Type: application/json
Request Body:
{
"email": "arvind@codefarm.com",
"password": "password123"
}
Response Body:
{
"message": "Login successful",
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"userId": "b71c4568-923c-4f0f-9a4b-1234abcd5678"
}
Notes:
- JWT token will be used for all subsequent API calls in the
Authorization
header. - Token expiration and refresh policies should be implemented.
3. Book Ticket
Endpoint: POST /api/v1/tickets
Books a ticket for the user.
Request Headers:
Content-Type: application/json
Authorization: Bearer <JWT_TOKEN>
Request Body:
{
"userId": "b71c4568-923c-4f0f-9a4b-1234abcd5678",
"originStationId": "s123",
"destinationStationId": "s456"
}
Response Body:
{
"ticketId": "a1b2c3d4-e567-8901-2345-6789abcdef01",
"originStation": "New Delhi",
"destinationStation": "Noida Electronic City",
"fare": 25.50,
"qrCodeData": "a1b2c3d4e5678901...",
"status": "ACTIVE"
}
Notes:
- Fare calculation logic should be implemented based on stations.
- QR code data is generated and stored in the database.
4. Validate QR Code
Endpoint: POST /api/v1/validation/qr
Validates the scanned QR code at the metro gate.
Request Headers:
Content-Type: application/json
Authorization: Bearer <JWT_TOKEN>
Request Body:
{
"qrCodeData": "a1b2c3d4e5678901..."
}
Response Body (Valid QR Code):
{
"message": "Validation successful",
"ticketId": "a1b2c3d4-e567-8901-2345-6789abcdef01",
"status": "USED",
"originStation": "New Delhi",
"destinationStation": "Noida Electronic City",
"validatedAt": "2025-01-13T10:20:30Z"
}
Response Body (Invalid QR Code):
{
"message": "Invalid or expired QR code",
"status": "ERROR"
}
Notes:
- Update ticket status to
USED
upon successful validation. - Handle expired or reused QR codes gracefully.
5. Admin: Add Station
Endpoint: POST /api/v1/admin/stations
Allows an admin to add new metro stations.
Request Headers:
Content-Type: application/json
Authorization: Bearer <ADMIN_JWT_TOKEN>
Request Body:
{
"name": "Noide Sector 140",
"latitude": 28.63576,
"longitude": 77.22445
}
Response Body:
{
"message": "Station added successfully",
"stationId": "s789"
}
Notes:
- Only accessible to users with the
ADMIN
role.
6. Process Payment
Endpoint: POST /api/v1/payments
Processes the payment for a booked ticket.
Request Headers:
Content-Type: application/json
Authorization: Bearer <JWT_TOKEN>
Request Body:
{
"userId": "b71c4568-923c-4f0f-9a4b-1234abcd5678",
"ticketId": "a1b2c3d4-e567-8901-2345-6789abcdef01",
"amount": 25.50,
"paymentMethod": "UPI"
}
Response Body (Success):
{
"message": "Payment successful",
"transactionId": "tx12345678",
"status": "SUCCESS"
}
Response Body (Failure):
{
"message": "Payment failed",
"transactionId": "tx12345678",
"status": "FAILED"
}
Notes:
- Integration with payment gateways should handle retries and failures.
Security Considerations in all the APIs/Services
- Use JWT tokens for authentication and authorization.
- Implement role-based access control (RBAC) for admin endpoints.
- Encrypt sensitive data such as passwords and payment information.
- Use HTTPS for all API calls to secure data in transit.
QR Code Generation
Backend (Spring Boot)
We use the ZXing library in Java to generate QR codes, providing control over the encoding process and customization.
Key Steps to Generate a QR Code in Spring Boot:
- Install the ZXing library dependency.
- Define the data to encode (e.g., ticket ID, user details, or metadata).
- Use ZXing to generate the QR code as an image.
Implementation Example:
import com.google.zxing.*;
import com.google.zxing.client.j2se.MatrixToImageWriter;
import java.io.ByteArrayOutputStream;
import java.nio.file.FileSystems;
import java.nio.file.Path;
public class QRCodeService {
public byte[] generateQRCode(String data, int width, int height) throws Exception {
QRCodeWriter qrCodeWriter = new QRCodeWriter();
BitMatrix bitMatrix = qrCodeWriter.encode(data, BarcodeFormat.QR_CODE, width, height);
// Write to file (optional for development)
Path path = FileSystems.getDefault().getPath("qr.png");
MatrixToImageWriter.writeToPath(bitMatrix, "PNG", path);
// Write to byte array for API response
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
MatrixToImageWriter.writeToStream(bitMatrix, "PNG", outputStream);
return outputStream.toByteArray();
}
}
Response:
Return the QR code as a byte array for embedding in the frontend. Alternatively, save it in the database or a cloud storage service and return the URL.
Frontend (React)
The React library qrcode.react simplifies QR code rendering in the user interface.
Key Steps:
- Install the
qrcode.react
library:
npm install qrcode.react
2. Render the QR code with the ticket data:
import React from 'react';
import QRCode from 'qrcode.react';
const TicketQR = ({ qrData }) => {
return (
<div>
<h3>Your Ticket QR Code</h3>
<QRCode value={qrData} size={250} />
</div>
);
};
export default TicketQR;
3. Pass the qrData
prop dynamically, typically a unique ticket identifier.
Testing Without a QR Scanner
Since a physical QR scanner may not be available during the development phase, you can simulate and test QR code workflows with the following strategies:
1. Generate and Decode QR Codes
A. Manually Generate QR Codes:
- Use the backend service or libraries like
qrcode.react
in React. - Generate QR codes with sample data such as:
{
"ticketId": "a1b2c3d4-e567-8901",
"userId": "b71c4568-923c",
"originStation": "Station A",
"destinationStation": "Station B",
"timestamp": "2025-01-13T10:20:30Z"
}
B. Decode QR Codes:
- Use online tools (e.g., ZXing Decoder Online) to verify the generated QR code.
- For automated tests, use QR decoding libraries in JavaScript or Java:
- JavaScript: jsQR.
- Java: ZXing decoding methods.
2. Simulate QR Scanning via Manual Input
Instead of scanning QR codes, simulate the process by allowing manual input of QR data in development:
- Update the frontend to include a text box where developers can input QR data.
- Example:
const [qrInput, setQrInput] = React.useState("");
const handleValidation = () => {
fetch('/api/v1/validation/qr', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${token}`,
},
body: JSON.stringify({ qrCodeData: qrInput }),
})
.then((res) => res.json())
.then((data) => {
console.log('Validation Response:', data);
});
};
return (
<div>
<input
type="text"
placeholder="Enter QR Data"
value={qrInput}
onChange={(e) => setQrInput(e.target.value)}
/>
<button onClick={handleValidation}>Validate QR</button>
</div>
);
3. API Testing Tools
Simulate QR workflows by directly invoking validation APIs using tools like Postman or cURL:
- Send a POST request to
/api/v1/validation/qr
with sample QR data. - Example:
curl -X POST https://your-backend-url/api/v1/validation/qr \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <JWT_TOKEN>" \
-d '{"qrCodeData": "sample-data"}'
3. Best Practices for QR Code Workflows
- Use Short, Encoded Data:
Keep QR code data concise to reduce complexity and improve scanning accuracy. - Include Checksum/Validation Mechanisms:
Add a checksum or signature to QR data to detect tampering. - Log All Validation Attempts:
Log QR validation requests to monitor potential misuse or suspicious activities.
Conclusion
This simplified system provides a robust foundation for a QR-based metro ticketing solution. Starting with a streamlined architecture ensures quick deployment for a POC, with scalability options for future growth.
Next Steps:
- Implement the system using Spring Boot and React.
- Conduct extensive tests with simulated QR flows.
- Gradually decompose the architecture into separate microservices as the user base grows.
— — — — —
Share your feedback and thoughts in the comment section and follow for more such content.