Determines if a credit card number is valid and returns the name of the issuing network.
// cardInfo()
//
// Determines if a credit card number is valid and returns the name of the issuing network. When
// passed a purported card number as a candidate if the card number is valid, the cardInfo()
// function returns a boolean flag ('isValid') indicating if the card is a valid number and a
// string ('network') containing the name of the issuing network. Card validity is determined
// based upon the card number length and Luhn check digit value. Valid card lengths are based
// upon the card lengths valid for a given issuing network. The Luhn check digit is calculated
// using the Luhn algorithm. The function contains a static table ('IINData') that defines the
// supported issuing network. The table is not a complete list--it is only meant to contain
// the networks supported by the system using this function. But, it can easily be expanded
// too support additional networks. cardInfo() expects that the card number be passed as a
// string and will log a console error and return 'isValid' of false if this is not the case. The
// function does not return the card number itself in the object for safety, but does return the
// last four digits and a masked version as a conveinence.
//
function cardInfo(cardNum) {
// Issuer identification number (IIN) look-up table
// c.f. https://en.wikipedia.org/wiki/Payment_card_number
// Expand this table to support additional issuing networks. 'startIIN' and 'endIIN' define
// the range of IINs supported by an issuing network. 'name' defines the network's common
// name. And, 'lengths' is an array containing the supported card number lengths valid
// for the IIN range.
const IINData = [
{startIIN:"222100", endIIN:"272099", name:"MasterCard", lengths:[16]},
{startIIN:"340000", endIIN:"349999", name:"American Express", lengths:[15]},
{startIIN:"370000", endIIN:"379999", name:"American Express", lengths:[15]},
{startIIN:"400000", endIIN:"499999", name:"Visa", lengths:[13, 16, 19]},
{startIIN:"510000", endIIN:"559999", name:"MasterCard", lengths:[16]},
];
var cardInfo = {
fourDigits: cardNum.slice(-4),
masked: Array(cardNum.length - 4).join('*') + cardNum.slice(-4),
isValid: false
}
var IIN, IINIndex, lengthOk, luhnOk, sum, parity;
// Set card primary account number or exit if card number is not a string
if (typeof cardNum !== "string") {
console.error("'cardInfo()' passed an unexpected type (" + (typeof cardNum) + ") instead of a string");
cardInfo.errMessage = "Internal error: 'cardInfo()' passed an unexpected type";
return cardInfo;
}
// Get IIN or exit if PAN is less then absolute minimum length (6)
if (cardNum.length < 6) {
cardInfo.errMessage = "Card number is not long enough";
return cardInfo;
}
IIN = cardNum.substring(0, 5);
// Search IIN table and exit if an entry is not found
for (IINIndex = 0; IINIndex < IINData.length; IINIndex++) {
if (IINData[IINIndex].startIIN <= IIN && IIN <= IINData[IINIndex].endIIN) {
break;
}
}
if (IINIndex === IINData.length) {
cardInfo.errMessage = "Issuing network is not supported"
return cardInfo;
}
cardInfo.network = IINData[IINIndex].name;
// Check length requirements and exit if length is too short for issuing network
lengthOk = false;
for (var i = 0; i < IINData[IINIndex].lengths.length && !lengthOk; i++) {
lengthOk = (IINData[IINIndex].lengths[i] === cardNum.length);
}
if (!lengthOk) {
cardInfo.errMessage = IINData[IINIndex].name + " card numbers must be " + IINData[IINIndex].lengths + " digits in length";
return cardInfo;
}
// Check Luhn
// c.f. https://en.wikipedia.org/wiki/Luhn_algorithm
sum = 0;
parity = cardNum.length % 2;
for (var i = 0; i < cardNum.length - 1; i++) {
let doubleDigit = (((i % 2) === parity) ? 2 : 1) * cardNum[i];
let sumOfDigits = (Math.floor(doubleDigit / 10) + doubleDigit % 10);
sum += sumOfDigits;
}
luhnOk = (String(9 * sum).slice(-1) === cardNum.slice(-1));
if (!luhnOk) {
cardInfo.errMessage = "Card number is invalid (incorrect Luhn value)";
return;
}
// Card is valid if both the length is correct and the Luhn check is valid
cardInfo.isValid = lengthOk && luhnOk;
// Return card
return cardInfo;
}
function test(cardNum, expectedResult) {
card = cardInfo(cardNum);
result = (card.cardNum + " " + card.isValid + " " + card.IIN);
return (result === expectedResult);
}
function runTests() {
tests = [
{card:undefined, result:"undefined false undefined"},
{card:"378282246310005", result:"378282246310005 true 37828"}
];
for (var i = 0; i < tests.length; i++) {
testNum = (1000+i).toString().substring(1);
if (test(tests[i].card, tests[i].result)) {
console.log(testNum + ': "' + tests[i].card + '": Pass');
} else {
console.log(testNum + ': "' + tests[i].card + '": Fail (' + tests[i].result + ')');
}
}
}