legacy/src/vms/credit-card-vm.js
import _ from 'underscore';
import prop from 'mithril/stream';
const { CatarseAnalytics } = window;
const defaultFormat = /(\d{1,4})/g;
const slice = [].slice,
indexOf = [].indexOf || function (item) { for (let i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; };
const cards = [
{
type: 'elo',
patterns: [401178, 401179, 431274, 438935, 451416, 457393, 457631, 457632, 504175, 506699, 5067, 509, 627780, 636297, 636368, 650, 6516, 6550],
format: defaultFormat,
length: [16],
cvvLength: [3],
luhn: true
}, {
type: 'maestro',
patterns: [5018, 502, 503, 506, 56, 58, 639, 6220, 67],
format: defaultFormat,
length: [12, 13, 14, 15, 16, 17, 18, 19],
cvvLength: [3],
luhn: true
}, {
type: 'forbrugsforeningen',
patterns: [600],
format: defaultFormat,
length: [16],
cvvLength: [3],
luhn: true
}, {
type: 'dankort',
patterns: [5019],
format: defaultFormat,
length: [16],
cvvLength: [3],
luhn: true
}, {
type: 'visa',
patterns: [4],
format: defaultFormat,
length: [13, 16],
cvvLength: [3],
luhn: true
}, {
type: 'mastercard',
patterns: [51, 52, 53, 54, 55, 22, 23, 24, 25, 26, 27],
format: defaultFormat,
length: [16],
cvvLength: [3],
luhn: true
}, {
type: 'amex',
patterns: [34, 37],
format: /(\d{1,4})(\d{1,6})?(\d{1,5})?/,
length: [15],
cvvLength: [3, 4],
luhn: true
}, {
type: 'dinersclub',
patterns: [30, 36, 38, 39],
format: /(\d{1,4})(\d{1,6})?(\d{1,4})?/,
length: [14],
cvvLength: [3],
luhn: true
}, {
type: 'discover',
patterns: [60, 64, 65, 622],
format: defaultFormat,
length: [16],
cvvLength: [3],
luhn: true
}, {
type: 'unionpay',
patterns: [62, 88],
format: defaultFormat,
length: [16, 17, 18, 19],
cvvLength: [3],
luhn: false
}, {
type: 'jcb',
patterns: [35],
format: defaultFormat,
length: [16],
cvvLength: [3],
luhn: true
}
];
const inputCardType = (num) => {
let ref;
if (!num) {
return null;
}
return ((ref = cardFromNumber(num)) != null ? ref.type : void 0) || null;
};
const cardFromType = (type) => {
let card,
i,
len;
for (i = 0, len = cards.length; i < len; i++) {
card = cards[i];
if (card.type === type) {
return card;
}
}
};
const setCardType = (e, type) => {
let $target,
allTypes,
card,
cardType,
val;
$target = e.currentTarget;
val = $target.value;
cardType = inputCardType(val) || 'unknown';
return type(cardType);
};
const formatBackCardNumber = (e, cardNumberProp) => {
let $target,
value;
$target = e.currentTarget;
value = $target.value;
if (e.which !== 8) {
return;
}
if (($target.selectionStart != null) && $target.selectionStart !== value.length) {
return;
}
if (/\d\s$/.test(value)) {
e.preventDefault();
return setTimeout(() => $target.value = cardNumberProp(value.replace(/\d\s$/, '')));
} else if (/\s\d?$/.test(value)) {
e.preventDefault();
return setTimeout(() => $target.value = cardNumberProp(value.replace(/\d$/, '')));
}
};
const replaceFullWidthChars = (str) => {
let chars,
chr,
fullWidth,
halfWidth,
i,
idx,
len,
value;
if (str == null) {
str = '';
}
fullWidth = '\uff10\uff11\uff12\uff13\uff14\uff15\uff16\uff17\uff18\uff19';
halfWidth = '0123456789';
value = '';
chars = str.split('');
for (i = 0, len = chars.length; i < len; i++) {
chr = chars[i];
idx = fullWidth.indexOf(chr);
if (idx > -1) {
chr = halfWidth[idx];
}
value += chr;
}
return value;
};
const safeVal = (value, $target, cardNumberProp) => {
let currPair,
cursor,
digit,
error,
error1,
last,
prevPair;
try {
cursor = $target.selectionStart;
} catch (error1) {
error = error1;
cursor = null;
}
last = $target.value;
$target.value = cardNumberProp(value);
if (cursor !== null && ($target === document.activeElement)) {
if (cursor === last.length) {
cursor = value.length;
}
if (last !== value) {
prevPair = last.slice(cursor - 1, +cursor + 1 || 9e9);
currPair = value.slice(cursor - 1, +cursor + 1 || 9e9);
digit = value[cursor];
if (/\d/.test(digit) && prevPair === (`${digit} `) && currPair === (` ${digit}`)) {
cursor += 1;
}
}
$target.selectionStart = cursor;
return $target.selectionEnd = cursor;
}
};
const reFormatCardNumber = (e, cardNumberProp) => {
const $target = e.currentTarget;
return setTimeout(() => {
let value;
value = $target.value;
value = replaceFullWidthChars(value);
value = formatCardNumber(value);
return safeVal(value, $target, cardNumberProp);
});
};
const formatCardNumber = function (num) {
let card,
groups,
ref,
upperLength;
num = num.replace(/\D/g, '');
card = cardFromNumber(num);
if (!card) {
return num;
}
upperLength = card.length[card.length.length - 1];
num = num.slice(0, upperLength);
if (card.format.global) {
return (ref = num.match(card.format)) != null ? ref.join(' ') : void 0;
}
groups = card.format.exec(num);
if (groups == null) {
return;
}
groups.shift();
groups = _.filter(groups, n => n);
return groups.join(' ');
};
const formatCardInputNumber = (e, cardNumberProp) => {
let $target,
card,
digit,
length,
re,
upperLength,
value;
digit = String.fromCharCode(e.which);
if (!/^\d+$/.test(digit)) {
return;
}
$target = e.currentTarget;
value = $target.value;
card = cardFromNumber(value + digit);
length = (value.replace(/\D/g, '') + digit).length;
upperLength = 16;
if (card) {
upperLength = card.length[card.length.length - 1];
}
if (length >= upperLength) {
return;
}
if (($target.selectionStart != null) && $target.selectionStart !== value.length) {
return;
}
if (card && card.type === 'amex') {
re = /^(\d{4}|\d{4}\s\d{6})$/;
} else {
re = /(?:^|\s)(\d{4})$/;
}
if (re.test(value)) {
e.preventDefault();
return setTimeout(() => $target.value = cardNumberProp(`${value} ${digit}`));
} else if (re.test(value + digit)) {
e.preventDefault();
return setTimeout(() => $target.value = cardNumberProp(`${value + digit} `));
}
};
const cardFromNumber = (num) => {
let card,
i,
j,
len,
len1,
p,
pattern,
ref;
num = (`${num}`).replace(/\D/g, '');
for (i = 0, len = cards.length; i < len; i++) {
card = cards[i];
ref = card.patterns;
for (j = 0, len1 = ref.length; j < len1; j++) {
pattern = ref[j];
p = `${pattern}`;
if (num.substr(0, p.length) === p) {
return card;
}
}
}
};
const hasTextSelected = ($target) => {
let ref;
if (($target.selectionStart != null) && $target.selectionStart !== $target.selectionEnd) {
return true;
}
if ((typeof document !== 'undefined' && document !== null ? (ref = document.selection) != null ? ref.createRange : void 0 : void 0) != null) {
if (document.selection.createRange().text) {
return true;
}
}
return false;
};
const restrictNumeric = (e) => {
let input;
if (e.metaKey || e.ctrlKey) {
return true;
}
if (e.which === 32) {
return false;
}
if (e.which === 0) {
return true;
}
if (e.which < 33) {
return true;
}
input = String.fromCharCode(e.which);
return !!/[\d\s]/.test(input);
};
const restrictCardNumber = (e) => {
let $target,
card,
digit,
value;
$target = e.currentTarget;
digit = String.fromCharCode(e.which);
if (!/^\d+$/.test(digit)) {
return;
}
if (hasTextSelected($target)) {
return;
}
value = ($target.value + digit).replace(/\D/g, '');
card = cardFromNumber(value);
if (card) {
return value.length <= card.length[card.length.length - 1];
}
return value.length <= 16;
};
const setEvents = (el, cardType, cardNumberProp) => {
el.onkeypress = (event) => {
restrictNumeric(event);
restrictCardNumber(event);
formatCardInputNumber(event, cardNumberProp);
};
el.oninput = (event) => {
reFormatCardNumber(event, cardNumberProp);
setCardType(event, cardType);
};
el.onkeydown = event => formatBackCardNumber(event, cardNumberProp);
el.onkeyup = (event) => {
setCardType(event, cardType);
};
el.onpaste = event => reFormatCardNumber(event, cardNumberProp);
el.onchange = (event) => {
CatarseAnalytics.oneTimeEvent({ cat: 'contribution_finish', act: 'contribution_cc_edit' });
reFormatCardNumber(event, cardNumberProp);
};
};
const luhnCheck = (num) => {
let digit,
digits,
i,
len,
odd,
sum;
odd = true;
sum = 0;
digits = (`${num}`).split('').reverse();
for (i = 0, len = digits.length; i < len; i++) {
digit = digits[i];
digit = parseInt(digit, 10);
if ((odd = !odd)) {
digit *= 2;
}
if (digit > 9) {
digit -= 9;
}
sum += digit;
}
return sum % 10 === 0;
};
const validateCardNumber = function (num) {
let card,
ref;
num = (`${num}`).replace(/\s+|-/g, '');
if (!/^\d+$/.test(num)) {
return false;
}
card = cardFromNumber(num);
if (!card) {
return false;
}
return (ref = num.length, indexOf.call(card.length, ref) >= 0) && (card.luhn === false || luhnCheck(num));
};
const validateCardExpiry = function (month, year) {
let currentTime,
expiry,
ref;
if (typeof month === 'object' && 'month' in month) {
ref = month, month = ref.month, year = ref.year;
}
if (!(month && year)) {
return false;
}
month = String(month).trim();
year = String(year).trim();
if (!/^\d+$/.test(month)) {
return false;
}
if (!/^\d+$/.test(year)) {
return false;
}
if (!((month >= 1 && month <= 12))) {
return false;
}
if (year.length === 2) {
if (year < 70) {
year = `20${year}`;
} else {
year = `19${year}`;
}
}
if (year.length !== 4) {
return false;
}
expiry = new Date(year, month);
currentTime = new Date();
expiry.setMonth(expiry.getMonth() - 1);
expiry.setMonth(expiry.getMonth() + 1, 1);
return expiry > currentTime;
};
const validateCardcvv = function (cvv, type) {
let card,
ref;
cvv = String(cvv).trim();
if (!/^\d+$/.test(cvv)) {
return false;
}
card = cardFromType(type);
if (card != null) {
return ref = cvv.length, indexOf.call(card.cvvLength, ref) >= 0;
}
return cvv.length >= 3 && cvv.length <= 4;
};
const creditCardVM = {
setEvents,
validateCardNumber,
validateCardcvv,
validateCardExpiry
};
export default creditCardVM;