Designing a QR-Based Metro Ticket Booking System

Arvind Kumar
7 min readJan 14, 2025

--

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

  1. User registration and login.
  2. Online ticket booking →
    — Select origin and destination.
    — Calculate fare.
  3. Payment processing.
  4. QR code generation for tickets.
  5. Validation of QR codes at metro gates.
  6. Notifications for booking confirmation.
  7. Admin functionalities →
    — Manage metro stations.
    — Configure fare rules.

Non-Functional Requirements

  1. Scalability: Support for high traffic as user base grows.
  2. Security: Encrypt sensitive data and secure APIs.
  3. Reliability: Ensure 99.9% uptime for ticket booking.
  4. Performance: Fast QR code validation (<2 seconds).
  5. 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

C4 Level1 — Context

Containers — Level 2

Shows the major services and how they interact

Containers — Level 2

Component — Level 3

Shows the internal components of each service

Component — Level 3

Database Schema

Tables:

  1. Users: User details (e.g., name, email, hashed password).
  2. Tickets: Booking information (origin, destination, fare, QR code data).
  3. Stations: Metro station details.
  4. 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

  1. Use JWT tokens for authentication and authorization.
  2. Implement role-based access control (RBAC) for admin endpoints.
  3. Encrypt sensitive data such as passwords and payment information.
  4. 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:

  1. 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

  1. Use Short, Encoded Data:
    Keep QR code data concise to reduce complexity and improve scanning accuracy.
  2. Include Checksum/Validation Mechanisms:
    Add a checksum or signature to QR data to detect tampering.
  3. 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:

  1. Implement the system using Spring Boot and React.
  2. Conduct extensive tests with simulated QR flows.
  3. 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.

--

--

Arvind Kumar
Arvind Kumar

Written by Arvind Kumar

Staff Engineer @Chegg || Passionate about technology || https://youtube.com/@codefarm0

No responses yet