A web browser extension that emulates Content Delivery Networks to improve your online privacy. It intercepts traffic, finds supported resources locally, and injects them into the environment. https://www.localcdn.org/
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

881 lines
20 KiB

angular.module('angularPayments', []);;angular.module('angularPayments')
.factory('Common', [function(){
var ret = {};
// expiry is a string "mm / yy[yy]"
ret.parseExpiry = function(value){
var month, prefix, year, _ref;
value = value || '';
value = value.replace(/\s/g, '');
_ref = value.split('/', 2);
month = _ref[0];
year = _ref[1];
if (year && year.length === 2 && /^\d+$/.test(year)) {
prefix = (new Date()).getFullYear();
prefix = prefix.toString().slice(0, 2);
year = prefix + year;
}
month = parseInt(month, 10);
year = parseInt(year, 10);
return {
month: month,
year: year
};
};
return ret;
}]);
;angular.module('angularPayments')
.factory('Cards', [function(){
var defaultFormat = /(\d{1,4})/g;
var defaultInputFormat = /(?:^|\s)(\d{4})$/;
var cards = [
{
type: 'maestro',
pattern: /^(5018|5020|5038|6304|6759|676[1-3])/,
format: defaultFormat,
inputFormat: defaultInputFormat,
length: [12, 13, 14, 15, 16, 17, 18, 19],
cvcLength: [3],
luhn: true
}, {
type: 'dinersclub',
pattern: /^(36|38|30[0-5])/,
format: defaultFormat,
inputFormat: defaultInputFormat,
length: [14],
cvcLength: [3],
luhn: true
}, {
type: 'laser',
pattern: /^(6706|6771|6709)/,
format: defaultFormat,
inputFormat: defaultInputFormat,
length: [16, 17, 18, 19],
cvcLength: [3],
luhn: true
}, {
type: 'jcb',
pattern: /^35/,
format: defaultFormat,
inputFormat: defaultInputFormat,
length: [16],
cvcLength: [3],
luhn: true
}, {
type: 'unionpay',
pattern: /^62/,
format: defaultFormat,
inputFormat: defaultInputFormat,
length: [16, 17, 18, 19],
cvcLength: [3],
luhn: false
}, {
type: 'discover',
pattern: /^(6011|65|64[4-9]|622)/,
format: defaultFormat,
inputFormat: defaultInputFormat,
length: [16],
cvcLength: [3],
luhn: true
}, {
type: 'mastercard',
pattern: /^5[1-5]/,
format: defaultFormat,
inputFormat: defaultInputFormat,
length: [16],
cvcLength: [3],
luhn: true
}, {
type: 'amex',
pattern: /^3[47]/,
format: /(\d{1,4})(\d{1,6})?(\d{1,5})?/,
inputFormat: /^(\d{4}|\d{4}\s\d{6})$/,
length: [15],
cvcLength: [3, 4],
luhn: true
}, {
type: 'visa',
pattern: /^4/,
format: defaultFormat,
inputFormat: defaultInputFormat,
length: [13, 14, 15, 16],
cvcLength: [3],
luhn: true
}
];
var _fromNumber = function(num){
var card, i, len;
num = (num + '').replace(/\D/g, '');
for (i = 0, len = cards.length; i < len; i++) {
card = cards[i];
if (card.pattern.test(num)) {
return card;
}
}
};
var _fromType = function(type) {
var card, i, len;
for (i = 0, len = cards.length; i < len; i++) {
card = cards[i];
if (card.type === type) {
return card;
}
}
};
return {
fromNumber: function(val) { return _fromNumber(val); },
fromType: function(val) { return _fromType(val); },
defaultFormat: function() { return defaultFormat; },
defaultInputFormat: function() { return defaultInputFormat; }
};
}]);
;/**
* Format
*/
angular.module('angularPayments')
.factory('_Format', ['Cards', 'Common', '$filter', function(Cards, Common, $filter){
var _formats = {};
var _hasTextSelected = function($target) {
var ref;
if (($target.prop('selectionStart') !== null) && $target.prop('selectionStart') !== $target.prop('selectionEnd')) {
return true;
}
if (document.selection) {
return true;
}
return false;
};
// card formatting
var isInvalidKey = function(e) {
var digit = String.fromCharCode(e.which);
return !/^\d+$/.test(digit) && !e.metaKey && e.charCode !== 0 && !e.ctrlKey;
};
var _formatCardNumber = function(e) {
var $target, card, digit, length, re, upperLength, value;
digit = String.fromCharCode(e.which);
$target = angular.element(e.currentTarget);
value = $target.val();
card = Cards.fromNumber(value + digit);
length = (value.replace(/\D/g, '') + digit).length;
upperLength = 16;
// Catch delete, tab, backspace, arrows, etc..
if (e.which === 8 || e.which === 0) {
return;
}
if (card) {
upperLength = card.length[card.length.length - 1];
}
if (isInvalidKey(e)) {
e.preventDefault();
return;
}
if (($target.prop('selectionStart') !== null) && $target.prop('selectionStart') !== value.length) {
return;
}
re = Cards.defaultInputFormat();
if (card) {
re = card.inputFormat;
}
if (length >= upperLength) {
return;
}
if (re.test(value)) {
e.preventDefault();
return $target.val(value + ' ' + digit);
} else if (re.test(value + digit)) {
e.preventDefault();
return $target.val(value + digit + ' ');
}
};
var _restrictCardNumber = function(e) {
var $target, card, digit, value;
$target = angular.element(e.currentTarget);
digit = String.fromCharCode(e.which);
// Catch delete, tab, backspace, arrows, etc..
if (e.which === 8 || e.which === 0) {
return;
}
if(!/^\d+$/.test(digit)) {
e.preventDefault();
return;
}
if(_hasTextSelected($target)) {
return;
}
value = ($target.val() + digit).replace(/\D/g, '');
card = Cards.fromNumber(value);
if(card) {
if(value.length > card.length[card.length.length - 1]){
e.preventDefault();
}
} else {
if(value.length > 16){
e.preventDefault();
}
}
};
var _formatBackCardNumber = function(e) {
var $target, value;
$target = angular.element(e.currentTarget);
value = $target.val();
if(e.metaKey) {
return;
}
if(e.which !== 8) {
return;
}
if(($target.prop('selectionStart') !== null) && $target.prop('selectionStart') !== value.length) {
return;
}
if(/\d\s$/.test(value) && !e.metaKey && e.keyCode >= 46) {
e.preventDefault();
return $target.val(value.replace(/\d\s$/, ''));
} else if (/\s\d?$/.test(value)) {
e.preventDefault();
return $target.val(value.replace(/\s\d?$/, ''));
}
};
var _getFormattedCardNumber = function(num) {
var card, groups, upperLength, ref;
card = Cards.fromNumber(num);
if (!card) {
return num;
}
upperLength = card.length[card.length.length - 1];
num = num.replace(/\D/g, '');
num = num.slice(0, +upperLength + 1 || 9e9);
if(card.format.global) {
return (ref = num.match(card.format)) !== null ? ref.join(' ') : void 0;
} else {
groups = card.format.exec(num);
if (groups !== null) {
groups.shift();
}
return groups !== null ? groups.join(' ') : void 0;
}
};
var _reFormatCardNumber = function(e) {
return setTimeout(function() {
var $target, value;
$target = angular.element(e.target);
value = $target.val();
value = _getFormattedCardNumber(value);
return $target.val(value);
});
};
var _parseCardNumber = function(value) {
return value !== null && value !== undefined ? value.replace(/\s/g, '') : value;
};
_formats.card = function(elem, ctrl){
elem.bind('keypress', _restrictCardNumber);
elem.bind('keypress', _formatCardNumber);
elem.bind('keydown', _formatBackCardNumber);
elem.bind('paste', _reFormatCardNumber);
ctrl.$parsers.push(_parseCardNumber);
ctrl.$formatters.push(_getFormattedCardNumber);
};
// cvc
var _formatCVC = function(e){
var $target, digit, value;
$target = angular.element(e.currentTarget);
digit = String.fromCharCode(e.which);
// Catch delete, tab, backspace, arrows, etc..
if (e.which === 8 || e.which === 0) {
return;
}
if (isInvalidKey(e)) {
e.preventDefault();
return;
}
if(_hasTextSelected($target)) {
return;
}
value = $target.val() + digit;
if(value.length <= 4){
return;
} else {
e.preventDefault();
return;
}
};
_formats.cvc = function(elem){
elem.bind('keypress', _formatCVC);
};
// expiry
var _restrictExpiry = function(e) {
var $target, digit, value;
$target = angular.element(e.currentTarget);
digit = String.fromCharCode(e.which);
if (isInvalidKey(e)) {
e.preventDefault();
return;
}
if(_hasTextSelected($target)) {
return;
}
value = $target.val() + digit;
value = value.replace(/\D/g, '');
if (value.length > 6) {
e.preventDefault();
return;
}
};
var _formatExpiry = function(e) {
var $target, digit, val;
digit = String.fromCharCode(e.which);
if (isInvalidKey(e)) {
e.preventDefault();
return;
}
$target = angular.element(e.currentTarget);
val = $target.val() + digit;
if (/^\d$/.test(val) && (val !== '0' && val !== '1')) {
e.preventDefault();
return $target.val("0" + val + " / ");
} else if (/^\d\d$/.test(val)) {
e.preventDefault();
return $target.val("" + val + " / ");
}
};
var _formatForwardExpiry = function(e) {
var $target, digit, val;
digit = String.fromCharCode(e.which);
if (isInvalidKey(e)) {
return;
}
$target = angular.element(e.currentTarget);
val = $target.val();
if (/^\d\d$/.test(val)) {
return $target.val("" + val + " / ");
}
};
var _formatForwardSlash = function(e) {
var $target, slash, val;
slash = String.fromCharCode(e.which);
if (slash !== '/') {
return;
}
$target = angular.element(e.currentTarget);
val = $target.val();
if (/^\d$/.test(val) && val !== '0') {
return $target.val("0" + val + " / ");
}
};
var _formatBackExpiry = function(e) {
var $target, value;
if (e.meta || e.metaKey) {
return;
}
$target = angular.element(e.currentTarget);
value = $target.val();
if (e.which !== 8) {
return;
}
if (($target.prop('selectionStart') !== null) && $target.prop('selectionStart') !== value.length) {
return;
}
if (/\d(\s|\/)+$/.test(value)) {
e.preventDefault();
return $target.val(value.replace(/\d(\s|\/)*$/, ''));
} else if (/\s\/\s?\d?$/.test(value)) {
e.preventDefault();
return $target.val(value.replace(/\s\/\s?\d?$/, ''));
}
};
var _parseExpiry = function(value) {
if(value !== null) {
var obj = Common.parseExpiry(value);
var expiry = new Date(obj.year, obj.month-1);
return $filter('date')(expiry, 'MM/yyyy');
}
return null;
};
var _getFormattedExpiry = function(value) {
if(value !== null) {
var obj = Common.parseExpiry(value);
var expiry = new Date(obj.year, obj.month-1);
return $filter('date')(expiry, 'MM / yyyy');
}
return null;
};
_formats.expiry = function(elem, ctrl){
elem.bind('keypress', _restrictExpiry);
elem.bind('keypress', _formatExpiry);
elem.bind('keypress', _formatForwardSlash);
elem.bind('keypress', _formatForwardExpiry);
elem.bind('keydown', _formatBackExpiry);
ctrl.$parsers.push(_parseExpiry);
ctrl.$formatters.push(_getFormattedExpiry);
};
return function(type, elem, ctrl){
var types, errstr;
if(!_formats[type]){
types = Object.keys(_formats);
errstr = 'Unknown type for formatting: "'+type+'". ';
errstr += 'Should be one of: "'+types.join('", "')+'"';
throw errstr;
}
return _formats[type](elem, ctrl);
};
}])
.directive('paymentsFormat', ['$window', '_Format', function($window, _Format){
return {
restrict: 'A',
require: 'ngModel',
link: function(scope, elem, attr, ctrl){
_Format(attr.paymentsFormat, elem, ctrl);
}
};
}]);
;angular.module('angularPayments')
.factory('_Validate', ['Cards', 'Common', '$parse', function(Cards, Common, $parse){
var __indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) { return i; } } return -1; };
var _luhnCheck = function(num) {
var digit, digits, odd, sum, i, len;
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;
};
var _validators = {};
_validators.cvc = function(cvc, ctrl, scope, attr){
var ref, ref1;
// valid if empty - let ng-required handle empty
if(!cvc) {
return true;
}
if (!/^\d+$/.test(cvc)) {
return false;
}
var type;
if(attr.paymentsTypeModel) {
var typeModel = $parse(attr.paymentsTypeModel);
type = typeModel(scope);
}
if (type) {
return ref = cvc.length, __indexOf.call((ref1 = Cards.fromType(type)) !== null ? ref1.cvcLength : void 0, ref) >= 0;
} else {
return cvc.length >= 3 && cvc.length <= 4;
}
};
_validators.card = function(num, ctrl, scope, attr){
var card, ref, typeModel, ret;
if(attr.paymentsTypeModel) {
typeModel = $parse(attr.paymentsTypeModel);
}
var clearCard = function(){
if(typeModel) {
typeModel.assign(scope, null);
}
ctrl.$card = null;
};
// valid if empty - let ng-required handle empty
if(!num){
clearCard();
return true;
}
num = (num + '').replace(/\s+|-/g, '');
if (!/^\d+$/.test(num)) {
clearCard();
return false;
}
card = Cards.fromNumber(num);
if(!card) {
clearCard();
return false;
}
ctrl.$card = angular.copy(card);
if(typeModel) {
typeModel.assign(scope, card.type);
}
var length = 16;
switch (card.type) {
case 'amex':
length = 15;
break;
}
ret = (ref = num.length, __indexOf.call(card.length, ref) >= 0) && num.length === length && (card.luhn === false || _luhnCheck(num));
return ret;
};
_validators.expiry = function(val){
var month, year, obj;
// valid if empty - let ng-required handle empty
if(!val) return true;
obj = Common.parseExpiry(val);
month = obj.month;
year = obj.year;
var currentTime, expiry, prefix;
if (!(month && year)) {
return false;
}
if (!/^\d+$/.test(month)) {
return false;
}
if (!/^\d+$/.test(year)) {
return false;
}
if (parseInt(month, 10) > 12) {
return false;
}
if (year.length === 2) {
prefix = (new Date()).getFullYear();
prefix = prefix.toString().slice(0, 2);
year = prefix + year;
}
expiry = new Date(year, month);
currentTime = new Date();
expiry.setMonth(expiry.getMonth() - 1);
expiry.setMonth(expiry.getMonth() + 1, 1);
return expiry > currentTime;
};
return function(type, val, ctrl, scope, attr){
var types, errstr;
if(!_validators[type]){
types = Object.keys(_validators);
errstr = 'Unknown type for validation: "'+type+'". ';
errstr += 'Should be one of: "'+types.join('", "')+'"';
throw errstr;
}
return _validators[type](val, ctrl, scope, attr);
};
}])
.factory('_ValidateWatch', ['_Validate', function(_Validate){
var _validatorWatches = {};
_validatorWatches.cvc = function(type, ctrl, scope, attr){
if(attr.paymentsTypeModel) {
scope.$watch(attr.paymentsTypeModel, function(newVal, oldVal) {
if(newVal !== oldVal) {
var valid = _Validate(type, ctrl.$modelValue, ctrl, scope, attr);
ctrl.$setValidity(type, valid);
}
});
}
};
return function(type, ctrl, scope, attr){
if(_validatorWatches[type]){
return _validatorWatches[type](type, ctrl, scope, attr);
}
};
}])
.directive('paymentsValidate', ['$window', '_Validate', '_ValidateWatch', function($window, _Validate, _ValidateWatch){
return {
restrict: 'A',
require: 'ngModel',
link: function(scope, elem, attr, ctrl){
var type = attr.paymentsValidate;
_ValidateWatch(type, ctrl, scope, attr);
var validateFn = function(val) {
var valid = _Validate(type, val, ctrl, scope, attr);
ctrl.$setValidity(type, valid);
return valid ? val : undefined;
};
ctrl.$formatters.push(validateFn);
ctrl.$parsers.push(validateFn);
}
};
}])
.directive('paymentsLength', [function() {
return {
require: 'ngModel',
link: function(scope, elem, attr, modelCtrl) {
modelCtrl.$parsers.push(function validateLength(value) {
if (attr.paymentsLength === 'card') {
var rawNumber = '';
var minlength = scope.type === 'amex' ? 15 : 16;
if (modelCtrl.$viewValue) {
rawNumber = modelCtrl.$viewValue.replace(/\s/g, '');
}
modelCtrl.$setValidity('length', rawNumber.length >= minlength);
}
return value;
});
}
};
}]);
;/**
* Stripe Form
*/
angular.module('angularPayments')
.directive('stripeForm', ['$window', '$parse', 'Common', function($window, $parse, Common) {
// directive intercepts form-submission, obtains Stripe's cardToken using stripe.js
// and then passes that to callback provided in stripeForm, attribute.
// data that is sent to stripe is filtered from scope, looking for valid values to
// send and converting camelCase to snake_case, e.g expMonth -> exp_month
// filter valid stripe-values from scope and convert them from camelCase to snake_case
var _getDataToSend = function(data){
var possibleKeys = ['number', 'expMonth', 'expYear',
'cvc', 'name','addressLine1',
'addressLine2', 'addressCity',
'addressState', 'addressZip',
'addressCountry'];
var camelToSnake = function(str){
return str.replace(/([A-Z])/g, function(m){
return "_"+m.toLowerCase();
});
};
var ret = {};
for(var i in possibleKeys){
if(data.hasOwnProperty(possibleKeys[i])){
ret[camelToSnake(possibleKeys[i])] = angular.copy(data[possibleKeys[i]]);
}
}
ret.number = (ret.number || '').replace(/ /g,'');
return ret;
};
return {
restrict: 'A',
link: function(scope, elem, attr) {
if(!$window.Stripe){
throw 'stripeForm requires that you have stripe.js installed. Include https://js.stripe.com/v2/ into your html.';
}
var form = angular.element(elem);
form.bind('submit', function() {
var expMonthUsed = scope.expMonth ? true : false;
var expYearUsed = scope.expYear ? true : false;
if(!(expMonthUsed && expYearUsed)){
var exp = Common.parseExpiry(scope.expiry);
scope.expMonth = exp.month;
scope.expYear = exp.year;
}
var button = form.find('button');
button.prop('disabled', true);
if(form.hasClass('ng-valid')) {
$window.Stripe.createToken(_getDataToSend(scope), function() {
var args = arguments;
scope.$apply(function() {
scope[attr.stripeForm].apply(scope, args);
});
button.prop('disabled', false);
});
} else {
scope.$apply(function() {
scope[attr.stripeForm].apply(scope, [400, {error: 'Invalid form submitted.'}]);
});
button.prop('disabled', false);
}
scope.expMonth = null;
scope.expYear = null;
});
}
};
}]);