Introduction

In the early stages of my software development career I was tasked with creating a voucher / coupon code system to help promote a mobile application.

The voucher codes would allow users to redeem a product for free instead of using their credit card.

During my research (read 'Googling') I came across this answer on StackOverflow. I decided to implement it as it did what we wanted for the most part.

How it works:

  1. A new record is inserted into the dateabase where the primary key is a GUID.
  2. The new record Id is retrieved and converted to a string.
  3. The string is truncated.
  4. The truncated string becomes the voucher code for that record (Voucher).

The Code

This code is just to demonstrate how it works. It won't win prizes for being super clean or following best practices for a public API.  I just wanted to show the main working parts.

I've created a sample application should you want to follow along. It was forked from an ASPNET Core App. The meat of the sample is under the VoucherController.cs file.

Here's a look at Voucher Entity.

public class Voucher
    {
        public Voucher(string name)
        {
            IsValid = true;
            Name = name;
            CreationDateTimeUTC = DateTime.UtcNow;
        }
        [Key]
        public Guid Id { get; set; }

        public string VoucherCode { get; set; }

        public bool IsValid { get; set; } = true;

        public string UsedById { get; set; }

        public string Name {get;set;}

        public DateTime CreationDateTimeUTC {get;set;}
    }

The voucher entity could be extended to include any other properties that you need e.g. DateTime at which a voucher was used, voucher expiration date etc.

Create voucher endpoint:

This endpoint takes a string as the voucher name and then inserts it into the database. It then takes the GUID Id, converts it to a base64 string and stores that as the VoucherCode:

// POST: api/Voucher
        [HttpPost]
        public async Task<ActionResult<Voucher>> CreateVoucher(string name)
        {

            var voucher = new Voucher(name);

            await _context.Vouchers.AddAsync(voucher);

            var voucherId = voucher.Id;

            string voucherCode = ConvertIdToVoucherCode(voucherId);

            voucher.VoucherCode = voucherCode;

            await _context.SaveChangesAsync();

            return voucher;

        }

The ConvertIdToVoucherCode Method:

 private static string ConvertIdToVoucherCode(Guid voucherId)
        {
            var plainTextBytes = System.Text.Encoding.UTF8.GetBytes(voucherId.ToString());
            var voucherCode = Convert.ToBase64String(plainTextBytes).Substring(0, VOUCHER_CODE_LENGTH);
            return voucherCode;
        }

The UseVoucher Endpoint:

This endpoint simple checks if the voucherCode received is valid, if it is, then it sets the IsValid Property to false. This is the endpoint that would be used should a customer want to use a voucher / coupon code.

If the voucher code is not found or it has already been used, then it just returns a NotFound() response.

*In the real world you would want to take some precautions around enumerations here. Perhaps adding a CAPTCHA or else rate limiting the endpoint.

// Post: api/Voucher/5
        [HttpPost("{voucherCode}")]
        public async Task<ActionResult<Voucher>> UseVoucher(string voucherCode)
        {

            var voucher = await _context.Vouchers.FirstOrDefaultAsync(v => v.VoucherCode == voucherCode && v.IsValid == true);

            if (voucher == null)
            {
                return NotFound();
            }

            voucher.IsValid = false;

            await _context.SaveChangesAsync();

            return voucher;
        }

Testing with Postman and Chrome

Initial GET request to see what vouchers are present. There is one voucher present as it's added if the in-memory database contains no vouchers.

GET Request to the voucher controller endpoint to see what vouchers are there
GET Request to the voucher controller endpoint to see what vouchers are there

The next thing we'll do is add a new voucher using a POST request. Here I've added a query string name=secondvoucher.

POST request using Postman to create a new voucher.
POST request using Postman to create a new voucher.

Let's check to see if that voucher has been created with another GET request in Chrome.

Second GET Request showing two vouchers in the system. The second having the name that we gave it with Postman earlier.
Second GET Request showing two vouchers in the system. The second having the name that we gave it with Postman earlier.

Now I'm going to use Postman to 'Use' a voucher. I'll send a POST request to the UseVoucher Endpoint defined earlier and see if it works.

POST Request that used voucher with voucher code 'YTQzZDk2'
POST Request that used voucher with voucher code 'YTQzZDk2'

Now, we're going to simulate trying to 'Use' this voucher again, by using the exact same request. Now note the response:

Trying to reuse the voucher code 'YTQzZDk2'. We get a 404 not found and as such, the voucher is not valid.
Trying to reuse the voucher code 'YTQzZDk2'. We get a 404 not found and as such, the voucher is not valid.

Lastly, we'll do one more GET request to see the state of the vouchers in the system.

Final GET request, showing the used and unused voucher in the system.
Final GET request, showing the used and unused voucher in the system.

Conclusion

So that's the basic functionality of the voucher system. The bones are there to allow you to alter to fit your needs.

The above examples uses a string with 8 characters using uppercase, lowercase and numbers. Let's do some quick maths to figure out how many combinations are possible. This takes me back to Irish Leaving Cert Maths circa 2008 !

26 possible uppercase letters(A-Z), 26 possible lowercase letters (a-z), 10 possible digits (0-9).

So in total that comes to 26+26+10 = 62 possible values for the first character. Extending this to 8 characters and allowing for repeated values we get:

62 x 62 X 62 x 62 x 62 x 62 x 62 x 62 = 2.1834011e+14

That's 218,000,000,000,000 if we remove the scientific notation. In any case, it's a big number.

If you've any questions just leave a comment or reach out to me on Twitter.