DEV Community

Cover image for Building a Simple Voucher System for Small Businesses
Matheus Bernardes Spilari
Matheus Bernardes Spilari

Posted on

Building a Simple Voucher System for Small Businesses

🇺🇸[EN-US] Building a Simple Voucher System for Small Businesses

When I started as a freelancer, one of my first projects was for a small burger shop. The owner wanted a voucher system to reward loyal customers: after collecting five vouchers, customers could claim a free burger. The project needed to be simple, reliable, and tailored to their specific needs. Here's how I approached it.

The Challenge

The main requirements were:

  • Generate unique vouchers for customers when they purchase a burger.
  • Validate a set of five vouchers to allow for a free burger.
  • Keep the system lightweight, as it would run on a single machine.

My Solution

I designed the system using Spring Boot with Thymeleaf to render the front end. Instead of building a complex REST API, I created an intuitive web interface that allows employees to generate and validate vouchers directly.

Key Features

  1. Voucher Generation:

    • A unique token is generated based on the current date and time.
    • The token is stored in a Redis database (for scalability) or in memory (for simplicity).
    • A web page with a single button generates a new token.
  2. Voucher Validation:

    • Employees can input five tokens into a form to verify their validity.
    • If all tokens are valid, the system approves the free burger.
  3. Simplicity:

    • Using Thymeleaf, I avoided the need for a separate frontend framework.
    • The system is accessible via any browser and integrates seamlessly with the small business's operations.

Technical Stack

  • Backend: Spring Boot
  • Frontend: Thymeleaf
  • Database: Redis (for token storage and expiration)
  • Hosting: A single machine

Code

HTML Templates

Inside the folder resources > templates.
Create 3 files, these are the views of our application.

  • index.html - The home page
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <title>Gerenciador de Vouchers</title>
    <link rel="stylesheet" href="/css/style.css">
</head>
<body>
    <div class="container">
        <h1>Bem-vindo ao Sistema de Vouchers</h1>
        <ul>
            <li><a th:href="@{/vouchers/create}">Gerar Voucher</a></li>
            <li><a th:href="@{/vouchers/validate}">Validar Vouchers</a></li>
        </ul>
    </div>
</body>
</html>
Enter fullscreen mode Exit fullscreen mode
  • createToken.html - View to create tokens
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <title>Gerar Token</title>
    <link rel="stylesheet" href="/css/style.css">
</head>
<body>
    <div class="container">
        <h1>Gerar Voucher</h1>
        <a href="/">Página Inicial</a>
        <form action="/vouchers/create" method="post">
            <button type="submit">Gerar Voucher</button>
        </form>
        <div class="ticket" th:if="${token}">
            <p>Seu Voucher:</p>
            <h2 th:text="${token}"></h2>
            <p>Valido até:</p>
            <h3 th:text="${validade}"></h3>
        </div>
    </div>
</body>
</html>
Enter fullscreen mode Exit fullscreen mode
  • validateTokens.html - View to validate tokens
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <title>Validar Vouchers</title>
    <link rel="stylesheet" href="/css/style.css">
</head>
<body>
    <div class="container">
        <h1>Validar Vouchers</h1>
        <a href="/">Página Inicial</a>
        <p class="errors" th:if="${erros}" th:text="${erros}"></p>
        <form action="/vouchers/validate" method="post">
            <label for="token1">Token 1:</label>
            <input type="text" id="token1" name="token1" required>
            <label for="token2">Token 2:</label>
            <input type="text" id="token2" name="token2" required>
            <label for="token3">Token 3:</label>
            <input type="text" id="token3" name="token3" required>
            <label for="token4">Token 4:</label>
            <input type="text" id="token4" name="token4" required>
            <label for="token5">Token 5:</label>
            <input type="text" id="token5" name="token5" required>
            <button type="submit">Validar</button>
        </form>
        <p th:if="${message}" th:text="${message}"></p>
    </div>
</body>
</html>
Enter fullscreen mode Exit fullscreen mode

CSS

Inside the folder resources > static
Create a folder CSS and inside that a file called style.css

style.css

body {
    font-family: Arial, sans-serif;
    margin: 0;
    padding: 0;
    background-color: #f9f9f9;
    color: #333;
}
.container {
    max-width: 600px;
    margin: 50px auto;
    background: #fff;
    padding: 20px;
    border-radius: 8px;
    box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
}
h1 {
    color: #0073e6;
    text-align: center;
}
a {
    text-decoration: none;
    color: #0073e6;
    margin-bottom: 20px;
    display: inline-block;
}
a:hover {
    text-decoration: underline;
}
button {
    background-color: #0073e6;
    color: white;
    border: none;
    padding: 10px 20px;
    border-radius: 5px;
    cursor: pointer;
}
button:hover {
    background-color: #005bb5;
}
form {
    display: flex;
    flex-direction: column;
}
label {
    margin: 10px 0 5px;
}
input {
    padding: 8px;
    border: 1px solid #ddd;
    border-radius: 4px;
    margin-bottom: 15px;
}
p {
    margin-top: 20px;
    font-weight: bold;
    color: #4caf50;
}

.errors{
    color: red;
}

.ticket {
    margin-top: 20px;
    padding: 20px;
    border: 2px dashed #333;
    border-radius: 10px;
    background: linear-gradient(135deg, #fdfdfd 25%, #f3f3f3 100%);
    text-align: center;
    box-shadow: 0 4px 6px rgba(0, 0, 0, 0.2);
    position: relative;
}

.ticket p {
    font-size: 1.2em;
    font-weight: bold;
    margin: 0;
    color: #555;
}

.ticket h2 {
    font-size: 2em;
    margin: 10px 0 0;
    color: #000;
    font-family: 'Courier New', Courier, monospace;
}

.ticket::before,
.ticket::after {
    content: '';
    position: absolute;
    width: 20px;
    height: 20px;
    background: #f9f9f9;
    border: 2px solid #333;
    border-radius: 50%;
    top: 50%;
    transform: translateY(-50%);
    z-index: 10;
}

.ticket::before {
    left: -10px;
}

.ticket::after {
    right: -10px;
}