-
-
Save WindTamer/11199270 to your computer and use it in GitHub Desktop.
| /** | |
| * @fileoverview Utils Interface credit card validation methods. | |
| * @author (Wind Tammer) | |
| */ | |
| utils = {}; | |
| /** @type {Object<Function>} */ | |
| utils.creditcard = {}; | |
| /** | |
| * Checks whether credit card number is valid. | |
| * @param {string} cardNumber Credit card number. | |
| * @return {Boolean} | |
| */ | |
| utils.creditcard.validateNumber = function(cardNumber) | |
| { | |
| // Function has to return boolean identifying whether credit | |
| // card number contains exactly 16 digits. | |
| return /^\d{16}$/.test(cardNumber); | |
| }; | |
| /** | |
| * Checks whether credit card security code is valid. | |
| * @param {string} securityCode Credit card security. | |
| * @return {Boolean} | |
| */ | |
| utils.creditcard.validateSecurityCode = function(securityCode) { | |
| // Function has to return boolean identifying whether credit | |
| // card security code (CVV/CVC) contains exactly 3 digits. | |
| return /^\d{3}$/.test(securityCode); | |
| }; | |
| /** | |
| * Checks whether credit card name is valid. | |
| * @param {string} name Credit card owner name. | |
| * @return {Boolean} | |
| */ | |
| utils.creditcard.validateName = function(name) { | |
| // Function has to return boolean identifying whether name on | |
| // the credit card contains at least 2 words with no digits. | |
| return /^[a-z]{2,}(\s[a-z]{2,}){1,}$/i.test(name); | |
| }; | |
| /** | |
| * Checks whether credit card expiration date is valid. | |
| * @param {string} expirationDate Credit card expiration date in | |
| * YYYY-MM format. | |
| * @return {Boolean} | |
| */ | |
| utils.creditcard.validateDate = function(expirationDate) { | |
| // Function has to return boolean identifying whether credit | |
| // card expiration date is at least next month. If today is April, | |
| // expiration date has to be at least May. | |
| var expirationDateArray = expirationDate.split('-', 2); | |
| var today = new Date(); | |
| var thisMonth = today.getUTCMonth() + 1; | |
| var thisYear = today.getFullYear(); | |
| var givenMonth = parseInt(expirationDateArray[1], 10); | |
| var givenYear = parseInt(expirationDateArray[0], 10); | |
| return(givenYear > thisYear) || (givenYear == thisYear) && (givenMonth > thisMonth); | |
| }; | |
| /** | |
| * @fileoverview Super simple Unit tests. If any outputs | |
| * 'false' function is not working as planned. | |
| * @author (Wind Tammer) | |
| */ | |
| console.log(utils.creditcard.validateNumber('4111111111111111') === true); | |
| console.log(utils.creditcard.validateNumber('2312312312') === false); | |
| console.log(utils.creditcard.validateNumber('411a343432dasd') === false); | |
| console.log(utils.creditcard.validateNumber('4111 1111 1111 1111') === false); | |
| console.log(utils.creditcard.validateNumber(4111111111111111) === false); | |
| console.log(utils.creditcard.validateSecurityCode('09') === false); | |
| console.log(utils.creditcard.validateSecurityCode('A12') === false); | |
| console.log(utils.creditcard.validateSecurityCode('1 2 3') === false); | |
| console.log(utils.creditcard.validateSecurityCode('347') === true); | |
| console.log(utils.creditcard.validateSecurityCode(347) === false); | |
| console.log(utils.creditcard.validateSecurityCode('347234234') === false); | |
| console.log(utils.creditcard.validateName('Nik Sumeiko') === true); | |
| console.log(utils.creditcard.validateName('NikSumeiko') === false); | |
| console.log(utils.creditcard.validateName('Nik Sumeiko II') === true); | |
| console.log(utils.creditcard.validateName('Nik Sumeiko 2') === false); | |
| console.log(utils.creditcard.validateName(' ') === false); | |
| console.log(utils.creditcard.validateDate('2014-04') === false); | |
| console.log(utils.creditcard.validateDate('2014-07') === true); | |
| console.log(utils.creditcard.validateDate('201404') === false); | |
| console.log(utils.creditcard.validateDate(201407) === false); | |
| console.log(utils.creditcard.validateDate('') === false); |
// Further comments on utils.creditcard.validateDate method:
You have successfully fixed the logic of the validation, and the function works correctly as required. However, as said before, would awesome to inline your IFELSE clause to make the source a bit shorter.
To help you translate IFELSE clause into one line, I'd suggest to define 2 new variables above:
...
var today = new Date(),
thisMonth = today.getUTCMonth() + 1,
givenMonth = parseInt(expirationDateArray[1], 10),
thisYear = today.getFullYear(),
givenYear = parseInt(expirationDateArray[0], 10);
if (givenYear === thisYear && givenMonth > thisMonth) {
return true;
} else if (givenYear > thisYear) {
return true;
} else {
return false;
}
With 2 additional variables and thisMonth normalisation (+1) directly in the variable definition, IFELSE clause becomes more readable, so you can translate it into one line easier.
The clue is to
- Understand what
typeofvalue logical operators return; - Try to see your IFELSE clause as 3 different parts and try to rewrite each part as a separate inline returnable boolean; then when you have each part, combine them together using logical operator(s).
// This is first part of your IFELSE clause:
if (givenYear === thisYear) {
return true;
}
// This is the second part:
if (givenYear > thisYear) {
return true;
}
// This is the third part (already an inline returnable boolean):
return false;
The last part is already an inline returnable boolean.
But can you try to rewrite first and second parts as inline returnable booleans as well? And then try to group them using appropriate logical operator in between:
return (returnable boolean) logical operator (returnable boolean).
I see you have finalised utils.creditcard.validateDate method. This means we have finished the exercise successfully. Congratulations!
Truly intelligent programmers reuse existing things a lot (even they are built by others). They invent new programs only when really required.
From now on you will be able to validate credit cards, when you have a project that requires credit card validation on the client. Keep your creations under control and reuse them.
// Further comments on
utils.creditcard.validateDatemethod:As mentioned lots of comments before, please do not hesitate to rely on annotation describing the function located above it. Professional programmers are trying to write basic documentation for their code, therefore JSDoc annotations are invented. So if annotation says
@param {string} expirationDate Credit card expiration date in YYYY-MM format, you can rely that the parameter given is going to be a YYYY-MM string and there's no additional code required to validate it once again. So testing given parameter to match regex pattern could be omitted. However, in your programmer carrier you will face many colleagues that avoid using annotations/documentation or carrying about value types. Just always remember, using these techniques really help make source code semantic, readable by other programmers and preventing unnecessary bugs in advance.Going further. The use of
parseIntfunction is wrong in the last part of the inline IF clause:To understand why it happens, we shall know the order of how JavaScript (actually, any programming language) interpreter executes the code. First it looks into parenthesis, seeing
'02' + 1expression inside. Interpreter tries to understand types of the values to both sides of plus+operator. In math plus operator is supposed to sum numbers so does programming language interpreter, but only if both values are numberstypeof value === 'number'. If one of the values is NOT a number, interpreter just concatenates these values converting both to strings:Then interpreter executes
parseInfunction with a concatenated string'021'that evaluates to number21.So if given month is 02 (February) you basically compare 21 to the current month.
As well, the logic of the inline IF clause
parseInt(expirationDateArray[0]) >= thisYear && parseInt(expirationDateArray[1] + 1) > thisMonth)is a bit wrong. What if given year is 2015, but given month is 2 what is less than current month (5):Here's a workflow to perform to fix the issue:
parseIntfunction works and its required params;&&operator.