-
Notifications
You must be signed in to change notification settings - Fork 18
/
coupon-code.js
156 lines (132 loc) · 4.64 KB
/
coupon-code.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
// --------------------------------------------------------------------------------------------------------------------
//
// coupon-code.js : An implementation of Perl's Algorithm::CouponCode for NodeJS.
//
// Author : Andrew Chilton
// Web : http://www.chilts.org/blog/
// Email : <andychilton@gmail.com>
//
// Copyright (c) : AppsAttic Ltd 2011
// Web : http://www.appsattic.com/
// License : http://opensource.org/licenses/MIT
//
// Copyright (c) : Andrew Chilton 2013
// Web : http://chilts.org/
// License : http://opensource.org/licenses/MIT
//
// --------------------------------------------------------------------------------------------------------------------
// npm
var xtend = require('xtend');
// --------------------------------------------------------------------------------------------------------------------
// constants
var badWordsList = ('SHPX PHAG JNAX JNAT CVFF PBPX FUVG GJNG GVGF SNEG URYY ZHSS QVPX XABO ' +
'NEFR FUNT GBFF FYHG GHEQ FYNT PENC CBBC OHGG SRPX OBBO WVFZ WVMM CUNG')
.replace(/[a-zA-Z]/g,function(c){return String.fromCharCode((c<="Z"?90:122)>=(c=c.charCodeAt(0)+13)?c:c-26);})
.split(' ');
var symbolsStr = '0123456789ABCDEFGHJKLMNPQRTUVWXY';
var symbolsArr = symbolsStr.split('');
var symbolsObj = {};
symbolsArr.forEach(function(c, i) {
symbolsObj[c] = i;
});
var defaults = {
parts : 3,
partLen : 4,
};
// --------------------------------------------------------------------------------------------------------------------
// exports
module.exports.generate = function(opts) {
opts = xtend({}, defaults, opts);
var parts = [];
var part;
var i;
var j;
// if we have a plaintext, generate a code from that
if ( opts.plaintext ) {
// not yet implemented
return '';
}
else {
// default to a random code
do {
parts.length = 0;
for( i = 0; i < opts.parts; i++ ) {
part = '';
for ( j = 0; j < opts.partLen - 1; j++ ) {
part += randomSymbol();
}
part = part + checkDigitAlg1(part, i+1);
parts.push(part);
}
} while (hasBadWord(parts.join('')))
}
return parts.join('-');
};
module.exports.validate = function(code, opts) {
if ( !code ) {
throw new Error("Provide a code to be validated");
}
opts = xtend({}, defaults, opts);
// uppercase the code, take out any random chars and replace OIZS with 0125
code = code.toUpperCase()
.replace(/[^0-9A-Z]+/g, '')
.replace(/O/g, '0')
.replace(/I/g, '1')
.replace(/Z/g, '2')
.replace(/S/g, '5');
// split in the different parts
var parts = [];
var tmp = code;
while( tmp.length > 0 ) {
parts.push( tmp.substr(0, opts.partLen) );
tmp = tmp.substr(opts.partLen);
}
// make sure we have been given the same number of parts as we are expecting
if ( parts.length !== opts.parts ) {
return '';
}
// validate each part
var part, str, check, data;
for ( var i = 0; i < parts.length; i++ ) {
part = parts[i];
// check this part has 4 chars
if ( part.length !== opts.partLen ) {
return '';
}
// split out the data and the check
data = part.substr(0, opts.partLen-1);
check = part.substr(opts.partLen-1, 1);
if ( check !== checkDigitAlg1(data, i+1) ) {
return '';
}
}
// everything looked ok with this code
return parts.join('-');
};
// --------------------------------------------------------------------------------------------------------------------
// internal helpers
function randomSymbol() {
return symbolsArr[parseInt(Math.random() * symbolsArr.length, 10)];
}
// returns the checksum character for this (data/part) combination
function checkDigitAlg1(data, check) {
// check's initial value is the part number (e.g. 3 or above)
// loop through the data chars
data.split('').forEach(function(v) {
var k = symbolsObj[v];
check = check * 19 + k;
});
return symbolsArr[ check % 31 ];
}
function hasBadWord(code) {
var i;
code = code.toUpperCase();
for( i = 0; i < badWordsList.length; i++ ) {
if (code.indexOf(badWordsList[i]) > -1)
return true;
}
return false;
};
// also export this (for testing)
module.exports.hasBadWord = hasBadWord
// --------------------------------------------------------------------------------------------------------------------