Skip to content

Commit

Permalink
Merge branch 'master' into adyen_3ds
Browse files Browse the repository at this point in the history
  • Loading branch information
isaacvance1027 authored Jul 19, 2022
2 parents d75dd59 + 75f5109 commit 526cf3f
Show file tree
Hide file tree
Showing 3 changed files with 252 additions and 137 deletions.
180 changes: 180 additions & 0 deletions lib/const/credit-card-types.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
{
"master": [
{
"lengths": [16],
"ranges": [
[2221, 2720],
[51, 55]
]
}
],
"diners_club": [
{
"lengths": [14],
"ranges": [
[300, 305],
[36, 36],
[38, 38]
]
}
],
"american_express": [
{
"lengths": [15],
"ranges": [
[34, 34],
[37, 37]
]
}
],
"jcb": [
{
"lengths": [16],
"ranges": [
[3528, 3589]
]
}
],
"hipercard": [
{
"lengths": [19],
"ranges": [
[3841, 3841],
[606282, 606282]
]
},
{
"lengths": [16, 17, 18],
"ranges": [
[606282, 606282]
]
}
],
"visa": [
{
"lengths": [13, 16],
"ranges": [
[4, 4]
]
}
],
"elo": [
{
"lengths": [16],
"ranges": [
[504175, 504175],
[5066, 5067],
[636297, 636297],
[636368, 636368]
]
}
],
"tarjeta_naranja": [
{
"lengths": [16, 17, 18, 19],
"ranges": [
[589562, 589562]
]
}
],
"discover": [
{
"lengths": [16, 17, 18, 19],
"ranges": [
[601100, 601103],
[601105, 601109],
[60112, 60114],
[601174, 601174],
[601177, 601179],
[601186, 601199],
[6440, 6505],
[650601, 650609],
[650611, 659999]
]
}
],
"union_pay": [
{
"lengths": [16, 17, 18, 19],
"ranges": [
[62000, 62182],
[62184, 62197],
[6220, 6270],
[6272, 6272],
[62760, 62777],
[627781, 627799],
[6282, 6289],
[6291, 6292],
[8100, 8171]
]
}
],
"maestro": [
{
"lengths": [12, 13, 14, 15],
"ranges": [
[50, 50],
[56, 58],
[6, 6]
]
},
{
"lengths": [16],
"ranges": [
[500000, 504174],
[504176, 506599],
[5068, 5099],
[560000, 589561],
[589563, 589999],
[6000, 6010],
[601104, 601104],
[60111, 60111],
[601150, 601173],
[601175, 601176],
[601180, 601185],
[601200, 606281],
[606283, 619999],
[62183, 62183],
[62198, 62199],
[6271, 6271],
[6273, 6275],
[627780, 627780],
[6278, 6281],
[6290, 6290],
[629300, 636296],
[636298, 636367],
[636369, 643999],
[650600, 650600],
[650610, 650610],
[66, 69]
]
},
{
"lengths": [17, 18, 19],
"ranges": [
[50, 50],
[560000, 589561],
[589563, 589999],
[6000, 6010],
[601104, 601104],
[60111, 60111],
[601150, 601173],
[601175, 601176],
[601180, 601185],
[601200, 606281],
[606283, 619999],
[62183, 62183],
[62198, 62199],
[6271, 6271],
[6273, 6275],
[627780, 627780],
[6278, 6281],
[6290, 6290],
[6293, 6439],
[650600, 650600],
[650610, 650610],
[66, 69]
]
}
]
}
179 changes: 49 additions & 130 deletions lib/recurly/validate.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,135 +4,10 @@ import { FIELDS as CARD_FIELDS } from './token';
import each from 'component-each';
import find from 'component-find';
import { parseCard } from '../util/parse-card';
import CREDIT_CARD_TYPES from '../const/credit-card-types.json';

const debug = require('debug')('recurly:validate');

/**
* Card patterns.
*
* @private
*/
const TYPES = [
{
type: 'discover',
pattern: {
test: (n) => {
const firstEight = parseInt(n.substr(0, 8), 10);
const acceptedRanges = [
[60110000, 60110399],
[60110500, 60110999],
[60112000, 60114999],
[60117400, 60117499],
[60117700, 60117999],
[60118600, 60119999],
[64400000, 65059999],
[65060100, 65060999],
[65061100, 65999999]
];

if (!(/^6[045]/.test(firstEight))) return false;
return validBinRange(acceptedRanges, firstEight);
}
},
lengths: [16, 17, 18, 19]
},
{
type: 'union_pay',
pattern: {
test: (n) => {
const firstEight = parseInt(n.substr(0, 8), 10);
const acceptedRanges = [
[62000000, 62099999],
[62100000, 62182999],
[62184000, 62197999],
[62200000, 62699999],
[62700000, 62709999],
[62720000, 62729999],
[62760000, 62769999],
[62770000, 62777999],
[62778100, 62779999],
[62820000, 62899999],
[62910000, 62929999],
[81000000, 81719999]
];

if (!(/^6[2]/.test(firstEight)) && !(/^8[1]/.test(firstEight))) return false;
return validBinRange(acceptedRanges, firstEight);
}
},
lengths: [16, 17, 18, 19]
},
{
type: 'master',
pattern: {
test: (n) => {
let firstSix = parseInt(n.substr(0, 6), 10);
if (/^5[1-5]/.test(n)) return true;
if (firstSix >= 222100 && firstSix <= 272099) return true;
return false;
}
},
lengths: [16]
},
{
type: 'american_express',
pattern: /^3[47]/,
lengths: [15]
},
{
type: 'elo',
pattern: /^(636368|504175|636297|506[67]\d\d)\d{0,10}$/,
lengths: [13, 16]
},
{
type: 'visa',
pattern: /^4/,
lengths: [13, 16]
},
{
type: 'jcb',
pattern: /^35[2-8]\d/,
lengths: [16]
},
{
type: 'diners_club',
pattern: /^(30[0-5]|309|36|3[89]|54|55|2014|2149)/,
lengths: [14]
},
{
type: 'hipercard',
pattern: /^(606282\d{10}(\d{3})?)|(3841\d{15})$/,
lengths: [16]
},
{
type: 'tarjeta_naranja',
pattern: {
test: (n) => {
const firstEight = parseInt(n.substr(0, 8), 10);
const acceptedRanges = [
[58956200, 58956299]
];

return validBinRange(acceptedRanges, firstEight);
}
},
lengths: [16, 17, 18, 19]
}
];

/**
* Validates bin ranges for supported card types
*
* @private
*/
function validBinRange (acceptedRanges, firstEight) {
for (let index = 0; index < acceptedRanges.length; index++) {
const [min, max] = acceptedRanges[index];
if (firstEight <= max && firstEight >= min) return true;
}
return false;
}

/**
* Validation error messages
* @type {String}
Expand Down Expand Up @@ -212,6 +87,27 @@ export function cardNumber (number) {
return sum % 10 === 0 && sum > 0;
}


/**
* Converts a number to another number to be used in comparison.
* For example, it converts 38 to 3800 (or 3899), suitable for range
* comparisons.
* @param {Integer} start The range start
* @param {Integer} length Size of the desired range item
* @param {String} terminator The char used for padding a smaller range
* @returns {Integer} A range item integer with `length` digits
*/
function buildCompareValue (start, length, terminator) {
let result = start.toString().substr(0, length);

// This can be replaced by padEnd after drop IE11 support
while (result.length < length) {
result = result + terminator;
}

return parseInt(result);
}

/**
* Returns the type of the card number as a string.
*
Expand All @@ -221,11 +117,34 @@ export function cardNumber (number) {
*/

export function cardType (number, partial = false) {
const str = parseCard(number);
const len = str.length;
const card = find(TYPES, card => card.pattern.test(str) && (partial || ~card.lengths.indexOf(len)));
const cardNumber = parseCard(number);
const compareLength = Math.min(cardNumber.length, 6);

const compareValue = buildCompareValue(cardNumber, compareLength, '0');

const types = Object.keys(CREDIT_CARD_TYPES).filter((type) => {
if (partial && type == 'maestro') {
// Maestro has a wide range (6*) that overlaps with some other types,
// which can be disambiguated only when the full lenght is given
return;
}

return find(CREDIT_CARD_TYPES[type], ((group) => {
if (!partial && group.lengths.indexOf(cardNumber.length) < 0) {
return false;
}

return find(group.ranges, ([rangeBegin, rangeEnd]) => {
const start = buildCompareValue(rangeBegin, compareLength, '0');
const end = buildCompareValue(rangeEnd, compareLength, '9');

return compareValue >= start && compareValue <= end;
});
}));
});

return card && card.type || 'unknown';
// Ignoring multiple matches because partials can match multiple ranges
return types.length == 1 && types[0] || 'unknown';
}

/**
Expand Down
Loading

0 comments on commit 526cf3f

Please sign in to comment.