diff --git a/core/files.js b/core/files.js
index b91edc98..de09bc19 100644
--- a/core/files.js
+++ b/core/files.js
@@ -39,6 +39,9 @@ var files = {
'resources/angular-ui-bootstrap/0.14.3/ui-bootstrap-tpls.min.jsm': true,
'resources/angular-ui-bootstrap/0.14.3/ui-bootstrap.min.jsm': true,
+ // Angular Payments
+ 'resources/angular-payments/1.0.7/angular-payments.jsm': true,
+
// AngularJS
'resources/angularjs/1.7.9/angular-animate.min.jsm': true,
'resources/angularjs/1.7.9/angular-aria.min.jsm': true,
diff --git a/core/mappings.js b/core/mappings.js
index 3570b8c8..4b821d58 100644
--- a/core/mappings.js
+++ b/core/mappings.js
@@ -192,6 +192,7 @@ var mappings = {
'/npm/': {
'angular@{version}/angular.': resources.angular,
'angular@{version}/angular.min.': resources.angular,
+ 'angular-payments@{version}/lib/angular-payments.js': resources.angularPayments,
'animate.css@{version}/animate.min.css': resources.animateCSS,
'backbone@{version}/backbone.': resources.backbone,
'backbone@{version}/backbone-min.': resources.backbone,
diff --git a/core/resources.js b/core/resources.js
index 9eecf02a..d6d773f7 100644
--- a/core/resources.js
+++ b/core/resources.js
@@ -121,6 +121,11 @@ var resources = {
'path': 'resources/angularjs-toaster/{version}/toaster.min.jsm',
'type': 'application/javascript'
},
+ // Angular Payments
+ 'angularPayments': {
+ 'path': 'resources/angular-payments/{version}/angular-payments.jsm',
+ 'type': 'application/javascript'
+ },
// Animate CSS
'animateCSS': {
'path': 'resources/animate.css/{version}/animate.min.css',
diff --git a/modules/internal/helpers.js b/modules/internal/helpers.js
index 2a3f9c2b..caff95b2 100644
--- a/modules/internal/helpers.js
+++ b/modules/internal/helpers.js
@@ -248,6 +248,8 @@ helpers.determineResourceName = function (filename) {
return 'AngularJS Toaster (JS)';
case 'angular-ui-router.min.jsm':
return 'Angular UI Router';
+ case 'angular-payments.jsm':
+ return 'Angular Payments';
case 'animate.min.css':
return 'Animate CSS'
case 'backbone-min.jsm':
@@ -457,6 +459,8 @@ helpers.setLastVersion = function (type, version) {
version = '2.2.0';
} else if (type.includes('/angularjs-toaster/0.')) {
version = '0.4.18';
+ } else if (type.includes('/angular-payments@1.')) {
+ version = '1.0.7';
} else if (type.includes('/angular-ui-bootstrap/0.')) {
version = '0.14.3';
} else if (type.includes('/angular-ui-bootstrap/1.')) {
diff --git a/pages/updates/updates.html b/pages/updates/updates.html
index b5fb0db0..16edd8f5 100644
--- a/pages/updates/updates.html
+++ b/pages/updates/updates.html
@@ -37,6 +37,7 @@
Added AngularJS upgraded to v1.6.10 (Fixed #72)
Added AngularJS v1.4.14 (#71)
Fixed typo in AngularUI Bootstrap (#71)
+ Added Angular Payments v1.0.7 (#71)
Please update your uBlock/uMatrix rules
diff --git a/resources/angular-payments/1.0.7/angular-payments.jsm b/resources/angular-payments/1.0.7/angular-payments.jsm
new file mode 100644
index 00000000..02bf1524
--- /dev/null
+++ b/resources/angular-payments/1.0.7/angular-payments.jsm
@@ -0,0 +1,881 @@
+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;
+
+ });
+ }
+ };
+}]);