Generating coupon codes with expiry dates

If you have some software or an add-on, and you want to find a way of giving a free trial that expires, one way is to create a token code that has all that built in. Here's a way to do it using Apps Script.

The coupon code

A generated code looks like this
prefix-part1-part2

where
  • prefix is a plain code you can use to identify which kind of coupon this is.
  • part1-part2 is generated cryptogram that is used to decide whether the code is valid and what its expiration date is

Encryption

To decode a coupon code, you need to know the private key and algorithm key with which it was created. Both are needed to sign and hash the coupon code

Using

The cCoupon library is available here 

1X-tyDMF_iILp3cQ4MMCY0GQwrOPHl8ocKtWhqVuw1u5PG5wMytL6mjOP

or you can get the source from github.  If using from the library, then you can define Coupon as
var Coupon = cCoupon.Coupon;

Examples


Let' say you want to generate a coupon for some promotion, lets call it xmas. You'll want to give it an expiry date that you can check later when the coupon is redeemed, but you also want to ensure that the coupon is a genuine one that you created using your private key and algorithm.

A coupon code could look like this, and is generated as per the examples below
xmas-b5s-1775r0qeb

Private key

This is any string (I've the minimum length at 6 characters, but longer is better), and you'd probably store this securely away in your Apps Script PropertiesStore. 

var privateKey = "my private key";

Expiring at a specific time

Here you specify a specific time as a timestamp. This is later decoded from the coupon code and is used to decide whether the coupon has expired 

- this generates  xmas-wlu-1ob0d0k4p
var couponCode = Coupon.generate(privateKey, new Date(2016,11,25).getTime(),'xmas');

Expiring in some days

This allows you ask for a code that expires some number of days from now.

- this generates xmas-b68-12bl3kk2d
  var couponCode = Coupon.generateDays(privateKey, 28,'xmas');

Expiring in some months

This allows you ask for a code that expires some number of months from now.

- this generates  xmas-is0-12blhhd7i
  var couponCode = Coupon.generateMonths(privateKey, 3,'xmas');

Number of days valid from redemption

Sometimes you want the code to have an expiry date for redemption, but to have a trial period starting at the date of redemption. Each of the examples above can take an extra parameter that is the number of days the coupon lasts for from the day of redemption. In this case the code contains not only the expiration date (when it has to be used by), but also the number of days it's valid for from redemption. 

This example generates a coupon that's valid for a year, and has a 28 day trial period from the date of redemption - xmas-ndt-1ebnjs1th8
  var couponCode = Coupon.generateMonths(privateKey, 12,'xmas',28);

Of course you'd need some caution with that one, because since the expiration is valid for a year, there's nothing to stop it being used multiple times within that period - there's no way to know if a token has already been used unless you record somewhere - in the property store or somewhere else - that a token has been redeemed.

Creating a coupon code specific to a user

Let's say you want to create a coupon code that's specific to a particular user.  All you need to do here is to add some user id to your private key. When you app checks the validity of the token, it will only be reported as valid if you decode it with the same user id appended to your private key -  xmas-n4b-2u9l7lb1b
var couponCode = Coupon.generateMonths(privateKey + "bruce@mcpher.com", 1,'xmas');

You could of course use this same technique to keep the same prefix and private key but apply it to different apps. 
For example;
var couponCode = Coupon.generateMonths(privateKey + "myapp1", 1,'xmas');

Would create an entirely different code from;
var couponCode = Coupon.generateMonths(privateKey + "myapp2", 1,'xmas');

even though both codes would start with xmas- 

Decoding

To decode a coupon, you need to know the private key it was created with. Let's take a look at all the keys we generated earlier.
var decode = Coupon.decode (privateKey , 'xmas-wlu-1ob0d0k4p');

gives this
{
"expiry": 1482624000000,
"valid": true,
"prefix": "xmas",
"coupon": "xmas-wlu-1ob0d0k4p",
"expired": false,
"extraDays": 0,
"extendedExpiry": 0
}

where
 property description
 expiry a timestamp for when the coupon code expires
 valid where the code is valid
 prefix the prefix
 coupon the coupon code
 expired whether the thing has expired now
 extraDays the number of extra days. This would be the value for a coupon that has some days available from the redemption date
 extendedExpiry If there are extraDays and the token was redeemed now, this is when it would expire

The others follow the same pattern. Let's look at what you get when you have extra days built in to the token, as in the example
 var decode = Coupon.decode (privateKey , 'xmas-ndt-1ebnjs1th8');

{
"expiry": 1509186713655,
"valid": true,
"prefix": "xmas",
"coupon": "xmas-ndt-1ebnjs1th8",
"expired": false,
"extraDays": 28,
"extendedExpiry": 1480074306097
}

If the coupon were to be redeemed now, then the expiry date is in the extendedExpiry property timestamp.
  Logger.log(new Date(decode.extendedExpiry).toLocaleString());

Specific  use code

If we have included some kind of user specific info in the encryption, as in the example
var couponCode = Coupon.generateMonths(privateKey + "bruce@mcpher.com", 1,'xmas');

then , we need to also include that in the decoding process, as in this example
var decode = Coupon.decode (privateKey + "bruce@mcpher.com" , 'xmas-n4b-2u9l7lb1b');

which produces this
{
"expiry": 1480332713662,
"valid": true,
"prefix": "xmas",
"coupon": "xmas-n4b-2u9l7lb1b",
"expired": false,
"extraDays": 0,
"extendedExpiry": 0
}

Expired code


Let's say a code is valid, but has expired - I'll deliberately create and decode one that expires yesterday like this.
  var couponCode = Coupon.generateDays(privateKey, -1 ,'xmas');
  var decode = Coupon.decode (privateKey , couponCode);

then the code is valid, but expired as below

{
"expiry": 1477565709692,
"valid": true,
"prefix": "xmas",
"coupon": "xmas-bdz-1vbbess02",
"expired": true,
"extraDays": 0,
"extendedExpiry": 0
}


Invalid code

Using a completely invalid code will of course result in an invalid decode.
var decode = Coupon.decode (privateKey , 'some-invalid-code');

{
"expiry": 0,
"valid": false,
"prefix": "some",
"coupon": "some-invalid-code",
"expired": true
}

Using the wrong private key will also be invalid
  var couponCode = Coupon.generateDays(privateKey, 10 ,'xmas');
  var decode = Coupon.decode ('the wrong private key' , couponCode);

{
"expiry": 0,
"valid": false,
"prefix": "xmas",
"coupon": "xmas-gns-15b0j0a0v",
"expired": true,
"extraDays": 0,
"extendedExpiry": 0
}

As will using a code generated with the wrong specific user info 
  var couponCode = Coupon.generateDays(privateKey + "id123", 10 ,'xmas');
  var decode = Coupon.decode (privateKey + "id456" , couponCode);

{
"expiry": 0,
"valid": false,
"prefix": "xmas",
"coupon": "xmas-2l4-0vabpc2b1",
"expired": true,
"extraDays": 0,
"extendedExpiry": 0
}


By the way, none of the codes shown here will be valid for you, as I used a different private key than this to generate them. 

Algorithm key and signature size

This is built in to the library. If you make your own copy of the code, you can  change the algorithm key to something else. You'll find them at the top of the Coupon namespace. Both the algorithm key and your private key are needed to create and decode coupon codes.  The signature size can also be changed. This controls size of the key, and should be at least 3, and you can increase it for stronger encryption  but longer code sizes. If you change any of these, then any previously generated keys will no longer  be decodable and will report as invalid, just as will happen if you use a different private key.
  var ALGO = "JABBA";
  var SIG_SIZE = 3;


Specific codes for Anonymous users

If you need to generate codes for specific anonymous users, you can use the library in Anonymous user registration with the Apps Script PropertiesService to generate an maintain anonymous ids, and then apply those ids as part of the encoding and decoding.

How to use

The cCoupon library is available here 

1X-tyDMF_iILp3cQ4MMCY0GQwrOPHl8ocKtWhqVuw1u5PG5wMytL6mjOP

or you can get the source from github.






For more like this, see Google Apps Scripts snippets. Why not join our forum, follow the blog or follow me on twitter to ensure you get updates when they are available.
Comments