Whenever I look at how online shopping platforms manage discounts, one thing becomes clear: displaying a coupon code is only a small part of the process. Behind every working coupon, there is a system responsible for generating codes, checking their validity, managing expiration dates, tracking usage, and calculating discounts.
For developers, building such a system is an interesting way to understand how different parts of an application work together.
In this article, I will walk through how I would build a basic coupon management system using ASP.NET Core and Entity Framework Core. My goal is to keep the implementation practical and explain the important decisions behind the code.
I will use an e-commerce scenario where customers can apply discount codes during checkout. The same approach can also be adapted for coupon platforms such as RabattDigga, where managing different offers, discount codes, and their validity is an important part of the overall experience.
1. Planning the Coupon Management System
Before writing code, I would first identify what the application needs to handle.
A basic coupon management system should support:
- Creating and updating coupon codes.
- Defining fixed or percentage-based discounts.
- Setting minimum order amounts.
- Managing coupon start and expiration dates.
- Limiting the number of times a coupon can be redeemed.
- Preventing customers from using the same coupon repeatedly.
- Calculating discounts during checkout.
I would divide the application into three main parts:
- API Layer: Handles incoming requests from the client.
- Business Logic Layer: Validates coupons and calculates discounts.
- Data Access Layer: Uses Entity Framework Core to communicate with SQL Server.
This separation makes the application easier to maintain and extend.
2. Creating the ASP.NET Core Project
I would start by creating an ASP.NET Core Web API project.
Using the .NET CLI:
dotnet new webapi -n CouponManagement
cd CouponManagementNext, I would install the Entity Framework Core packages required for SQL Server.
dotnet add package Microsoft.EntityFrameworkCore.SqlServer
dotnet add package Microsoft.EntityFrameworkCore.ToolsEntity Framework Core allows me to work with database records through C# objects instead of writing SQL queries for every operation.
3. Designing the Coupon Entity
The first important step is designing the database structure.
I would create a Coupon entity containing the information required to manage each discount code.
public class Coupon
{
public int Id { get; set; }
public string Code { get; set; } = string.Empty;
public decimal DiscountValue { get; set; }
public string DiscountType { get; set; } = "Percentage";
public decimal MinimumOrderAmount { get; set; }
public DateTime StartDate { get; set; }
public DateTime ExpiryDate { get; set; }
public int MaxRedemptions { get; set; }
public int RedeemedCount { get; set; }
public bool IsActive { get; set; }
}I would also create a separate entity to track coupon redemptions.
public class CouponRedemption
{
public int Id { get; set; }
public int CouponId { get; set; }
public string UserId { get; set; } = string.Empty;
public DateTime RedeemedAt { get; set; }
public decimal DiscountApplied { get; set; }
public Coupon? Coupon { get; set; }
}Keeping redemption records separate is important. It allows me to track which customer used a particular coupon and how much discount was applied.
It also makes reporting easier when I want to understand which coupons are performing well.
4. Configuring Entity Framework Core
Now I need to connect these entities to the database.
I would create a CouponDbContext class.
using Microsoft.EntityFrameworkCore;
public class CouponDbContext : DbContext
{
public CouponDbContext(
DbContextOptions<CouponDbContext> options)
: base(options)
{
}
public DbSet<Coupon> Coupons => Set<Coupon>();
public DbSet<CouponRedemption> CouponRedemptions
=> Set<CouponRedemption>();
protected override void OnModelCreating(
ModelBuilder modelBuilder)
{
modelBuilder.Entity<Coupon>()
.HasIndex(c => c.Code)
.IsUnique();
modelBuilder.Entity<CouponRedemption>()
.HasIndex(r => new { r.CouponId, r.UserId })
.IsUnique();
modelBuilder.Entity<CouponRedemption>()
.HasOne(r => r.Coupon)
.WithMany()
.HasForeignKey(r => r.CouponId);
}
}There are two important database constraints here.
First, every coupon code must be unique. Second, the same user should not be able to redeem the same coupon more than once.
I prefer enforcing these rules at the database level as well as in the application logic. This provides additional protection when multiple requests are processed simultaneously.
5. Connecting the SQL Server Database
I would store the database connection string in appsettings.json.
{
"ConnectionStrings": {
"DefaultConnection": "Server=.;Database=CouponDB;Trusted_Connection=True;TrustServerCertificate=True"
}
}Then I would register the database context in Program.cs.
builder.Services.AddDbContext<CouponDbContext>(
options =>
options.UseSqlServer(
builder.Configuration.GetConnectionString(
"DefaultConnection")));For a production application, I would use secure configuration or environment variables for database credentials rather than exposing sensitive connection details in source control.
After configuring the connection, I would create the database migration.
dotnet ef migrations add InitialCreate
dotnet ef database updateAt this point, the database structure is ready.
6. Building the Coupon Validation Logic
This is where the main business logic comes in.
I would never allow a coupon to be applied simply because its code exists in the database. The application must check several conditions before accepting it.
For example:
- Is the coupon active?
- Has its validity period started?
- Has it expired?
- Has it reached its redemption limit?
- Does the customer's order meet the minimum amount?
- Has the customer already redeemed it?
I would put these checks inside a dedicated service rather than placing all the logic inside an API controller.
Here is a simplified validation method:
public async Task<Coupon?> ValidateCouponAsync(
string code,
decimal orderAmount)
{
var normalizedCode = code.Trim().ToUpperInvariant();
var coupon = await _context.Coupons
.FirstOrDefaultAsync(c => c.Code == normalizedCode);
if (coupon == null || !coupon.IsActive)
return null;
var now = DateTime.UtcNow;
if (now < coupon.StartDate || now > coupon.ExpiryDate)
return null;
if (coupon.RedeemedCount >= coupon.MaxRedemptions)
return null;
if (orderAmount < coupon.MinimumOrderAmount)
return null;
return coupon;
}
Join the conversation! Your thoughts help the community grow.