diff --git a/services/web/frontend/js/directives/creditCards.js b/services/web/frontend/js/directives/creditCards.js deleted file mode 100644 index cb7ac4c1f7..0000000000 --- a/services/web/frontend/js/directives/creditCards.js +++ /dev/null @@ -1,714 +0,0 @@ -/* eslint-disable - max-len, - no-useless-escape, -*/ -// TODO: This file was created by bulk-decaffeinate. -// Fix any style issues and re-enable lint. -/* - * decaffeinate suggestions: - * DS101: Remove unnecessary use of Array.from - * DS102: Remove unnecessary code created because of implicit returns - * DS103: Rewrite code to no longer use __guard__ - * DS207: Consider shorter variations of null checks - * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md - */ -import App from '../base' -const defaultFormat = /(\d{1,4})/g -const defaultInputFormat = /(?:^|\s)(\d{4})$/ - -const cards = [ - // Credit cards - { - type: 'visa', - patterns: [4], - format: defaultFormat, - length: [13, 16], - cvcLength: [3], - luhn: true - }, - { - type: 'mastercard', - patterns: [51, 52, 53, 54, 55, 22, 23, 24, 25, 26, 27], - format: defaultFormat, - length: [16], - cvcLength: [3], - luhn: true - }, - { - type: 'amex', - patterns: [34, 37], - format: /(\d{1,4})(\d{1,6})?(\d{1,5})?/, - length: [15], - cvcLength: [3, 4], - luhn: true - }, - { - type: 'dinersclub', - patterns: [30, 36, 38, 39], - format: /(\d{1,4})(\d{1,6})?(\d{1,4})?/, - length: [14], - cvcLength: [3], - luhn: true - }, - { - type: 'discover', - patterns: [60, 64, 65, 622], - format: defaultFormat, - length: [16], - cvcLength: [3], - luhn: true - }, - { - type: 'unionpay', - patterns: [62, 88], - format: defaultFormat, - length: [16, 17, 18, 19], - cvcLength: [3], - luhn: false - }, - { - type: 'jcb', - patterns: [35], - format: defaultFormat, - length: [16], - cvcLength: [3], - luhn: true - } -] - -App.factory('ccUtils', function() { - const cardFromNumber = function(num) { - num = (num + '').replace(/\D/g, '') - for (let card of Array.from(cards)) { - for (let pattern of Array.from(card.patterns)) { - const p = pattern + '' - if (num.substr(0, p.length) === p) { - return card - } - } - } - } - - const cardFromType = function(type) { - for (let card of Array.from(cards)) { - if (card.type === type) { - return card - } - } - } - - const cardType = function(num) { - if (!num) { - return null - } - return __guard__(cardFromNumber(num), x => x.type) || null - } - - const formatCardNumber = function(num) { - num = num.replace(/\D/g, '') - const card = cardFromNumber(num) - if (!card) { - return num - } - - const upperLength = card.length[card.length.length - 1] - num = num.slice(0, upperLength) - - if (card.format.global) { - return __guard__(num.match(card.format), x => x.join(' ')) - } else { - let groups = card.format.exec(num) - if (groups == null) { - return - } - groups.shift() - groups = $.grep(groups, n => n) // Filter empty groups - return groups.join(' ') - } - } - - const formatExpiry = function(expiry) { - const parts = expiry.match(/^\D*(\d{1,2})(\D+)?(\d{1,4})?/) - if (!parts) { - return '' - } - - let mon = parts[1] || '' - let sep = parts[2] || '' - const year = parts[3] || '' - - if (year.length > 0) { - sep = ' / ' - } else if (sep === ' /') { - mon = mon.substring(0, 1) - sep = '' - } else if (mon.length === 2 || sep.length > 0) { - sep = ' / ' - } else if (mon.length === 1 && !['0', '1'].includes(mon)) { - mon = `0${mon}` - sep = ' / ' - } - - return mon + sep + year - } - - const parseExpiry = function(value) { - if (value == null) { - value = '' - } - let [month, year] = Array.from(value.split(/[\s\/]+/, 2)) - - // Allow for year shortcut - if ((year != null ? year.length : undefined) === 2 && /^\d+$/.test(year)) { - let prefix = new Date().getFullYear() - prefix = prefix.toString().slice(0, 2) - year = prefix + year - } - - month = parseInt(month, 10) - year = parseInt(year, 10) - - if (!!isNaN(month) || !!isNaN(year)) { - return - } - - return { month, year } - } - - return { - fromNumber: cardFromNumber, - fromType: cardFromType, - cardType, - formatExpiry, - formatCardNumber, - defaultFormat, - defaultInputFormat, - parseExpiry - } -}) - -App.factory('ccFormat', function(ccUtils, $filter) { - const hasTextSelected = function($target) { - // If some text is selected - if ( - $target.prop('selectionStart') != null && - $target.prop('selectionStart') !== $target.prop('selectionEnd') - ) { - return true - } - - // If some text is selected in IE - if ( - __guard__( - typeof document !== 'undefined' && document !== null - ? document.selection - : undefined, - x => x.createRange - ) != null - ) { - if (document.selection.createRange().text) { - return true - } - } - - return false - } - - const safeVal = function(value, $target) { - let cursor - try { - cursor = $target.prop('selectionStart') - } catch (error) { - cursor = null - } - - const last = $target.val() - $target.val(value) - - if (cursor !== null && $target.is(':focus')) { - if (cursor === last.length) { - cursor = value.length - } - - // This hack looks for scenarios where we are changing an input's value such - // that "X| " is replaced with " |X" (where "|" is the cursor). In those - // scenarios, we want " X|". - // - // For example: - // 1. Input field has value "4444| " - // 2. User types "1" - // 3. Input field has value "44441| " - // 4. Reformatter changes it to "4444 |1" - // 5. By incrementing the cursor, we make it "4444 1|" - // - // This is awful, and ideally doesn't go here, but given the current design - // of the system there does not appear to be a better solution. - // - // Note that we can't just detect when the cursor-1 is " ", because that - // would incorrectly increment the cursor when backspacing, e.g. pressing - // backspace in this scenario: "4444 1|234 5". - if (last !== value) { - const prevPair = last.slice(cursor - 1, +cursor + 1 || undefined) - const currPair = value.slice(cursor - 1, +cursor + 1 || undefined) - const digit = value[cursor] - if ( - /\d/.test(digit) && - prevPair === `${digit} ` && - currPair === ` ${digit}` - ) { - cursor = cursor + 1 - } - } - - $target.prop('selectionStart', cursor) - return $target.prop('selectionEnd', cursor) - } - } - - // Replace Full-Width Chars - const replaceFullWidthChars = function(str) { - if (str == null) { - str = '' - } - const fullWidth = - '\uff10\uff11\uff12\uff13\uff14\uff15\uff16\uff17\uff18\uff19' - const halfWidth = '0123456789' - - let value = '' - const chars = str.split('') - - // Avoid using reserved word `char` - for (let chr of Array.from(chars)) { - const idx = fullWidth.indexOf(chr) - if (idx > -1) { - chr = halfWidth[idx] - } - value += chr - } - - return value - } - - // Format Numeric - const reFormatNumeric = function(e) { - const $target = $(e.currentTarget) - return setTimeout(function() { - let value = $target.val() - value = replaceFullWidthChars(value) - value = value.replace(/\D/g, '') - return safeVal(value, $target) - }) - } - - // Format Card Number - const reFormatCardNumber = function(e) { - const $target = $(e.currentTarget) - return setTimeout(function() { - let value = $target.val() - value = replaceFullWidthChars(value) - value = ccUtils.formatCardNumber(value) - return safeVal(value, $target) - }) - } - - const formatCardNumber = function(e) { - // Only format if input is a number - let re - const digit = String.fromCharCode(e.which) - if (!/^\d+$/.test(digit)) { - return - } - - const $target = $(e.currentTarget) - const value = $target.val() - const card = ccUtils.fromNumber(value + digit) - const { length } = value.replace(/\D/g, '') + digit - - let upperLength = 16 - if (card) { - upperLength = card.length[card.length.length - 1] - } - if (length >= upperLength) { - return - } - - // Return if focus isn't at the end of the text - if ( - $target.prop('selectionStart') != null && - $target.prop('selectionStart') !== value.length - ) { - return - } - - if (card && card.type === 'amex') { - // AMEX cards are formatted differently - re = /^(\d{4}|\d{4}\s\d{6})$/ - } else { - re = /(?:^|\s)(\d{4})$/ - } - - // If '4242' + 4 - if (re.test(value)) { - e.preventDefault() - return setTimeout(() => $target.val(value + ' ' + digit)) - - // If '424' + 2 - } else if (re.test(value + digit)) { - e.preventDefault() - return setTimeout(() => $target.val(value + digit + ' ')) - } - } - - const formatBackCardNumber = function(e) { - const $target = $(e.currentTarget) - const value = $target.val() - - // Return unless backspacing - if (e.which !== 8) { - return - } - - // Return if focus isn't at the end of the text - if ( - $target.prop('selectionStart') != null && - $target.prop('selectionStart') !== value.length - ) { - return - } - - // Remove the digit + trailing space - if (/\d\s$/.test(value)) { - e.preventDefault() - return setTimeout(() => $target.val(value.replace(/\d\s$/, ''))) - // Remove digit if ends in space + digit - } else if (/\s\d?$/.test(value)) { - e.preventDefault() - return setTimeout(() => $target.val(value.replace(/\d$/, ''))) - } - } - - const getFormattedCardNumber = function(num) { - num = num.replace(/\D/g, '') - const card = ccUtils.fromNumber(num) - if (!card) { - return num - } - - const upperLength = card.length[card.length.length - 1] - num = num.slice(0, upperLength) - - if (card.format.global) { - return __guard__(num.match(card.format), x => x.join(' ')) - } else { - let groups = card.format.exec(num) - if (groups == null) { - return - } - groups.shift() - groups = $.grep(groups, n => n) // Filter empty groups - return groups.join(' ') - } - } - - const parseCardNumber = function(value) { - if (value != null) { - return value.replace(/\s/g, '') - } else { - return value - } - } - - // Format Expiry - const reFormatExpiry = function(e) { - const $target = $(e.currentTarget) - return setTimeout(function() { - let value = $target.val() - value = replaceFullWidthChars(value) - value = ccUtils.formatExpiry(value) - return safeVal(value, $target) - }) - } - - const formatExpiry = function(e) { - // Only format if input is a number - const digit = String.fromCharCode(e.which) - if (!/^\d+$/.test(digit)) { - return - } - - const $target = $(e.currentTarget) - const val = $target.val() + digit - - if (/^\d$/.test(val) && !['0', '1'].includes(val)) { - e.preventDefault() - return setTimeout(() => $target.val(`0${val} / `)) - } else if (/^\d\d$/.test(val)) { - e.preventDefault() - return setTimeout(function() { - // Split for months where we have the second digit > 2 (past 12) and turn - // that into (m1)(m2) => 0(m1) / (m2) - const m1 = parseInt(val[0], 10) - const m2 = parseInt(val[1], 10) - if (m2 > 2 && m1 !== 0) { - return $target.val(`0${m1} / ${m2}`) - } else { - return $target.val(`${val} / `) - } - }) - } - } - - const formatForwardExpiry = function(e) { - const digit = String.fromCharCode(e.which) - if (!/^\d+$/.test(digit)) { - return - } - - const $target = $(e.currentTarget) - const val = $target.val() - - if (/^\d\d$/.test(val)) { - return $target.val(`${val} / `) - } - } - - const formatForwardSlash = function(e) { - const which = String.fromCharCode(e.which) - if (which !== '/' && which !== ' ') { - return - } - - const $target = $(e.currentTarget) - const val = $target.val() - - if (/^\d$/.test(val) && val !== '0') { - return $target.val(`0${val} / `) - } - } - - const formatBackExpiry = function(e) { - const $target = $(e.currentTarget) - const value = $target.val() - - // Return unless backspacing - if (e.which !== 8) { - return - } - - // Return if focus isn't at the end of the text - if ( - $target.prop('selectionStart') != null && - $target.prop('selectionStart') !== value.length - ) { - return - } - - // Remove the trailing space + last digit - if (/\d\s\/\s$/.test(value)) { - e.preventDefault() - return setTimeout(() => $target.val(value.replace(/\d\s\/\s$/, ''))) - } - } - - const parseExpiry = function(value) { - if (value != null) { - const dateAsObj = ccUtils.parseExpiry(value) - - if (dateAsObj == null) { - return - } - - const expiry = new Date(dateAsObj.year, dateAsObj.month - 1) - - return $filter('date')(expiry, 'MM/yyyy') - } - } - - // Format CVC - const reFormatCVC = function(e) { - const $target = $(e.currentTarget) - return setTimeout(function() { - let value = $target.val() - value = replaceFullWidthChars(value) - value = value.replace(/\D/g, '').slice(0, 4) - return safeVal(value, $target) - }) - } - - // Restrictions - const restrictNumeric = function(e) { - // Key event is for a browser shortcut - if (e.metaKey || e.ctrlKey) { - return true - } - - // If keycode is a space - if (e.which === 32) { - return false - } - - // If keycode is a special char (WebKit) - if (e.which === 0) { - return true - } - - // If char is a special char (Firefox) - if (e.which < 33) { - return true - } - - const input = String.fromCharCode(e.which) - - // Char is a number or a space - return !!/[\d\s]/.test(input) - } - - const restrictCardNumber = function(e) { - const $target = $(e.currentTarget) - const digit = String.fromCharCode(e.which) - if (!/^\d+$/.test(digit)) { - return - } - - if (hasTextSelected($target)) { - return - } - - // Restrict number of digits - const value = ($target.val() + digit).replace(/\D/g, '') - const card = ccUtils.fromNumber(value) - - if (card) { - return value.length <= card.length[card.length.length - 1] - } else { - // All other cards are 16 digits long - return value.length <= 16 - } - } - - const restrictExpiry = function(e) { - const $target = $(e.currentTarget) - const digit = String.fromCharCode(e.which) - if (!/^\d+$/.test(digit)) { - return - } - - if (hasTextSelected($target)) { - return - } - - let value = $target.val() + digit - value = value.replace(/\D/g, '') - - if (value.length > 6) { - return false - } - } - - const restrictCVC = function(e) { - const $target = $(e.currentTarget) - const digit = String.fromCharCode(e.which) - if (!/^\d+$/.test(digit)) { - return - } - - if (hasTextSelected($target)) { - return - } - - const val = $target.val() + digit - return val.length <= 4 - } - - const setCardType = function(e) { - const $target = $(e.currentTarget) - const val = $target.val() - const cardType = ccUtils.cardType(val) || 'unknown' - - if (!$target.hasClass(cardType)) { - const allTypes = Array.from(cards).map(card => card.type) - - $target.removeClass('unknown') - $target.removeClass(allTypes.join(' ')) - - $target.addClass(cardType) - $target.toggleClass('identified', cardType !== 'unknown') - return $target.trigger('payment.cardType', cardType) - } - } - - return { - hasTextSelected, - replaceFullWidthChars, - reFormatNumeric, - reFormatCardNumber, - formatCardNumber, - formatBackCardNumber, - getFormattedCardNumber, - parseCardNumber, - reFormatExpiry, - formatExpiry, - formatForwardExpiry, - formatForwardSlash, - formatBackExpiry, - parseExpiry, - reFormatCVC, - restrictNumeric, - restrictCardNumber, - restrictExpiry, - restrictCVC, - setCardType - } -}) - -App.directive('ccFormatExpiry', ccFormat => ({ - restrict: 'A', - require: 'ngModel', - link(scope, el, attrs, ngModel) { - el.on('keypress', ccFormat.restrictNumeric) - el.on('keypress', ccFormat.restrictExpiry) - el.on('keypress', ccFormat.formatExpiry) - el.on('keypress', ccFormat.formatForwardSlash) - el.on('keypress', ccFormat.formatForwardExpiry) - el.on('keydown', ccFormat.formatBackExpiry) - el.on('change', ccFormat.reFormatExpiry) - el.on('input', ccFormat.reFormatExpiry) - el.on('paste', ccFormat.reFormatExpiry) - - ngModel.$parsers.push(ccFormat.parseExpiry) - return ngModel.$formatters.push(ccFormat.parseExpiry) - } -})) - -App.directive('ccFormatCardNumber', ccFormat => ({ - restrict: 'A', - require: 'ngModel', - link(scope, el, attrs, ngModel) { - el.on('keypress', ccFormat.restrictNumeric) - el.on('keypress', ccFormat.restrictCardNumber) - el.on('keypress', ccFormat.formatCardNumber) - el.on('keydown', ccFormat.formatBackCardNumber) - el.on('paste', ccFormat.reFormatCardNumber) - - ngModel.$parsers.push(ccFormat.parseCardNumber) - return ngModel.$formatters.push(ccFormat.getFormattedCardNumber) - } -})) - -export default App.directive('ccFormatSecCode', ccFormat => ({ - restrict: 'A', - require: 'ngModel', - link(scope, el, attrs, ngModel) { - el.on('keypress', ccFormat.restrictNumeric) - el.on('keypress', ccFormat.restrictCVC) - el.on('paste', ccFormat.reFormatCVC) - el.on('change', ccFormat.reFormatCVC) - return el.on('input', ccFormat.reFormatCVC) - } -})) - -function __guard__(value, transform) { - return typeof value !== 'undefined' && value !== null - ? transform(value) - : undefined -} diff --git a/services/web/frontend/js/main.js b/services/web/frontend/js/main.js index 747475b31e..8c66fdc26a 100644 --- a/services/web/frontend/js/main.js +++ b/services/web/frontend/js/main.js @@ -46,7 +46,6 @@ import './directives/fineUpload' import './directives/onEnter' import './directives/selectAll' import './directives/maxHeight' -import './directives/creditCards' import './directives/bookmarkableTabset' import './services/queued-http' import './services/validateCaptcha' diff --git a/services/web/frontend/js/main/new-subscription.js b/services/web/frontend/js/main/new-subscription.js index ae69ce8e2d..4005e0320e 100644 --- a/services/web/frontend/js/main/new-subscription.js +++ b/services/web/frontend/js/main/new-subscription.js @@ -6,14 +6,12 @@ import _ from 'lodash' */ /* global recurly */ import App from '../base' -import '../directives/creditCards' export default App.controller('NewSubscriptionController', function( $scope, MultiCurrencyPricing, $http, - eventTracking, - ccUtils + eventTracking ) { if (typeof recurly === 'undefined' || !recurly) { $scope.recurlyLoadError = true