From 4dcd992aa7b0eeb7a5851c79522cdade18b66518 Mon Sep 17 00:00:00 2001 From: Kaspar Emanuel Date: Fri, 17 Jun 2016 20:06:51 +0100 Subject: [PATCH] Added jquery.payment to make card-input easier --- Gruntfile.js | 1 + bower.json | 3 +- package.json | 1 + public/assets/javascript/app-public.js | 20 +- public/assets/javascript/frontend.js | 685 +++++++++++++++++- public/assets/stylesheet/frontend.css | 2 +- public/vendor/jquery.payment/.bower.json | 19 + .../jquery.payment/.github/CONTRIBUTING.md | 16 + .../jquery.payment/.github/ISSUE_TEMPLATE.md | 11 + .../.github/PULL_REQUEST_TEMPLATE.md | 11 + public/vendor/jquery.payment/.gitignore | 1 + public/vendor/jquery.payment/.travis.yml | 3 + public/vendor/jquery.payment/Cakefile | 38 + public/vendor/jquery.payment/LICENSE | 20 + public/vendor/jquery.payment/README.md | 252 +++++++ public/vendor/jquery.payment/bower.json | 7 + .../vendor/jquery.payment/example/index.html | 86 +++ .../jquery.payment/lib/jquery.payment.js | 666 +++++++++++++++++ .../jquery.payment/lib/jquery.payment.min.js | 1 + public/vendor/jquery.payment/package.json | 31 + .../vendor/jquery.payment/payment.jquery.json | 29 + .../jquery.payment/src/jquery.payment.coffee | 608 ++++++++++++++++ .../vendor/jquery.payment/test/jquery.coffee | 7 + .../vendor/jquery.payment/test/specs.coffee | 407 +++++++++++ .../vendor/jquery.payment/test/zepto.coffee | 9 + 25 files changed, 2916 insertions(+), 18 deletions(-) create mode 100644 public/vendor/jquery.payment/.bower.json create mode 100644 public/vendor/jquery.payment/.github/CONTRIBUTING.md create mode 100644 public/vendor/jquery.payment/.github/ISSUE_TEMPLATE.md create mode 100644 public/vendor/jquery.payment/.github/PULL_REQUEST_TEMPLATE.md create mode 100644 public/vendor/jquery.payment/.gitignore create mode 100644 public/vendor/jquery.payment/.travis.yml create mode 100644 public/vendor/jquery.payment/Cakefile create mode 100644 public/vendor/jquery.payment/LICENSE create mode 100644 public/vendor/jquery.payment/README.md create mode 100644 public/vendor/jquery.payment/bower.json create mode 100644 public/vendor/jquery.payment/example/index.html create mode 100644 public/vendor/jquery.payment/lib/jquery.payment.js create mode 100644 public/vendor/jquery.payment/lib/jquery.payment.min.js create mode 100644 public/vendor/jquery.payment/package.json create mode 100644 public/vendor/jquery.payment/payment.jquery.json create mode 100644 public/vendor/jquery.payment/src/jquery.payment.coffee create mode 100644 public/vendor/jquery.payment/test/jquery.coffee create mode 100644 public/vendor/jquery.payment/test/specs.coffee create mode 100644 public/vendor/jquery.payment/test/zepto.coffee diff --git a/Gruntfile.js b/Gruntfile.js index 8cb9813c..bae41c4c 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -30,6 +30,7 @@ module.exports = function (grunt) { './public/vendor/jquery-form/jquery.form.js', './public/vendor/RRSSB/js/rrssb.js', './public/vendor/humane-js/humane.js', + './public/vendor/jquery.payment/lib/jquery.payment.js', './public/assets/javascript/app-public.js' ], dest: './public/assets/javascript/frontend.js', diff --git a/bower.json b/bower.json index 0df0436c..b2483df5 100644 --- a/bower.json +++ b/bower.json @@ -31,7 +31,8 @@ "fullcalendar": "^2.7.1", "vue": "^1.0.24", "vue-resource": "^0.7.0", - "html.sortable": "^0.3.1" + "html.sortable": "^0.3.1", + "jquery.payment": "https://github.com/stripe/jquery.payment.git#^1.4.2" }, "resolutions": { "jquery": ">=1.5", diff --git a/package.json b/package.json index c63509c6..0fef30a8 100644 --- a/package.json +++ b/package.json @@ -8,6 +8,7 @@ }, "devDependencies": { "grunt": "^0.4.5", + "grunt-cli": "^1.2.0", "grunt-contrib-concat": "^0.5.1", "grunt-contrib-less": "^0.12.0", "grunt-contrib-uglify": "^0.6.0", diff --git a/public/assets/javascript/app-public.js b/public/assets/javascript/app-public.js index a572fc38..5a05c9a5 100644 --- a/public/assets/javascript/app-public.js +++ b/public/assets/javascript/app-public.js @@ -83,9 +83,8 @@ $(function() { toggleSubmitDisabled($submitButton); if ($form.hasClass('payment-form')) { - clearFormErrors($('.payment-form')); - + Stripe.setPublishableKey($form.data('stripe-pub-key')); var @@ -96,6 +95,7 @@ $(function() { $expiryMonth = $('.card-expiry-month'), $expiryYear = $('.card-expiry-year'); + if (!Stripe.validateCardNumber($cardNumber.val())) { showFormError($cardNumber, 'The credit card number appears to be invalid.'); noErrors = false; @@ -131,7 +131,7 @@ $(function() { $form.append($('').val(token)); $form.ajaxSubmit(ajaxFormConf); } - + }); } else { showMessage('Please check your card details and try again.'); @@ -147,6 +147,7 @@ $(function() { offset: -60 }); + /* Scroll to top */ $(window).scroll(function() { if ($(this).scrollTop() > 100) { @@ -172,6 +173,9 @@ $(function() { $('.ticket_holder_email').val($('#order_email').val()); }); + $('.card-number').payment('formatCardNumber'); + $('.card-cvc').payment('formatCardCVC'); + }); function processFormErrors($form, errors) @@ -201,7 +205,7 @@ function processFormErrors($form, errors) /** * Toggle a submit button disabled/enabled - duh! - * + * * @param element $submitButton * @returns void */ @@ -222,7 +226,7 @@ function toggleSubmitDisabled($submitButton) { /** * Clears given form of any error classes / messages - * + * * @param {Element} $form * @returns {void} */ @@ -247,7 +251,7 @@ function showFormError($formElement, message) { /** * Shows users a message. * Currently uses humane.js - * + * * @param string message * @returns void */ @@ -264,7 +268,7 @@ function hideMessage() { /** * Counts down to the given number of seconds - * + * * @param element $element * @param int seconds * @returns void @@ -370,4 +374,4 @@ $.extend( }, t.smoothScroll.version = l, t.smoothScroll.filterPath = function(t) { return t.replace(/^\//, "").replace(/(?:index|default).[a-zA-Z]{3,4}$/, "").replace(/\/$/, "") }, t.fn.smoothScroll.defaults = s -})(jQuery); \ No newline at end of file +})(jQuery); diff --git a/public/assets/javascript/frontend.js b/public/assets/javascript/frontend.js index 688c913b..f46c74da 100644 --- a/public/assets/javascript/frontend.js +++ b/public/assets/javascript/frontend.js @@ -3901,6 +3901,671 @@ function log() { } return new Humane() }); +;(function() { + var $, cardFromNumber, cardFromType, cards, defaultFormat, formatBackCardNumber, formatBackExpiry, formatCardNumber, formatExpiry, formatForwardExpiry, formatForwardSlashAndSpace, hasTextSelected, luhnCheck, reFormatCVC, reFormatCardNumber, reFormatExpiry, reFormatNumeric, replaceFullWidthChars, restrictCVC, restrictCardNumber, restrictExpiry, restrictNumeric, safeVal, setCardType, + __slice = [].slice, + __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; }; + + $ = window.jQuery || window.Zepto || window.$; + + $.payment = {}; + + $.payment.fn = {}; + + $.fn.payment = function() { + var args, method; + method = arguments[0], args = 2 <= arguments.length ? __slice.call(arguments, 1) : []; + return $.payment.fn[method].apply(this, args); + }; + + defaultFormat = /(\d{1,4})/g; + + $.payment.cards = cards = [ + { + type: 'elo', + patterns: [4011, 4312, 4389, 4514, 4573, 4576, 5041, 5066, 5067, 509, 6277, 6362, 6363, 650, 6516, 6550], + format: defaultFormat, + length: [16], + cvcLength: [3], + luhn: true + }, { + type: 'visaelectron', + patterns: [4026, 417500, 4405, 4508, 4844, 4913, 4917], + format: defaultFormat, + length: [16], + cvcLength: [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], + cvcLength: [3], + luhn: true + }, { + type: 'forbrugsforeningen', + patterns: [600], + format: defaultFormat, + length: [16], + cvcLength: [3], + luhn: true + }, { + type: 'dankort', + patterns: [5019], + format: defaultFormat, + length: [16], + cvcLength: [3], + luhn: true + }, { + 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 + } + ]; + + cardFromNumber = function(num) { + var card, p, pattern, _i, _j, _len, _len1, _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; + } + } + } + }; + + cardFromType = function(type) { + var card, _i, _len; + for (_i = 0, _len = cards.length; _i < _len; _i++) { + card = cards[_i]; + if (card.type === type) { + return card; + } + } + }; + + 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; + }; + + hasTextSelected = function($target) { + var _ref; + if (($target.prop('selectionStart') != null) && $target.prop('selectionStart') !== $target.prop('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; + }; + + safeVal = function(value, $target) { + var currPair, cursor, digit, error, last, prevPair; + try { + cursor = $target.prop('selectionStart'); + } catch (_error) { + error = _error; + cursor = null; + } + last = $target.val(); + $target.val(value); + if (cursor !== null && $target.is(":focus")) { + 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 = cursor + 1; + } + } + $target.prop('selectionStart', cursor); + return $target.prop('selectionEnd', cursor); + } + }; + + replaceFullWidthChars = function(str) { + var chars, chr, fullWidth, halfWidth, idx, value, _i, _len; + 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; + }; + + reFormatNumeric = function(e) { + var $target; + $target = $(e.currentTarget); + return setTimeout(function() { + var value; + value = $target.val(); + value = replaceFullWidthChars(value); + value = value.replace(/\D/g, ''); + return safeVal(value, $target); + }); + }; + + reFormatCardNumber = function(e) { + var $target; + $target = $(e.currentTarget); + return setTimeout(function() { + var value; + value = $target.val(); + value = replaceFullWidthChars(value); + value = $.payment.formatCardNumber(value); + return safeVal(value, $target); + }); + }; + + formatCardNumber = function(e) { + var $target, card, digit, length, re, upperLength, value; + digit = String.fromCharCode(e.which); + if (!/^\d+$/.test(digit)) { + return; + } + $target = $(e.currentTarget); + value = $target.val(); + 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.prop('selectionStart') != null) && $target.prop('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(function() { + return $target.val(value + ' ' + digit); + }); + } else if (re.test(value + digit)) { + e.preventDefault(); + return setTimeout(function() { + return $target.val(value + digit + ' '); + }); + } + }; + + formatBackCardNumber = function(e) { + var $target, value; + $target = $(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 setTimeout(function() { + return $target.val(value.replace(/\d\s$/, '')); + }); + } else if (/\s\d?$/.test(value)) { + e.preventDefault(); + return setTimeout(function() { + return $target.val(value.replace(/\d$/, '')); + }); + } + }; + + reFormatExpiry = function(e) { + var $target; + $target = $(e.currentTarget); + return setTimeout(function() { + var value; + value = $target.val(); + value = replaceFullWidthChars(value); + value = $.payment.formatExpiry(value); + return safeVal(value, $target); + }); + }; + + formatExpiry = function(e) { + var $target, digit, val; + digit = String.fromCharCode(e.which); + if (!/^\d+$/.test(digit)) { + return; + } + $target = $(e.currentTarget); + val = $target.val() + digit; + if (/^\d$/.test(val) && (val !== '0' && val !== '1')) { + e.preventDefault(); + return setTimeout(function() { + return $target.val("0" + val + " / "); + }); + } else if (/^\d\d$/.test(val)) { + e.preventDefault(); + return setTimeout(function() { + var m1, m2; + m1 = parseInt(val[0], 10); + m2 = parseInt(val[1], 10); + if (m2 > 2 && m1 !== 0) { + return $target.val("0" + m1 + " / " + m2); + } else { + return $target.val("" + val + " / "); + } + }); + } + }; + + formatForwardExpiry = function(e) { + var $target, digit, val; + digit = String.fromCharCode(e.which); + if (!/^\d+$/.test(digit)) { + return; + } + $target = $(e.currentTarget); + val = $target.val(); + if (/^\d\d$/.test(val)) { + return $target.val("" + val + " / "); + } + }; + + formatForwardSlashAndSpace = function(e) { + var $target, val, which; + which = String.fromCharCode(e.which); + if (!(which === '/' || which === ' ')) { + return; + } + $target = $(e.currentTarget); + val = $target.val(); + if (/^\d$/.test(val) && val !== '0') { + return $target.val("0" + val + " / "); + } + }; + + formatBackExpiry = function(e) { + var $target, value; + $target = $(e.currentTarget); + value = $target.val(); + if (e.which !== 8) { + return; + } + if (($target.prop('selectionStart') != null) && $target.prop('selectionStart') !== value.length) { + return; + } + if (/\d\s\/\s$/.test(value)) { + e.preventDefault(); + return setTimeout(function() { + return $target.val(value.replace(/\d\s\/\s$/, '')); + }); + } + }; + + reFormatCVC = function(e) { + var $target; + $target = $(e.currentTarget); + return setTimeout(function() { + var value; + value = $target.val(); + value = replaceFullWidthChars(value); + value = value.replace(/\D/g, '').slice(0, 4); + return safeVal(value, $target); + }); + }; + + restrictNumeric = function(e) { + var 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); + }; + + restrictCardNumber = function(e) { + var $target, card, digit, value; + $target = $(e.currentTarget); + digit = String.fromCharCode(e.which); + if (!/^\d+$/.test(digit)) { + return; + } + if (hasTextSelected($target)) { + return; + } + value = ($target.val() + digit).replace(/\D/g, ''); + card = cardFromNumber(value); + if (card) { + return value.length <= card.length[card.length.length - 1]; + } else { + return value.length <= 16; + } + }; + + restrictExpiry = function(e) { + var $target, digit, value; + $target = $(e.currentTarget); + digit = String.fromCharCode(e.which); + if (!/^\d+$/.test(digit)) { + return; + } + if (hasTextSelected($target)) { + return; + } + value = $target.val() + digit; + value = value.replace(/\D/g, ''); + if (value.length > 6) { + return false; + } + }; + + restrictCVC = function(e) { + var $target, digit, val; + $target = $(e.currentTarget); + digit = String.fromCharCode(e.which); + if (!/^\d+$/.test(digit)) { + return; + } + if (hasTextSelected($target)) { + return; + } + val = $target.val() + digit; + return val.length <= 4; + }; + + setCardType = function(e) { + var $target, allTypes, card, cardType, val; + $target = $(e.currentTarget); + val = $target.val(); + cardType = $.payment.cardType(val) || 'unknown'; + if (!$target.hasClass(cardType)) { + allTypes = (function() { + var _i, _len, _results; + _results = []; + for (_i = 0, _len = cards.length; _i < _len; _i++) { + card = cards[_i]; + _results.push(card.type); + } + return _results; + })(); + $target.removeClass('unknown'); + $target.removeClass(allTypes.join(' ')); + $target.addClass(cardType); + $target.toggleClass('identified', cardType !== 'unknown'); + return $target.trigger('payment.cardType', cardType); + } + }; + + $.payment.fn.formatCardCVC = function() { + this.on('keypress', restrictNumeric); + this.on('keypress', restrictCVC); + this.on('paste', reFormatCVC); + this.on('change', reFormatCVC); + this.on('input', reFormatCVC); + return this; + }; + + $.payment.fn.formatCardExpiry = function() { + this.on('keypress', restrictNumeric); + this.on('keypress', restrictExpiry); + this.on('keypress', formatExpiry); + this.on('keypress', formatForwardSlashAndSpace); + this.on('keypress', formatForwardExpiry); + this.on('keydown', formatBackExpiry); + this.on('change', reFormatExpiry); + this.on('input', reFormatExpiry); + return this; + }; + + $.payment.fn.formatCardNumber = function() { + this.on('keypress', restrictNumeric); + this.on('keypress', restrictCardNumber); + this.on('keypress', formatCardNumber); + this.on('keydown', formatBackCardNumber); + this.on('keyup', setCardType); + this.on('paste', reFormatCardNumber); + this.on('change', reFormatCardNumber); + this.on('input', reFormatCardNumber); + this.on('input', setCardType); + return this; + }; + + $.payment.fn.restrictNumeric = function() { + this.on('keypress', restrictNumeric); + this.on('paste', reFormatNumeric); + this.on('change', reFormatNumeric); + this.on('input', reFormatNumeric); + return this; + }; + + $.payment.fn.cardExpiryVal = function() { + return $.payment.cardExpiryVal($(this).val()); + }; + + $.payment.cardExpiryVal = function(value) { + var month, prefix, year, _ref; + _ref = value.split(/[\s\/]+/, 2), month = _ref[0], year = _ref[1]; + if ((year != null ? year.length : void 0) === 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 + }; + }; + + $.payment.validateCardNumber = function(num) { + var 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)); + }; + + $.payment.validateCardExpiry = function(month, year) { + var currentTime, expiry, _ref; + if (typeof month === 'object' && 'month' in month) { + _ref = month, month = _ref.month, year = _ref.year; + } + if (!(month && year)) { + return false; + } + month = $.trim(month); + year = $.trim(year); + if (!/^\d+$/.test(month)) { + return false; + } + if (!/^\d+$/.test(year)) { + return false; + } + if (!((1 <= month && 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; + }; + + $.payment.validateCardCVC = function(cvc, type) { + var card, _ref; + cvc = $.trim(cvc); + if (!/^\d+$/.test(cvc)) { + return false; + } + card = cardFromType(type); + if (card != null) { + return _ref = cvc.length, __indexOf.call(card.cvcLength, _ref) >= 0; + } else { + return cvc.length >= 3 && cvc.length <= 4; + } + }; + + $.payment.cardType = function(num) { + var _ref; + if (!num) { + return null; + } + return ((_ref = cardFromNumber(num)) != null ? _ref.type : void 0) || null; + }; + + $.payment.formatCardNumber = function(num) { + var card, groups, upperLength, _ref; + 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; + } else { + groups = card.format.exec(num); + if (groups == null) { + return; + } + groups.shift(); + groups = $.grep(groups, function(n) { + return n; + }); + return groups.join(' '); + } + }; + + $.payment.formatExpiry = function(expiry) { + var mon, parts, sep, year; + parts = expiry.match(/^\D*(\d{1,2})(\D+)?(\d{1,4})?/); + if (!parts) { + return ''; + } + mon = parts[1] || ''; + sep = parts[2] || ''; + 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 && (mon !== '0' && mon !== '1')) { + mon = "0" + mon; + sep = ' / '; + } + return mon + sep + year; + }; + +}).call(this); ;$(function() { $('form.ajax').on('submit', function(e) { e.preventDefault(); @@ -3986,9 +4651,8 @@ function log() { toggleSubmitDisabled($submitButton); if ($form.hasClass('payment-form')) { - clearFormErrors($('.payment-form')); - + Stripe.setPublishableKey($form.data('stripe-pub-key')); var @@ -3999,6 +4663,7 @@ function log() { $expiryMonth = $('.card-expiry-month'), $expiryYear = $('.card-expiry-year'); + if (!Stripe.validateCardNumber($cardNumber.val())) { showFormError($cardNumber, 'The credit card number appears to be invalid.'); noErrors = false; @@ -4034,7 +4699,7 @@ function log() { $form.append($('').val(token)); $form.ajaxSubmit(ajaxFormConf); } - + }); } else { showMessage('Please check your card details and try again.'); @@ -4050,6 +4715,7 @@ function log() { offset: -60 }); + /* Scroll to top */ $(window).scroll(function() { if ($(this).scrollTop() > 100) { @@ -4075,6 +4741,9 @@ function log() { $('.ticket_holder_email').val($('#order_email').val()); }); + $('.card-number').payment('formatCardNumber'); + $('.card-cvc').payment('formatCardCVC'); + }); function processFormErrors($form, errors) @@ -4104,7 +4773,7 @@ function processFormErrors($form, errors) /** * Toggle a submit button disabled/enabled - duh! - * + * * @param element $submitButton * @returns void */ @@ -4125,7 +4794,7 @@ function toggleSubmitDisabled($submitButton) { /** * Clears given form of any error classes / messages - * + * * @param {Element} $form * @returns {void} */ @@ -4150,7 +4819,7 @@ function showFormError($formElement, message) { /** * Shows users a message. * Currently uses humane.js - * + * * @param string message * @returns void */ @@ -4167,7 +4836,7 @@ function hideMessage() { /** * Counts down to the given number of seconds - * + * * @param element $element * @param int seconds * @returns void @@ -4273,4 +4942,4 @@ $.extend( }, t.smoothScroll.version = l, t.smoothScroll.filterPath = function(t) { return t.replace(/^\//, "").replace(/(?:index|default).[a-zA-Z]{3,4}$/, "").replace(/\/$/, "") }, t.fn.smoothScroll.defaults = s -})(jQuery); \ No newline at end of file +})(jQuery); diff --git a/public/assets/stylesheet/frontend.css b/public/assets/stylesheet/frontend.css index 13fbed8a..db361a61 100644 --- a/public/assets/stylesheet/frontend.css +++ b/public/assets/stylesheet/frontend.css @@ -92,4 +92,4 @@ body { opacity: 0.7; filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=70); } -html,body{height:100%}body{font-family:'Open Sans',sans-serif}table{margin:0}label.required::after{content:'*';color:red;padding-left:3px;font-size:9px}@media (min-width:1200px){.container{width:960px}}section.container{padding:40px;background-color:#ffffff;margin-bottom:25px}.section_head{border:none !important;font-size:35px;text-align:center;margin:0;margin-bottom:30px;letter-spacing:.2em;font-weight:200}.section_head h1{margin:0;font-weight:100;text-align:center}section{color:#666}#organiser_page_wrap #intro{position:relative;text-align:center;font-weight:100;padding:20px 0 20px 0;border:none;margin-bottom:0;margin-top:20px;color:#fff;background-color:#AF5050}#organiser_page_wrap .organiser_logo{max-width:150px;margin:0 auto}#organiser_page_wrap .organiser_logo .thumbnail{background-color:transparent;border:none}#event_page_wrap{min-height:100%;margin:0 auto -60px;background:rgba(0,0,0,0.4)}#event_page_wrap #organiserHead{text-align:center;color:#fff;border:none;font-size:15px;opacity:.6;transition:all .15s ease-in-out;background:rgba(0,0,0,0.4);line-height:30px;cursor:pointer}#event_page_wrap #organiserHead:hover{opacity:1}#event_page_wrap #intro{position:relative;text-align:center;font-weight:100;padding:20px 0 20px 0;color:#ffffff;border:none;background-color:transparent;margin-bottom:0}#event_page_wrap #intro h1{position:relative;padding:10px 10px;margin:0;font-weight:400;font-size:60px;margin-bottom:10px}#event_page_wrap #intro .event_date{font-size:15px;padding:10px;font-weight:500}#event_page_wrap #intro .event_venue{font-size:19px}#event_page_wrap #intro .event_buttons{margin-top:30px;margin-bottom:30px}#event_page_wrap #intro .event_buttons .btn-event-link{line-height:35px;font-size:17px;text-transform:uppercase;letter-spacing:5px;border:1px solid;border-color:rgba(255,255,255,0.2);text-decoration:none;color:#fff;padding:0 15px;transition:all .15s ease-in-out;width:100%;background:rgba(0,0,0,0.3)}#event_page_wrap #intro .event_buttons .btn-event-link:hover{border-color:rgba(255,255,255,0.6)}#event_page_wrap #tickets .input-group-addon{background:none;border:none}#event_page_wrap #tickets table tr:first-child td{border-top:none}#event_page_wrap #tickets table tr td{padding:20px 0px}#event_page_wrap #details .event_poster img{border:4px solid #f6f6f6;max-width:100%;min-width:100%}#event_page_wrap #details .event_details iframe,#event_page_wrap #details .event_details img{max-width:100%}#event_page_wrap #details .event_details h1,#event_page_wrap #details .event_details h2,#event_page_wrap #details .event_details h3,#event_page_wrap #details .event_details h4,#event_page_wrap #details .event_details h5,#event_page_wrap #details .event_details h6{margin-top:0px;margin-bottom:15px;font-weight:100}#event_page_wrap #details .event_details h1{font-size:28px}#event_page_wrap #details .event_details h2{font-size:24px}#event_page_wrap #details .event_details h3{font-size:20px}#event_page_wrap #details .event_details h4{font-size:17px}#event_page_wrap #share .btn{margin-bottom:20px}#event_page_wrap #location{padding:0px}#event_page_wrap #location .google-maps{position:relative;overflow:hidden}#event_page_wrap #location .google-maps iframe{width:100% !important;height:100% !important;min-height:500px}footer,.push{height:60px}#footer{background-color:#888;background-color:rgba(0,0,0,0.4);min-height:60px;line-height:60px;color:#fff;text-align:center}#organiser{text-align:center}#organiser .contact_form{display:none;padding:20px;margin-top:25px;text-align:left}.totop{border-radius:0;background-color:#888;background-color:rgba(0,0,0,0.4)}.totop:hover{background-color:#fff;background-color:rgba(255,255,255,0.4);color:#000}@media (min-width:100px) and (max-width:767px){.row{margin:0}section.container{margin-bottom:0;padding:10px}.section_head{padding:10px;font-size:30px}.main_content{padding:0px;background:#fff}#organiser_page_wrap #intro{padding:30px;margin-top:0}#organiser_page_wrap #intro h1{font-size:2.236em;padding:15px}#event_page_wrap #intro{padding:30px}#event_page_wrap #intro .event_date h2{font-size:20px}#event_page_wrap #intro .event_date h4{font-size:11px}#event_page_wrap #intro h1{font-size:2.236em;padding:15px}#event_page_wrap #intro .event_venue{color:#fff;font-size:20px;margin-top:10px}#event_page_wrap #intro .event_buttons{margin-top:50px}#event_page_wrap #intro .event_buttons .btn-event-link{padding:5px 0px;font-size:18px;margin-bottom:5px;line-height:30px}#event_page_wrap #tickets .btn-checkout{width:100%}#event_page_wrap #location .google-maps iframe{min-height:290px}.content{padding:15px}}.rrssb-buttons.large-format li a{border-radius:0}.event-listing-heading{margin-top:0;margin-bottom:10px;font-size:20px}.event-list{list-style:none;margin:0px;padding:0px}.event-list>li{background-color:#F3F3F3;padding:0px;margin:0px 0px 20px}.event-list>li>time{display:inline-block;width:100%;padding:5px;text-align:center;text-transform:uppercase}.event-list>li>time>span{display:none}.event-list>li>time>.day{display:block;font-size:18pt;font-weight:100;line-height:1}.event-list>li time>.month{display:block;font-size:24pt;font-weight:900;line-height:1}.event-list>li>img{width:100%}.event-list>li>.info{padding-top:10px;text-align:center}.event-list>li>.info>.title{font-size:15pt;font-weight:500;margin:0px}.event-list>li>.info>.desc{font-size:10pt;font-weight:300;margin:0px}.event-list>li>.info>ul{display:table;list-style:none;margin:10px 0px 0px;padding:0px;width:100%;text-align:center;background-color:#DEDEDE}.event-list>li>.info>ul>li{display:table-cell;cursor:pointer;color:#1e1e1e;font-size:11pt;font-weight:300;padding:3px 0px}.event-list>li>.info>ul>li>a{display:block;width:100%;color:#6D6D6D;text-decoration:none}.event-list>li>.info>ul>li:hover{color:#1e1e1e;background-color:#c8c8c8}@media (min-width:768px){.event-list>li{position:relative;display:block;width:100%;height:120px;padding:0px}.event-list>li>time,.event-list>li>img{display:inline-block}.event-list>li>time,.event-list>li>img{width:120px;float:left}.event-list>li>.info{background-color:#f5f5f5;overflow:hidden}.event-list>li>time,.event-list>li>img{width:120px;height:120px;padding:0px;margin:0px}.event-list>li>time>.day{font-size:56pt}.event-list>li>.info{position:relative;height:120px;text-align:left;padding-right:40px;padding-top:30px}.event-list>li>.info>.title,.event-list>li>.info>.desc{padding:0px 10px}.event-list>li>.info>ul{position:absolute;left:0px;bottom:0px;background-color:#D2D2D2}} +html,body{height:100%}body{font-family:'Open Sans',sans-serif}table{margin:0}label.required::after{content:'*';color:red;padding-left:3px;font-size:9px}@media (min-width:1200px){.container{width:960px}}section.container{padding:40px;background-color:#ffffff;margin-bottom:25px}.section_head{border:none !important;font-size:35px;text-align:center;margin:0;margin-bottom:30px;letter-spacing:.2em;font-weight:200}.section_head h1{margin:0;font-weight:100;text-align:center}section{color:#666}#organiser_page_wrap #intro{position:relative;text-align:center;font-weight:100;padding:20px 0 20px 0;border:none;margin-bottom:0;margin-top:20px;color:#fff;background-color:#AF5050}#organiser_page_wrap .organiser_logo{max-width:150px;margin:0 auto}#organiser_page_wrap .organiser_logo .thumbnail{background-color:transparent;border:none}#event_page_wrap{min-height:100%;margin:0 auto -60px;background:rgba(0,0,0,0.4)}#event_page_wrap #organiserHead{text-align:center;color:#fff;border:none;font-size:15px;opacity:.6;transition:all .15s ease-in-out;background:rgba(0,0,0,0.4);line-height:30px;cursor:pointer}#event_page_wrap #organiserHead:hover{opacity:1}#event_page_wrap #intro{position:relative;text-align:center;font-weight:100;padding:20px 0 20px 0;color:#ffffff;border:none;background-color:transparent;margin-bottom:0}#event_page_wrap #intro h1{position:relative;padding:10px 10px;margin:0;font-weight:400;font-size:60px;margin-bottom:10px}#event_page_wrap #intro .event_date{font-size:15px;padding:10px;font-weight:500}#event_page_wrap #intro .event_venue{font-size:19px}#event_page_wrap #intro .event_buttons{margin-top:30px;margin-bottom:30px}#event_page_wrap #intro .event_buttons .btn-event-link{line-height:35px;font-size:17px;text-transform:uppercase;letter-spacing:5px;border:1px solid;border-color:rgba(255,255,255,0.2);text-decoration:none;color:#fff;padding:0 15px;transition:all .15s ease-in-out;width:100%;background:rgba(0,0,0,0.3)}#event_page_wrap #intro .event_buttons .btn-event-link:hover{border-color:rgba(255,255,255,0.6)}#event_page_wrap #tickets .input-group-addon{background:none;border:none}#event_page_wrap #tickets table tr:first-child td{border-top:none}#event_page_wrap #tickets table tr td{padding:20px 0px}#event_page_wrap #details .event_poster img{border:4px solid #f6f6f6;max-width:100%;min-width:100%}#event_page_wrap #details .event_details iframe,#event_page_wrap #details .event_details img{max-width:100%}#event_page_wrap #details .event_details h1,#event_page_wrap #details .event_details h2,#event_page_wrap #details .event_details h3,#event_page_wrap #details .event_details h4,#event_page_wrap #details .event_details h5,#event_page_wrap #details .event_details h6{margin-top:0px;margin-bottom:15px;font-weight:100}#event_page_wrap #details .event_details h1{font-size:28px}#event_page_wrap #details .event_details h2{font-size:24px}#event_page_wrap #details .event_details h3{font-size:20px}#event_page_wrap #details .event_details h4{font-size:17px}#event_page_wrap #share .btn{margin-bottom:20px}#event_page_wrap #location{padding:0px}#event_page_wrap #location .google-maps{position:relative;overflow:hidden}#event_page_wrap #location .google-maps iframe{width:100% !important;height:100% !important;min-height:500px}footer,.push{height:60px}#footer{background-color:#888;background-color:rgba(0,0,0,0.4);min-height:60px;line-height:60px;color:#fff;text-align:center}#organiser{text-align:center}#organiser .contact_form{display:none;padding:20px;margin-top:25px;text-align:left}.totop{border-radius:0;background-color:#888;background-color:rgba(0,0,0,0.4)}.totop:hover{background-color:#fff;background-color:rgba(255,255,255,0.4);color:#000}@media (min-width:100px) and (max-width:767px){.row{margin:0}section.container{margin-bottom:0;padding:10px}.section_head{padding:10px;font-size:30px}.main_content{padding:0px;background:#fff}#organiser_page_wrap #intro{padding:30px;margin-top:0}#organiser_page_wrap #intro h1{font-size:2.236em;padding:15px}#organiser_page_wrap #events{min-height:350px}#event_page_wrap #intro{padding:30px}#event_page_wrap #intro .event_date h2{font-size:20px}#event_page_wrap #intro .event_date h4{font-size:11px}#event_page_wrap #intro h1{font-size:2.236em;padding:15px}#event_page_wrap #intro .event_venue{color:#fff;font-size:20px;margin-top:10px}#event_page_wrap #intro .event_buttons{margin-top:50px}#event_page_wrap #intro .event_buttons .btn-event-link{padding:5px 0px;font-size:18px;margin-bottom:5px;line-height:30px}#event_page_wrap #tickets .btn-checkout{width:100%}#event_page_wrap #location .google-maps iframe{min-height:290px}.content{padding:15px}}.rrssb-buttons.large-format li a{border-radius:0}.event-listing-heading{margin-top:0;margin-bottom:10px;font-size:20px}.event-list{list-style:none;margin:0px;padding:0px}.event-list>li{background-color:#F3F3F3;padding:0px;margin:0px 0px 20px}.event-list>li>time{display:inline-block;width:100%;padding:5px;text-align:center;text-transform:uppercase}.event-list>li>time>span{display:none}.event-list>li>time>.day{display:block;font-size:18pt;font-weight:100;line-height:1}.event-list>li time>.month{display:block;font-size:24pt;font-weight:900;line-height:1}.event-list>li>img{width:100%}.event-list>li>.info{padding-top:10px;text-align:center}.event-list>li>.info>.title{font-size:15pt;font-weight:500;margin:0px}.event-list>li>.info>.desc{font-size:10pt;font-weight:300;margin:0px}.event-list>li>.info>ul{display:table;list-style:none;margin:10px 0px 0px;padding:0px;width:100%;text-align:center;background-color:#DEDEDE}.event-list>li>.info>ul>li{display:table-cell;cursor:pointer;color:#1e1e1e;font-size:11pt;font-weight:300;padding:3px 0px}.event-list>li>.info>ul>li>a{display:block;width:100%;color:#6D6D6D;text-decoration:none}.event-list>li>.info>ul>li:hover{color:#1e1e1e;background-color:#c8c8c8}@media (min-width:768px){.event-list>li{position:relative;display:block;width:100%;height:120px;padding:0px}.event-list>li>time,.event-list>li>img{display:inline-block}.event-list>li>time,.event-list>li>img{width:120px;float:left}.event-list>li>.info{background-color:#f5f5f5;overflow:hidden}.event-list>li>time,.event-list>li>img{width:120px;height:120px;padding:0px;margin:0px}.event-list>li>time>.day{font-size:56pt}.event-list>li>.info{position:relative;height:120px;text-align:left;padding-right:40px;padding-top:30px}.event-list>li>.info>.title,.event-list>li>.info>.desc{padding:0px 10px}.event-list>li>.info>ul{position:absolute;left:0px;bottom:0px;background-color:#D2D2D2}} \ No newline at end of file diff --git a/public/vendor/jquery.payment/.bower.json b/public/vendor/jquery.payment/.bower.json new file mode 100644 index 00000000..55aba193 --- /dev/null +++ b/public/vendor/jquery.payment/.bower.json @@ -0,0 +1,19 @@ +{ + "name": "jquery.payment", + "main": "lib/jquery.payment.js", + "dependencies": { + "jquery": ">=1.5" + }, + "homepage": "https://github.com/stripe/jquery.payment", + "version": "1.4.2", + "_release": "1.4.2", + "_resolution": { + "type": "version", + "tag": "v1.4.2", + "commit": "4269e038cd6f9b2c416d240a0e090c5ab0949c46" + }, + "_source": "https://github.com/stripe/jquery.payment.git", + "_target": "^1.4.2", + "_originalSource": "https://github.com/stripe/jquery.payment.git", + "_direct": true +} \ No newline at end of file diff --git a/public/vendor/jquery.payment/.github/CONTRIBUTING.md b/public/vendor/jquery.payment/.github/CONTRIBUTING.md new file mode 100644 index 00000000..2b0e1977 --- /dev/null +++ b/public/vendor/jquery.payment/.github/CONTRIBUTING.md @@ -0,0 +1,16 @@ +Thanks for your interest in contributing! + +## Filing issues + +`jQuery.payment` is not currently accepting feature requests. We _are_ interested in fixing bugs and updating credit card BINs where appropriate. Please file issues for these items only. + +When filing bugs, make sure to follow the issue template. When reporting credit card BIN changes, please include links to supporting documentation. + +## Submitting pull requests + +We will happily review and merge pull requests for bugs and card number changes. When submitting a pull request: + +- Ensure that the code and commit message are well-commented and grammatically correct. +- Follow the style of the rest of the code, where possible. +- Make sure that your diff is the minimal set of changes necessary to fix the bug in question. +- Follow the pull request template. diff --git a/public/vendor/jquery.payment/.github/ISSUE_TEMPLATE.md b/public/vendor/jquery.payment/.github/ISSUE_TEMPLATE.md new file mode 100644 index 00000000..06449d46 --- /dev/null +++ b/public/vendor/jquery.payment/.github/ISSUE_TEMPLATE.md @@ -0,0 +1,11 @@ +## Expected behavior + +Describe the expected behavior. For new credit card BIN numbers, please include supporting references. + +## Actual behavior + +Describe the actual behavior. List all browsers affected, as specifically as possible (e.g. "Chrome 49.2623.112 on OS X 10.11.4", not just "Chrome"). + +## Steps to reproduce + +Describe steps to reproduce the problem. Please include a link to a minimal reproduction of the problem with jsfiddle (or similar tool) where possible. diff --git a/public/vendor/jquery.payment/.github/PULL_REQUEST_TEMPLATE.md b/public/vendor/jquery.payment/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 00000000..4064003a --- /dev/null +++ b/public/vendor/jquery.payment/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,11 @@ +## Summary + +Simple summary of what was changed. + +## Motivation + +Why are you making this change? Please provide supporting references for new BIN numbers and links to minimal reproductions (e.g. with jsfiddle) when fixing bugs. + +## Testing + +How was the code tested? Be as specific as possible. diff --git a/public/vendor/jquery.payment/.gitignore b/public/vendor/jquery.payment/.gitignore new file mode 100644 index 00000000..3c3629e6 --- /dev/null +++ b/public/vendor/jquery.payment/.gitignore @@ -0,0 +1 @@ +node_modules diff --git a/public/vendor/jquery.payment/.travis.yml b/public/vendor/jquery.payment/.travis.yml new file mode 100644 index 00000000..63f7b350 --- /dev/null +++ b/public/vendor/jquery.payment/.travis.yml @@ -0,0 +1,3 @@ +language: node_js +node_js: + - "4" diff --git a/public/vendor/jquery.payment/Cakefile b/public/vendor/jquery.payment/Cakefile new file mode 100644 index 00000000..d1c027f0 --- /dev/null +++ b/public/vendor/jquery.payment/Cakefile @@ -0,0 +1,38 @@ +{spawn} = require 'child_process' +path = require 'path' + +binPath = (bin) -> path.resolve(__dirname, "./node_modules/.bin/#{bin}") + +runExternal = (cmd, args, callback = process.exit) -> + child = spawn(binPath(cmd), args, stdio: 'inherit') + child.on('error', console.error) + child.on('close', callback) + +runSequential = (cmds, status = 0) -> + process.exit status if status or !cmds.length + cmd = cmds.shift() + cmd.push (status) -> runSequential cmds, status + runExternal.apply null, cmd + +task 'build', 'Build lib/ from src/', -> + runExternal 'coffee', + ['-c', '-o', 'lib', 'src'], + -> invoke 'minify' + +task 'minify', 'Minify lib/', -> + runExternal 'uglifyjs', [ + 'lib/jquery.payment.js', + '--mangle', + '--compress', + '--output', + 'lib/jquery.payment.min.js' + ] + +task 'watch', 'Watch src/ for changes', -> + runExternal 'coffee', ['-w', '-c', '-o', 'lib', 'src'] + +task 'test', 'Run tests', -> + runSequential [ + ['mocha', ['--compilers', 'coffee:coffee-script/register', 'test/jquery.coffee']] + ['mocha', ['--compilers', 'coffee:coffee-script/register', 'test/zepto.coffee']] + ] diff --git a/public/vendor/jquery.payment/LICENSE b/public/vendor/jquery.payment/LICENSE new file mode 100644 index 00000000..877ee042 --- /dev/null +++ b/public/vendor/jquery.payment/LICENSE @@ -0,0 +1,20 @@ +Copyright (c) 2014 Stripe + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/public/vendor/jquery.payment/README.md b/public/vendor/jquery.payment/README.md new file mode 100644 index 00000000..a55b5850 --- /dev/null +++ b/public/vendor/jquery.payment/README.md @@ -0,0 +1,252 @@ +# jQuery.payment [![Build Status](https://travis-ci.org/stripe/jquery.payment.svg?branch=master)](https://travis-ci.org/stripe/jquery.payment) + +A general purpose library for building credit card forms, validating inputs and formatting numbers. + +## Project status + +We consider `jQuery.payment` to be feature complete. We continue to use it in production, and we will happily accept bug reports and pull requests fixing those bugs, but we will not be adding new features or modifying the project for new frameworks or build systems. + +### Why? + +The library was born in a different age, and we think it has served tremendously, but it is fundamentally doing too many things. Complecting DOM element manipulation, input masking, card formatting, and cursor positioning makes it difficult to test and modify. An ideal version of this library would separate the independent components and make the internal logic functional. + +## Usage + +You can make an input act like a credit card field (with number formatting and length restriction): + +``` javascript +$('input.cc-num').payment('formatCardNumber'); +``` + +Then, when the payment form is submitted, you can validate the card number on the client-side: + +``` javascript +var valid = $.payment.validateCardNumber($('input.cc-num').val()); + +if (!valid) { + alert('Your card is not valid!'); + return false; +} +``` + +You can find a full [demo here](http://stripe.github.io/jquery.payment/example). + +Supported card types are: + +* Visa +* MasterCard +* American Express +* Diners Club +* Discover +* UnionPay +* JCB +* Visa Electron +* Maestro +* Forbrugsforeningen +* Dankort +* Elo + +(Additional card types are supported by extending the [`$.payment.cards`](#paymentcards) array.) + +## API + +### $.fn.payment('formatCardNumber') + +Formats card numbers: + +* Includes a space between every 4 digits +* Restricts input to numbers +* Limits to 16 numbers +* Supports American Express formatting +* Adds a class of the card type (e.g. 'visa') to the input + +Example: + +``` javascript +$('input.cc-num').payment('formatCardNumber'); +``` + +### $.fn.payment('formatCardExpiry') + +Formats card expiry: + +* Includes a `/` between the month and year +* Restricts input to numbers +* Restricts length + +Example: + +``` javascript +$('input.cc-exp').payment('formatCardExpiry'); +``` + +### $.fn.payment('formatCardCVC') + +Formats card CVC: + +* Restricts length to 4 numbers +* Restricts input to numbers + +Example: + +``` javascript +$('input.cc-cvc').payment('formatCardCVC'); +``` + +### $.fn.payment('restrictNumeric') + +General numeric input restriction. + +Example: + +``` javascript +$('[data-numeric]').payment('restrictNumeric'); +``` + +### $.payment.validateCardNumber(number) + +Validates a card number: + +* Validates numbers +* Validates Luhn algorithm +* Validates length + +Example: + +``` javascript +$.payment.validateCardNumber('4242 4242 4242 4242'); //=> true +``` + +### $.payment.validateCardExpiry(month, year) + +Validates a card expiry: + +* Validates numbers +* Validates in the future +* Supports year shorthand + +Example: + +``` javascript +$.payment.validateCardExpiry('05', '20'); //=> true +$.payment.validateCardExpiry('05', '2015'); //=> true +$.payment.validateCardExpiry('05', '05'); //=> false +``` + +### $.payment.validateCardCVC(cvc, type) + +Validates a card CVC: + +* Validates number +* Validates length to 4 + +Example: + +``` javascript +$.payment.validateCardCVC('123'); //=> true +$.payment.validateCardCVC('123', 'amex'); //=> true +$.payment.validateCardCVC('1234', 'amex'); //=> true +$.payment.validateCardCVC('12344'); //=> false +``` + +### $.payment.cardType(number) + +Returns a card type. Either: + +* `visa` +* `mastercard` +* `amex` +* `dinersclub` +* `discover` +* `unionpay` +* `jcb` +* `visaelectron` +* `maestro` +* `forbrugsforeningen` +* `dankort` +* `elo` + +The function will return `null` if the card type can't be determined. + +Example: + +``` javascript +$.payment.cardType('4242 4242 4242 4242'); //=> 'visa' +``` + +### $.payment.cardExpiryVal(string) and $.fn.payment('cardExpiryVal') + +Parses a credit card expiry in the form of MM/YYYY, returning an object containing the `month` and `year`. Shorthand years, such as `13` are also supported (and converted into the longhand, e.g. `2013`). + +``` javascript +$.payment.cardExpiryVal('03 / 2025'); //=> {month: 3, year: 2025} +$.payment.cardExpiryVal('05 / 04'); //=> {month: 5, year: 2004} +$('input.cc-exp').payment('cardExpiryVal') //=> {month: 4, year: 2020} +``` + +This function doesn't perform any validation of the month or year; use `$.payment.validateCardExpiry(month, year)` for that. + +### $.payment.cards + +Array of objects that describe valid card types. Each object should contain the following fields: + +``` javascript +{ + // Card type, as returned by $.payment.cardType. + type: 'mastercard', + // Array of prefixes used to identify the card type. + patterns: [ + 51, 52, 53, 54, 55, + 22, 23, 24, 25, 26, 27 + ], + // Array of valid card number lengths. + length: [16], + // Array of valid card CVC lengths. + cvcLength: [3], + // Boolean indicating whether a valid card number should satisfy the Luhn check. + luhn: true, + // Regex used to format the card number. Each match is joined with a space. + format: /(\d{1,4})/g +} +``` + +When identifying a card type, the array is traversed in order until the card number matches a prefix in `patterns`. For this reason, patterns with higher specificity should appear towards the beginning of the array. + +## Example + +Look in [`./example/index.html`](example/index.html) + +## Building + +Run `cake build` + +## Running tests + +Run `cake test` + +## Autocomplete recommendations + +We recommend you turn autocomplete on for credit card forms, except for the CVC field (which should never be stored). You can do this by setting the `autocomplete` attribute: + +``` html +
+ + +
+``` + +You should also mark up your fields using the [Autofill spec](https://html.spec.whatwg.org/multipage/forms.html#autofill). These are respected by a number of browsers, including Chrome. + +``` html + +``` + +Set `autocomplete` to `cc-number` for credit card numbers and `cc-exp` for credit card expiry. + +## Mobile recommendations + +We recommend you to use `` which will cause the numeric keyboard to be displayed on mobile devices: + +``` html + +``` diff --git a/public/vendor/jquery.payment/bower.json b/public/vendor/jquery.payment/bower.json new file mode 100644 index 00000000..cd3fa390 --- /dev/null +++ b/public/vendor/jquery.payment/bower.json @@ -0,0 +1,7 @@ +{ + "name": "jquery.payment", + "main": "lib/jquery.payment.js", + "dependencies": { + "jquery": ">=1.5" + } +} diff --git a/public/vendor/jquery.payment/example/index.html b/public/vendor/jquery.payment/example/index.html new file mode 100644 index 00000000..63b22b99 --- /dev/null +++ b/public/vendor/jquery.payment/example/index.html @@ -0,0 +1,86 @@ + + + + + + + + + + + + + + +
+

+ jquery.payment demo + Fork on GitHub +

+

A general purpose library for building credit card forms, validating inputs and formatting numbers.

+
+
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ + + +

+
+
+ + diff --git a/public/vendor/jquery.payment/lib/jquery.payment.js b/public/vendor/jquery.payment/lib/jquery.payment.js new file mode 100644 index 00000000..6351362e --- /dev/null +++ b/public/vendor/jquery.payment/lib/jquery.payment.js @@ -0,0 +1,666 @@ +// Generated by CoffeeScript 1.7.1 +(function() { + var $, cardFromNumber, cardFromType, cards, defaultFormat, formatBackCardNumber, formatBackExpiry, formatCardNumber, formatExpiry, formatForwardExpiry, formatForwardSlashAndSpace, hasTextSelected, luhnCheck, reFormatCVC, reFormatCardNumber, reFormatExpiry, reFormatNumeric, replaceFullWidthChars, restrictCVC, restrictCardNumber, restrictExpiry, restrictNumeric, safeVal, setCardType, + __slice = [].slice, + __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; }; + + $ = window.jQuery || window.Zepto || window.$; + + $.payment = {}; + + $.payment.fn = {}; + + $.fn.payment = function() { + var args, method; + method = arguments[0], args = 2 <= arguments.length ? __slice.call(arguments, 1) : []; + return $.payment.fn[method].apply(this, args); + }; + + defaultFormat = /(\d{1,4})/g; + + $.payment.cards = cards = [ + { + type: 'elo', + patterns: [4011, 4312, 4389, 4514, 4573, 4576, 5041, 5066, 5067, 509, 6277, 6362, 6363, 650, 6516, 6550], + format: defaultFormat, + length: [16], + cvcLength: [3], + luhn: true + }, { + type: 'visaelectron', + patterns: [4026, 417500, 4405, 4508, 4844, 4913, 4917], + format: defaultFormat, + length: [16], + cvcLength: [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], + cvcLength: [3], + luhn: true + }, { + type: 'forbrugsforeningen', + patterns: [600], + format: defaultFormat, + length: [16], + cvcLength: [3], + luhn: true + }, { + type: 'dankort', + patterns: [5019], + format: defaultFormat, + length: [16], + cvcLength: [3], + luhn: true + }, { + 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 + } + ]; + + cardFromNumber = function(num) { + var card, p, pattern, _i, _j, _len, _len1, _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; + } + } + } + }; + + cardFromType = function(type) { + var card, _i, _len; + for (_i = 0, _len = cards.length; _i < _len; _i++) { + card = cards[_i]; + if (card.type === type) { + return card; + } + } + }; + + 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; + }; + + hasTextSelected = function($target) { + var _ref; + if (($target.prop('selectionStart') != null) && $target.prop('selectionStart') !== $target.prop('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; + }; + + safeVal = function(value, $target) { + var currPair, cursor, digit, error, last, prevPair; + try { + cursor = $target.prop('selectionStart'); + } catch (_error) { + error = _error; + cursor = null; + } + last = $target.val(); + $target.val(value); + if (cursor !== null && $target.is(":focus")) { + 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 = cursor + 1; + } + } + $target.prop('selectionStart', cursor); + return $target.prop('selectionEnd', cursor); + } + }; + + replaceFullWidthChars = function(str) { + var chars, chr, fullWidth, halfWidth, idx, value, _i, _len; + 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; + }; + + reFormatNumeric = function(e) { + var $target; + $target = $(e.currentTarget); + return setTimeout(function() { + var value; + value = $target.val(); + value = replaceFullWidthChars(value); + value = value.replace(/\D/g, ''); + return safeVal(value, $target); + }); + }; + + reFormatCardNumber = function(e) { + var $target; + $target = $(e.currentTarget); + return setTimeout(function() { + var value; + value = $target.val(); + value = replaceFullWidthChars(value); + value = $.payment.formatCardNumber(value); + return safeVal(value, $target); + }); + }; + + formatCardNumber = function(e) { + var $target, card, digit, length, re, upperLength, value; + digit = String.fromCharCode(e.which); + if (!/^\d+$/.test(digit)) { + return; + } + $target = $(e.currentTarget); + value = $target.val(); + 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.prop('selectionStart') != null) && $target.prop('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(function() { + return $target.val(value + ' ' + digit); + }); + } else if (re.test(value + digit)) { + e.preventDefault(); + return setTimeout(function() { + return $target.val(value + digit + ' '); + }); + } + }; + + formatBackCardNumber = function(e) { + var $target, value; + $target = $(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 setTimeout(function() { + return $target.val(value.replace(/\d\s$/, '')); + }); + } else if (/\s\d?$/.test(value)) { + e.preventDefault(); + return setTimeout(function() { + return $target.val(value.replace(/\d$/, '')); + }); + } + }; + + reFormatExpiry = function(e) { + var $target; + $target = $(e.currentTarget); + return setTimeout(function() { + var value; + value = $target.val(); + value = replaceFullWidthChars(value); + value = $.payment.formatExpiry(value); + return safeVal(value, $target); + }); + }; + + formatExpiry = function(e) { + var $target, digit, val; + digit = String.fromCharCode(e.which); + if (!/^\d+$/.test(digit)) { + return; + } + $target = $(e.currentTarget); + val = $target.val() + digit; + if (/^\d$/.test(val) && (val !== '0' && val !== '1')) { + e.preventDefault(); + return setTimeout(function() { + return $target.val("0" + val + " / "); + }); + } else if (/^\d\d$/.test(val)) { + e.preventDefault(); + return setTimeout(function() { + var m1, m2; + m1 = parseInt(val[0], 10); + m2 = parseInt(val[1], 10); + if (m2 > 2 && m1 !== 0) { + return $target.val("0" + m1 + " / " + m2); + } else { + return $target.val("" + val + " / "); + } + }); + } + }; + + formatForwardExpiry = function(e) { + var $target, digit, val; + digit = String.fromCharCode(e.which); + if (!/^\d+$/.test(digit)) { + return; + } + $target = $(e.currentTarget); + val = $target.val(); + if (/^\d\d$/.test(val)) { + return $target.val("" + val + " / "); + } + }; + + formatForwardSlashAndSpace = function(e) { + var $target, val, which; + which = String.fromCharCode(e.which); + if (!(which === '/' || which === ' ')) { + return; + } + $target = $(e.currentTarget); + val = $target.val(); + if (/^\d$/.test(val) && val !== '0') { + return $target.val("0" + val + " / "); + } + }; + + formatBackExpiry = function(e) { + var $target, value; + $target = $(e.currentTarget); + value = $target.val(); + if (e.which !== 8) { + return; + } + if (($target.prop('selectionStart') != null) && $target.prop('selectionStart') !== value.length) { + return; + } + if (/\d\s\/\s$/.test(value)) { + e.preventDefault(); + return setTimeout(function() { + return $target.val(value.replace(/\d\s\/\s$/, '')); + }); + } + }; + + reFormatCVC = function(e) { + var $target; + $target = $(e.currentTarget); + return setTimeout(function() { + var value; + value = $target.val(); + value = replaceFullWidthChars(value); + value = value.replace(/\D/g, '').slice(0, 4); + return safeVal(value, $target); + }); + }; + + restrictNumeric = function(e) { + var 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); + }; + + restrictCardNumber = function(e) { + var $target, card, digit, value; + $target = $(e.currentTarget); + digit = String.fromCharCode(e.which); + if (!/^\d+$/.test(digit)) { + return; + } + if (hasTextSelected($target)) { + return; + } + value = ($target.val() + digit).replace(/\D/g, ''); + card = cardFromNumber(value); + if (card) { + return value.length <= card.length[card.length.length - 1]; + } else { + return value.length <= 16; + } + }; + + restrictExpiry = function(e) { + var $target, digit, value; + $target = $(e.currentTarget); + digit = String.fromCharCode(e.which); + if (!/^\d+$/.test(digit)) { + return; + } + if (hasTextSelected($target)) { + return; + } + value = $target.val() + digit; + value = value.replace(/\D/g, ''); + if (value.length > 6) { + return false; + } + }; + + restrictCVC = function(e) { + var $target, digit, val; + $target = $(e.currentTarget); + digit = String.fromCharCode(e.which); + if (!/^\d+$/.test(digit)) { + return; + } + if (hasTextSelected($target)) { + return; + } + val = $target.val() + digit; + return val.length <= 4; + }; + + setCardType = function(e) { + var $target, allTypes, card, cardType, val; + $target = $(e.currentTarget); + val = $target.val(); + cardType = $.payment.cardType(val) || 'unknown'; + if (!$target.hasClass(cardType)) { + allTypes = (function() { + var _i, _len, _results; + _results = []; + for (_i = 0, _len = cards.length; _i < _len; _i++) { + card = cards[_i]; + _results.push(card.type); + } + return _results; + })(); + $target.removeClass('unknown'); + $target.removeClass(allTypes.join(' ')); + $target.addClass(cardType); + $target.toggleClass('identified', cardType !== 'unknown'); + return $target.trigger('payment.cardType', cardType); + } + }; + + $.payment.fn.formatCardCVC = function() { + this.on('keypress', restrictNumeric); + this.on('keypress', restrictCVC); + this.on('paste', reFormatCVC); + this.on('change', reFormatCVC); + this.on('input', reFormatCVC); + return this; + }; + + $.payment.fn.formatCardExpiry = function() { + this.on('keypress', restrictNumeric); + this.on('keypress', restrictExpiry); + this.on('keypress', formatExpiry); + this.on('keypress', formatForwardSlashAndSpace); + this.on('keypress', formatForwardExpiry); + this.on('keydown', formatBackExpiry); + this.on('change', reFormatExpiry); + this.on('input', reFormatExpiry); + return this; + }; + + $.payment.fn.formatCardNumber = function() { + this.on('keypress', restrictNumeric); + this.on('keypress', restrictCardNumber); + this.on('keypress', formatCardNumber); + this.on('keydown', formatBackCardNumber); + this.on('keyup', setCardType); + this.on('paste', reFormatCardNumber); + this.on('change', reFormatCardNumber); + this.on('input', reFormatCardNumber); + this.on('input', setCardType); + return this; + }; + + $.payment.fn.restrictNumeric = function() { + this.on('keypress', restrictNumeric); + this.on('paste', reFormatNumeric); + this.on('change', reFormatNumeric); + this.on('input', reFormatNumeric); + return this; + }; + + $.payment.fn.cardExpiryVal = function() { + return $.payment.cardExpiryVal($(this).val()); + }; + + $.payment.cardExpiryVal = function(value) { + var month, prefix, year, _ref; + _ref = value.split(/[\s\/]+/, 2), month = _ref[0], year = _ref[1]; + if ((year != null ? year.length : void 0) === 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 + }; + }; + + $.payment.validateCardNumber = function(num) { + var 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)); + }; + + $.payment.validateCardExpiry = function(month, year) { + var currentTime, expiry, _ref; + if (typeof month === 'object' && 'month' in month) { + _ref = month, month = _ref.month, year = _ref.year; + } + if (!(month && year)) { + return false; + } + month = $.trim(month); + year = $.trim(year); + if (!/^\d+$/.test(month)) { + return false; + } + if (!/^\d+$/.test(year)) { + return false; + } + if (!((1 <= month && 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; + }; + + $.payment.validateCardCVC = function(cvc, type) { + var card, _ref; + cvc = $.trim(cvc); + if (!/^\d+$/.test(cvc)) { + return false; + } + card = cardFromType(type); + if (card != null) { + return _ref = cvc.length, __indexOf.call(card.cvcLength, _ref) >= 0; + } else { + return cvc.length >= 3 && cvc.length <= 4; + } + }; + + $.payment.cardType = function(num) { + var _ref; + if (!num) { + return null; + } + return ((_ref = cardFromNumber(num)) != null ? _ref.type : void 0) || null; + }; + + $.payment.formatCardNumber = function(num) { + var card, groups, upperLength, _ref; + 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; + } else { + groups = card.format.exec(num); + if (groups == null) { + return; + } + groups.shift(); + groups = $.grep(groups, function(n) { + return n; + }); + return groups.join(' '); + } + }; + + $.payment.formatExpiry = function(expiry) { + var mon, parts, sep, year; + parts = expiry.match(/^\D*(\d{1,2})(\D+)?(\d{1,4})?/); + if (!parts) { + return ''; + } + mon = parts[1] || ''; + sep = parts[2] || ''; + 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 && (mon !== '0' && mon !== '1')) { + mon = "0" + mon; + sep = ' / '; + } + return mon + sep + year; + }; + +}).call(this); diff --git a/public/vendor/jquery.payment/lib/jquery.payment.min.js b/public/vendor/jquery.payment/lib/jquery.payment.min.js new file mode 100644 index 00000000..79e9cd53 --- /dev/null +++ b/public/vendor/jquery.payment/lib/jquery.payment.min.js @@ -0,0 +1 @@ +(function(){var t,e,n,r,a,o,i,l,u,s,c,h,p,g,v,f,d,m,y,C,T,w,$,D,S=[].slice,k=[].indexOf||function(t){for(var e=0,n=this.length;n>e;e++)if(e in this&&this[e]===t)return e;return-1};t=window.jQuery||window.Zepto||window.$,t.payment={},t.payment.fn={},t.fn.payment=function(){var e,n;return n=arguments[0],e=2<=arguments.length?S.call(arguments,1):[],t.payment.fn[n].apply(this,e)},a=/(\d{1,4})/g,t.payment.cards=r=[{type:"elo",patterns:[4011,4312,4389,4514,4573,4576,5041,5066,5067,509,6277,6362,6363,650,6516,6550],format:a,length:[16],cvcLength:[3],luhn:!0},{type:"visaelectron",patterns:[4026,417500,4405,4508,4844,4913,4917],format:a,length:[16],cvcLength:[3],luhn:!0},{type:"maestro",patterns:[5018,502,503,506,56,58,639,6220,67],format:a,length:[12,13,14,15,16,17,18,19],cvcLength:[3],luhn:!0},{type:"forbrugsforeningen",patterns:[600],format:a,length:[16],cvcLength:[3],luhn:!0},{type:"dankort",patterns:[5019],format:a,length:[16],cvcLength:[3],luhn:!0},{type:"visa",patterns:[4],format:a,length:[13,16],cvcLength:[3],luhn:!0},{type:"mastercard",patterns:[51,52,53,54,55,22,23,24,25,26,27],format:a,length:[16],cvcLength:[3],luhn:!0},{type:"amex",patterns:[34,37],format:/(\d{1,4})(\d{1,6})?(\d{1,5})?/,length:[15],cvcLength:[3,4],luhn:!0},{type:"dinersclub",patterns:[30,36,38,39],format:/(\d{1,4})(\d{1,6})?(\d{1,4})?/,length:[14],cvcLength:[3],luhn:!0},{type:"discover",patterns:[60,64,65,622],format:a,length:[16],cvcLength:[3],luhn:!0},{type:"unionpay",patterns:[62,88],format:a,length:[16,17,18,19],cvcLength:[3],luhn:!1},{type:"jcb",patterns:[35],format:a,length:[16],cvcLength:[3],luhn:!0}],e=function(t){var e,n,a,o,i,l,u,s;for(t=(t+"").replace(/\D/g,""),o=0,l=r.length;l>o;o++)for(e=r[o],s=e.patterns,i=0,u=s.length;u>i;i++)if(a=s[i],n=a+"",t.substr(0,n.length)===n)return e},n=function(t){var e,n,a;for(n=0,a=r.length;a>n;n++)if(e=r[n],e.type===t)return e},p=function(t){var e,n,r,a,o,i;for(r=!0,a=0,n=(t+"").split("").reverse(),o=0,i=n.length;i>o;o++)e=n[o],e=parseInt(e,10),(r=!r)&&(e*=2),e>9&&(e-=9),a+=e;return a%10===0},h=function(t){var e;return null!=t.prop("selectionStart")&&t.prop("selectionStart")!==t.prop("selectionEnd")?!0:null!=("undefined"!=typeof document&&null!==document&&null!=(e=document.selection)?e.createRange:void 0)&&document.selection.createRange().text?!0:!1},$=function(t,e){var n,r,a,o,i,l;try{r=e.prop("selectionStart")}catch(u){o=u,r=null}return i=e.val(),e.val(t),null!==r&&e.is(":focus")?(r===i.length&&(r=t.length),i!==t&&(l=i.slice(r-1,+r+1||9e9),n=t.slice(r-1,+r+1||9e9),a=t[r],/\d/.test(a)&&l===""+a+" "&&n===" "+a&&(r+=1)),e.prop("selectionStart",r),e.prop("selectionEnd",r)):void 0},m=function(t){var e,n,r,a,o,i,l,u;for(null==t&&(t=""),r="0123456789",a="0123456789",i="",e=t.split(""),l=0,u=e.length;u>l;l++)n=e[l],o=r.indexOf(n),o>-1&&(n=a[o]),i+=n;return i},d=function(e){var n;return n=t(e.currentTarget),setTimeout(function(){var t;return t=n.val(),t=m(t),t=t.replace(/\D/g,""),$(t,n)})},v=function(e){var n;return n=t(e.currentTarget),setTimeout(function(){var e;return e=n.val(),e=m(e),e=t.payment.formatCardNumber(e),$(e,n)})},l=function(n){var r,a,o,i,l,u,s;return o=String.fromCharCode(n.which),!/^\d+$/.test(o)||(r=t(n.currentTarget),s=r.val(),a=e(s+o),i=(s.replace(/\D/g,"")+o).length,u=16,a&&(u=a.length[a.length.length-1]),i>=u||null!=r.prop("selectionStart")&&r.prop("selectionStart")!==s.length)?void 0:(l=a&&"amex"===a.type?/^(\d{4}|\d{4}\s\d{6})$/:/(?:^|\s)(\d{4})$/,l.test(s)?(n.preventDefault(),setTimeout(function(){return r.val(s+" "+o)})):l.test(s+o)?(n.preventDefault(),setTimeout(function(){return r.val(s+o+" ")})):void 0)},o=function(e){var n,r;return n=t(e.currentTarget),r=n.val(),8!==e.which||null!=n.prop("selectionStart")&&n.prop("selectionStart")!==r.length?void 0:/\d\s$/.test(r)?(e.preventDefault(),setTimeout(function(){return n.val(r.replace(/\d\s$/,""))})):/\s\d?$/.test(r)?(e.preventDefault(),setTimeout(function(){return n.val(r.replace(/\d$/,""))})):void 0},f=function(e){var n;return n=t(e.currentTarget),setTimeout(function(){var e;return e=n.val(),e=m(e),e=t.payment.formatExpiry(e),$(e,n)})},u=function(e){var n,r,a;return r=String.fromCharCode(e.which),/^\d+$/.test(r)?(n=t(e.currentTarget),a=n.val()+r,/^\d$/.test(a)&&"0"!==a&&"1"!==a?(e.preventDefault(),setTimeout(function(){return n.val("0"+a+" / ")})):/^\d\d$/.test(a)?(e.preventDefault(),setTimeout(function(){var t,e;return t=parseInt(a[0],10),e=parseInt(a[1],10),e>2&&0!==t?n.val("0"+t+" / "+e):n.val(""+a+" / ")})):void 0):void 0},s=function(e){var n,r,a;return r=String.fromCharCode(e.which),/^\d+$/.test(r)?(n=t(e.currentTarget),a=n.val(),/^\d\d$/.test(a)?n.val(""+a+" / "):void 0):void 0},c=function(e){var n,r,a;return a=String.fromCharCode(e.which),"/"===a||" "===a?(n=t(e.currentTarget),r=n.val(),/^\d$/.test(r)&&"0"!==r?n.val("0"+r+" / "):void 0):void 0},i=function(e){var n,r;return n=t(e.currentTarget),r=n.val(),8!==e.which||null!=n.prop("selectionStart")&&n.prop("selectionStart")!==r.length?void 0:/\d\s\/\s$/.test(r)?(e.preventDefault(),setTimeout(function(){return n.val(r.replace(/\d\s\/\s$/,""))})):void 0},g=function(e){var n;return n=t(e.currentTarget),setTimeout(function(){var t;return t=n.val(),t=m(t),t=t.replace(/\D/g,"").slice(0,4),$(t,n)})},w=function(t){var e;return t.metaKey||t.ctrlKey?!0:32===t.which?!1:0===t.which?!0:t.which<33?!0:(e=String.fromCharCode(t.which),!!/[\d\s]/.test(e))},C=function(n){var r,a,o,i;return r=t(n.currentTarget),o=String.fromCharCode(n.which),/^\d+$/.test(o)&&!h(r)?(i=(r.val()+o).replace(/\D/g,""),a=e(i),a?i.length<=a.length[a.length.length-1]:i.length<=16):void 0},T=function(e){var n,r,a;return n=t(e.currentTarget),r=String.fromCharCode(e.which),/^\d+$/.test(r)&&!h(n)?(a=n.val()+r,a=a.replace(/\D/g,""),a.length>6?!1:void 0):void 0},y=function(e){var n,r,a;return n=t(e.currentTarget),r=String.fromCharCode(e.which),/^\d+$/.test(r)&&!h(n)?(a=n.val()+r,a.length<=4):void 0},D=function(e){var n,a,o,i,l;return n=t(e.currentTarget),l=n.val(),i=t.payment.cardType(l)||"unknown",n.hasClass(i)?void 0:(a=function(){var t,e,n;for(n=[],t=0,e=r.length;e>t;t++)o=r[t],n.push(o.type);return n}(),n.removeClass("unknown"),n.removeClass(a.join(" ")),n.addClass(i),n.toggleClass("identified","unknown"!==i),n.trigger("payment.cardType",i))},t.payment.fn.formatCardCVC=function(){return this.on("keypress",w),this.on("keypress",y),this.on("paste",g),this.on("change",g),this.on("input",g),this},t.payment.fn.formatCardExpiry=function(){return this.on("keypress",w),this.on("keypress",T),this.on("keypress",u),this.on("keypress",c),this.on("keypress",s),this.on("keydown",i),this.on("change",f),this.on("input",f),this},t.payment.fn.formatCardNumber=function(){return this.on("keypress",w),this.on("keypress",C),this.on("keypress",l),this.on("keydown",o),this.on("keyup",D),this.on("paste",v),this.on("change",v),this.on("input",v),this.on("input",D),this},t.payment.fn.restrictNumeric=function(){return this.on("keypress",w),this.on("paste",d),this.on("change",d),this.on("input",d),this},t.payment.fn.cardExpiryVal=function(){return t.payment.cardExpiryVal(t(this).val())},t.payment.cardExpiryVal=function(t){var e,n,r,a;return a=t.split(/[\s\/]+/,2),e=a[0],r=a[1],2===(null!=r?r.length:void 0)&&/^\d+$/.test(r)&&(n=(new Date).getFullYear(),n=n.toString().slice(0,2),r=n+r),e=parseInt(e,10),r=parseInt(r,10),{month:e,year:r}},t.payment.validateCardNumber=function(t){var n,r;return t=(t+"").replace(/\s+|-/g,""),/^\d+$/.test(t)?(n=e(t),n?(r=t.length,k.call(n.length,r)>=0&&(n.luhn===!1||p(t))):!1):!1},t.payment.validateCardExpiry=function(e,n){var r,a,o;return"object"==typeof e&&"month"in e&&(o=e,e=o.month,n=o.year),e&&n?(e=t.trim(e),n=t.trim(n),/^\d+$/.test(e)&&/^\d+$/.test(n)&&e>=1&&12>=e?(2===n.length&&(n=70>n?"20"+n:"19"+n),4!==n.length?!1:(a=new Date(n,e),r=new Date,a.setMonth(a.getMonth()-1),a.setMonth(a.getMonth()+1,1),a>r)):!1):!1},t.payment.validateCardCVC=function(e,r){var a,o;return e=t.trim(e),/^\d+$/.test(e)?(a=n(r),null!=a?(o=e.length,k.call(a.cvcLength,o)>=0):e.length>=3&&e.length<=4):!1},t.payment.cardType=function(t){var n;return t?(null!=(n=e(t))?n.type:void 0)||null:null},t.payment.formatCardNumber=function(n){var r,a,o,i;return n=n.replace(/\D/g,""),(r=e(n))?(o=r.length[r.length.length-1],n=n.slice(0,o),r.format.global?null!=(i=n.match(r.format))?i.join(" "):void 0:(a=r.format.exec(n),null!=a?(a.shift(),a=t.grep(a,function(t){return t}),a.join(" ")):void 0)):n},t.payment.formatExpiry=function(t){var e,n,r,a;return(n=t.match(/^\D*(\d{1,2})(\D+)?(\d{1,4})?/))?(e=n[1]||"",r=n[2]||"",a=n[3]||"",a.length>0?r=" / ":" /"===r?(e=e.substring(0,1),r=""):2===e.length||r.length>0?r=" / ":1===e.length&&"0"!==e&&"1"!==e&&(e="0"+e,r=" / "),e+r+a):""}}).call(this); \ No newline at end of file diff --git a/public/vendor/jquery.payment/package.json b/public/vendor/jquery.payment/package.json new file mode 100644 index 00000000..df86d205 --- /dev/null +++ b/public/vendor/jquery.payment/package.json @@ -0,0 +1,31 @@ +{ + "name": "jquery.payment", + "version": "1.4.2", + "description": "A general purpose library for building credit card forms, validating inputs and formatting numbers.", + "keywords": [ + "payment", + "cc", + "card" + ], + "author": "Stripe (https://www.stripe.com)", + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/stripe/jquery.payment.git" + }, + "main": "lib/jquery.payment.js", + "scripts": { + "test": "cake test" + }, + "dependencies": { + "jquery": ">=1.7" + }, + "devDependencies": { + "cake": "~0.1", + "coffee-script": "~1.7", + "jsdom": "~7.2", + "mocha": "~1.18", + "uglify-js": "~2.4.24", + "zeptojs": "~1.1" + } +} diff --git a/public/vendor/jquery.payment/payment.jquery.json b/public/vendor/jquery.payment/payment.jquery.json new file mode 100644 index 00000000..7c0199fc --- /dev/null +++ b/public/vendor/jquery.payment/payment.jquery.json @@ -0,0 +1,29 @@ +{ + "name": "payment", + "version": "1.4.2", + "title": "jQuery.payment", + "description": "A general purpose library for building credit card forms, validating inputs and formatting numbers.", + "keywords": [ + "payment", + "cc", + "card" + ], + "author": { + "name": "Stripe", + "url": "https://www.stripe.com", + "email": "support+github@stripe.com" + }, + "licenses": [ + { + "type": "MIT", + "url": "https://github.com/stripe/jquery.payment/blob/master/LICENSE" + } + ], + "homepage": "https://github.com/stripe/jquery.payment", + "docs": "https://github.com/stripe/jquery.payment", + "bugs": "https://github.com/stripe/jquery.payment/issues", + "demo": "http://stripe.github.io/jquery.payment/example", + "dependencies": { + "jquery": ">=1.7" + } +} diff --git a/public/vendor/jquery.payment/src/jquery.payment.coffee b/public/vendor/jquery.payment/src/jquery.payment.coffee new file mode 100644 index 00000000..060c18e7 --- /dev/null +++ b/public/vendor/jquery.payment/src/jquery.payment.coffee @@ -0,0 +1,608 @@ +$ = window.jQuery or window.Zepto or window.$ +$.payment = {} +$.payment.fn = {} +$.fn.payment = (method, args...) -> + $.payment.fn[method].apply(this, args) + +# Utils + +defaultFormat = /(\d{1,4})/g + +$.payment.cards = cards = [ + # Specifics patterns must be analysed first or it + # will generate false positives + { + type: 'elo' + patterns: [ + 4011, 4312, 4389, 4514, 4573, 4576, + 5041, 5066, 5067, 509, + 6277, 6362, 6363, 650, 6516, 6550 + ] + format: defaultFormat + length: [16] + cvcLength: [3] + luhn: true + } + { + type: 'visaelectron' + patterns: [ + 4026, 417500, 4405, 4508, 4844, 4913, 4917 + ] + format: defaultFormat + length: [16] + cvcLength: [3] + luhn: true + } + { + type: 'maestro' + patterns: [ + 5018, 502, 503, 506, 56, 58, 639, 6220, 67 + ] + format: defaultFormat + length: [12..19] + cvcLength: [3] + luhn: true + } + { + type: 'forbrugsforeningen' + patterns: [600] + format: defaultFormat + length: [16] + cvcLength: [3] + luhn: true + } + { + type: 'dankort' + patterns: [5019] + format: defaultFormat + length: [16] + cvcLength: [3] + luhn: true + } + # 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..19] + cvcLength: [3] + luhn: false + } + { + type: 'jcb' + patterns: [35] + format: defaultFormat + length: [16] + cvcLength: [3] + luhn: true + } +] + +cardFromNumber = (num) -> + num = (num + '').replace(/\D/g, '') + for card in cards + for pattern in card.patterns + p = pattern + '' + return card if num.substr(0, p.length) == p + +cardFromType = (type) -> + return card for card in cards when card.type is type + +luhnCheck = (num) -> + odd = true + sum = 0 + + digits = (num + '').split('').reverse() + + for digit in digits + digit = parseInt(digit, 10) + digit *= 2 if (odd = !odd) + digit -= 9 if digit > 9 + sum += digit + + sum % 10 == 0 + +hasTextSelected = ($target) -> + # If some text is selected + return true if $target.prop('selectionStart')? and + $target.prop('selectionStart') isnt $target.prop('selectionEnd') + + # If some text is selected in IE + if document?.selection?.createRange? + return true if document.selection.createRange().text + + false + +# Private + +# Safe Val + +safeVal = (value, $target) -> + try + cursor = $target.prop('selectionStart') + catch error + cursor = null + last = $target.val() + $target.val(value) + if cursor != null && $target.is(":focus") + cursor = value.length if cursor is last.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 + prevPair = last[cursor-1..cursor] + currPair = value[cursor-1..cursor] + digit = value[cursor] + cursor = cursor + 1 if /\d/.test(digit) and + prevPair == "#{digit} " and currPair == " #{digit}" + + $target.prop('selectionStart', cursor) + $target.prop('selectionEnd', cursor) + +# Replace Full-Width Chars + +replaceFullWidthChars = (str = '') -> + fullWidth = '\uff10\uff11\uff12\uff13\uff14\uff15\uff16\uff17\uff18\uff19' + halfWidth = '0123456789' + + value = '' + chars = str.split('') + + # Avoid using reserved word `char` + for chr in chars + idx = fullWidth.indexOf(chr) + chr = halfWidth[idx] if idx > -1 + value += chr + + value + +# Format Numeric + +reFormatNumeric = (e) -> + $target = $(e.currentTarget) + setTimeout -> + value = $target.val() + value = replaceFullWidthChars(value) + value = value.replace(/\D/g, '') + safeVal(value, $target) + +# Format Card Number + +reFormatCardNumber = (e) -> + $target = $(e.currentTarget) + setTimeout -> + value = $target.val() + value = replaceFullWidthChars(value) + value = $.payment.formatCardNumber(value) + safeVal(value, $target) + +formatCardNumber = (e) -> + # Only format if input is a number + digit = String.fromCharCode(e.which) + return unless /^\d+$/.test(digit) + + $target = $(e.currentTarget) + value = $target.val() + card = cardFromNumber(value + digit) + length = (value.replace(/\D/g, '') + digit).length + + upperLength = 16 + upperLength = card.length[card.length.length - 1] if card + return if length >= upperLength + + # Return if focus isn't at the end of the text + return if $target.prop('selectionStart')? and + $target.prop('selectionStart') isnt value.length + + if card && card.type is '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() + setTimeout -> $target.val(value + ' ' + digit) + + # If '424' + 2 + else if re.test(value + digit) + e.preventDefault() + setTimeout -> $target.val(value + digit + ' ') + +formatBackCardNumber = (e) -> + $target = $(e.currentTarget) + value = $target.val() + + # Return unless backspacing + return unless e.which is 8 + + # Return if focus isn't at the end of the text + return if $target.prop('selectionStart')? and + $target.prop('selectionStart') isnt value.length + + # Remove the digit + trailing space + if /\d\s$/.test(value) + e.preventDefault() + setTimeout -> $target.val(value.replace(/\d\s$/, '')) + # Remove digit if ends in space + digit + else if /\s\d?$/.test(value) + e.preventDefault() + setTimeout -> $target.val(value.replace(/\d$/, '')) + +# Format Expiry + +reFormatExpiry = (e) -> + $target = $(e.currentTarget) + setTimeout -> + value = $target.val() + value = replaceFullWidthChars(value) + value = $.payment.formatExpiry(value) + safeVal(value, $target) + +formatExpiry = (e) -> + # Only format if input is a number + digit = String.fromCharCode(e.which) + return unless /^\d+$/.test(digit) + + $target = $(e.currentTarget) + val = $target.val() + digit + + if /^\d$/.test(val) and val not in ['0', '1'] + e.preventDefault() + setTimeout -> $target.val("0#{val} / ") + + else if /^\d\d$/.test(val) + e.preventDefault() + setTimeout -> + # Split for months where we have the second digit > 2 (past 12) and turn + # that into (m1)(m2) => 0(m1) / (m2) + m1 = parseInt(val[0], 10) + m2 = parseInt(val[1], 10) + if m2 > 2 and m1 != 0 + $target.val("0#{m1} / #{m2}") + else + $target.val("#{val} / ") + +formatForwardExpiry = (e) -> + digit = String.fromCharCode(e.which) + return unless /^\d+$/.test(digit) + + $target = $(e.currentTarget) + val = $target.val() + + if /^\d\d$/.test(val) + $target.val("#{val} / ") + +formatForwardSlashAndSpace = (e) -> + which = String.fromCharCode(e.which) + return unless which is '/' or which is ' ' + + $target = $(e.currentTarget) + val = $target.val() + + if /^\d$/.test(val) and val isnt '0' + $target.val("0#{val} / ") + +formatBackExpiry = (e) -> + $target = $(e.currentTarget) + value = $target.val() + + # Return unless backspacing + return unless e.which is 8 + + # Return if focus isn't at the end of the text + return if $target.prop('selectionStart')? and + $target.prop('selectionStart') isnt value.length + + # Remove the trailing space + last digit + if /\d\s\/\s$/.test(value) + e.preventDefault() + setTimeout -> $target.val(value.replace(/\d\s\/\s$/, '')) + +# Format CVC + +reFormatCVC = (e) -> + $target = $(e.currentTarget) + setTimeout -> + value = $target.val() + value = replaceFullWidthChars(value) + value = value.replace(/\D/g, '')[0...4] + safeVal(value, $target) + +# Restrictions + +restrictNumeric = (e) -> + # Key event is for a browser shortcut + return true if e.metaKey or e.ctrlKey + + # If keycode is a space + return false if e.which is 32 + + # If keycode is a special char (WebKit) + return true if e.which is 0 + + # If char is a special char (Firefox) + return true if e.which < 33 + + input = String.fromCharCode(e.which) + + # Char is a number or a space + !!/[\d\s]/.test(input) + +restrictCardNumber = (e) -> + $target = $(e.currentTarget) + digit = String.fromCharCode(e.which) + return unless /^\d+$/.test(digit) + + return if hasTextSelected($target) + + # Restrict number of digits + value = ($target.val() + digit).replace(/\D/g, '') + card = cardFromNumber(value) + + if card + value.length <= card.length[card.length.length - 1] + else + # All other cards are 16 digits long + value.length <= 16 + +restrictExpiry = (e) -> + $target = $(e.currentTarget) + digit = String.fromCharCode(e.which) + return unless /^\d+$/.test(digit) + + return if hasTextSelected($target) + + value = $target.val() + digit + value = value.replace(/\D/g, '') + + return false if value.length > 6 + +restrictCVC = (e) -> + $target = $(e.currentTarget) + digit = String.fromCharCode(e.which) + return unless /^\d+$/.test(digit) + + return if hasTextSelected($target) + + val = $target.val() + digit + val.length <= 4 + +setCardType = (e) -> + $target = $(e.currentTarget) + val = $target.val() + cardType = $.payment.cardType(val) or 'unknown' + + unless $target.hasClass(cardType) + allTypes = (card.type for card in cards) + + $target.removeClass('unknown') + $target.removeClass(allTypes.join(' ')) + + $target.addClass(cardType) + $target.toggleClass('identified', cardType isnt 'unknown') + $target.trigger('payment.cardType', cardType) + +# Public + +# Formatting + +$.payment.fn.formatCardCVC = -> + @on('keypress', restrictNumeric) + @on('keypress', restrictCVC) + @on('paste', reFormatCVC) + @on('change', reFormatCVC) + @on('input', reFormatCVC) + this + +$.payment.fn.formatCardExpiry = -> + @on('keypress', restrictNumeric) + @on('keypress', restrictExpiry) + @on('keypress', formatExpiry) + @on('keypress', formatForwardSlashAndSpace) + @on('keypress', formatForwardExpiry) + @on('keydown', formatBackExpiry) + @on('change', reFormatExpiry) + @on('input', reFormatExpiry) + this + +$.payment.fn.formatCardNumber = -> + @on('keypress', restrictNumeric) + @on('keypress', restrictCardNumber) + @on('keypress', formatCardNumber) + @on('keydown', formatBackCardNumber) + @on('keyup', setCardType) + @on('paste', reFormatCardNumber) + @on('change', reFormatCardNumber) + @on('input', reFormatCardNumber) + @on('input', setCardType) + this + +# Restrictions + +$.payment.fn.restrictNumeric = -> + @on('keypress', restrictNumeric) + @on('paste', reFormatNumeric) + @on('change', reFormatNumeric) + @on('input', reFormatNumeric) + this + +# Validations + +$.payment.fn.cardExpiryVal = -> + $.payment.cardExpiryVal($(this).val()) + +$.payment.cardExpiryVal = (value) -> + [month, year] = value.split(/[\s\/]+/, 2) + + # Allow for year shortcut + if year?.length is 2 and /^\d+$/.test(year) + prefix = (new Date).getFullYear() + prefix = prefix.toString()[0..1] + year = prefix + year + + month = parseInt(month, 10) + year = parseInt(year, 10) + + month: month, year: year + +$.payment.validateCardNumber = (num) -> + num = (num + '').replace(/\s+|-/g, '') + return false unless /^\d+$/.test(num) + + card = cardFromNumber(num) + return false unless card + + num.length in card.length and + (card.luhn is false or luhnCheck(num)) + +$.payment.validateCardExpiry = (month, year) -> + # Allow passing an object + if typeof month is 'object' and 'month' of month + {month, year} = month + + return false unless month and year + + month = $.trim(month) + year = $.trim(year) + + return false unless /^\d+$/.test(month) + return false unless /^\d+$/.test(year) + return false unless 1 <= month <= 12 + + if year.length == 2 + if year < 70 + year = "20#{year}" + else + year = "19#{year}" + + return false unless year.length == 4 + + expiry = new Date(year, month) + currentTime = new Date + + # Months start from 0 in JavaScript + expiry.setMonth(expiry.getMonth() - 1) + + # The cc expires at the end of the month, + # so we need to make the expiry the first day + # of the month after + expiry.setMonth(expiry.getMonth() + 1, 1) + + expiry > currentTime + +$.payment.validateCardCVC = (cvc, type) -> + cvc = $.trim(cvc) + return false unless /^\d+$/.test(cvc) + + card = cardFromType(type) + if card? + # Check against a explicit card type + cvc.length in card.cvcLength + else + # Check against all types + cvc.length >= 3 and cvc.length <= 4 + +$.payment.cardType = (num) -> + return null unless num + cardFromNumber(num)?.type or null + +$.payment.formatCardNumber = (num) -> + num = num.replace(/\D/g, '') + card = cardFromNumber(num) + return num unless card + + upperLength = card.length[card.length.length - 1] + num = num[0...upperLength] + + if card.format.global + num.match(card.format)?.join(' ') + else + groups = card.format.exec(num) + return unless groups? + groups.shift() + groups = $.grep(groups, (n) -> n) # Filter empty groups + groups.join(' ') + +$.payment.formatExpiry = (expiry) -> + parts = expiry.match(/^\D*(\d{1,2})(\D+)?(\d{1,4})?/) + return '' unless parts + + mon = parts[1] || '' + sep = parts[2] || '' + year = parts[3] || '' + + if year.length > 0 + sep = ' / ' + + else if sep is ' /' + mon = mon.substring(0, 1) + sep = '' + + else if mon.length == 2 or sep.length > 0 + sep = ' / ' + + else if mon.length == 1 and mon not in ['0', '1'] + mon = "0#{mon}" + sep = ' / ' + + return mon + sep + year diff --git a/public/vendor/jquery.payment/test/jquery.coffee b/public/vendor/jquery.payment/test/jquery.coffee new file mode 100644 index 00000000..924312b3 --- /dev/null +++ b/public/vendor/jquery.payment/test/jquery.coffee @@ -0,0 +1,7 @@ +window = require('jsdom').jsdom().defaultView +global.$ = require('jquery')(window) +global.window = window +global.document = window.document + +require('../src/jquery.payment') +require('./specs') diff --git a/public/vendor/jquery.payment/test/specs.coffee b/public/vendor/jquery.payment/test/specs.coffee new file mode 100644 index 00000000..6f9621ce --- /dev/null +++ b/public/vendor/jquery.payment/test/specs.coffee @@ -0,0 +1,407 @@ +assert = require('assert') + +describe 'jquery.payment', -> + describe 'Validating a card number', -> + it 'should fail if empty', -> + topic = $.payment.validateCardNumber '' + assert.equal topic, false + + it 'should fail if is a bunch of spaces', -> + topic = $.payment.validateCardNumber ' ' + assert.equal topic, false + + it 'should success if is valid', -> + topic = $.payment.validateCardNumber '4242424242424242' + assert.equal topic, true + + it 'that has dashes in it but is valid', -> + topic = $.payment.validateCardNumber '4242-4242-4242-4242' + assert.equal topic, true + + it 'should succeed if it has spaces in it but is valid', -> + topic = $.payment.validateCardNumber '4242 4242 4242 4242' + assert.equal topic, true + + it 'that does not pass the luhn checker', -> + topic = $.payment.validateCardNumber '4242424242424241' + assert.equal topic, false + + it 'should fail if is more than 16 digits', -> + topic = $.payment.validateCardNumber '42424242424242424' + assert.equal topic, false + + it 'should fail if is less than 10 digits', -> + topic = $.payment.validateCardNumber '424242424' + assert.equal topic, false + + it 'should fail with non-digits', -> + topic = $.payment.validateCardNumber '4242424e42424241' + assert.equal topic, false + + it 'should validate for all card types', -> + assert($.payment.validateCardNumber('4917300800000000'), 'visaelectron') + + assert($.payment.validateCardNumber('6759649826438453'), 'maestro') + + assert($.payment.validateCardNumber('6007220000000004'), 'forbrugsforeningen') + + assert($.payment.validateCardNumber('5019717010103742'), 'dankort') + + assert($.payment.validateCardNumber('4111111111111111'), 'visa') + assert($.payment.validateCardNumber('4012888888881881'), 'visa') + assert($.payment.validateCardNumber('4222222222222'), 'visa') + assert($.payment.validateCardNumber('4462030000000000'), 'visa') + assert($.payment.validateCardNumber('4484070000000000'), 'visa') + + assert($.payment.validateCardNumber('5555555555554444'), 'mastercard') + assert($.payment.validateCardNumber('5454545454545454'), 'mastercard') + assert($.payment.validateCardNumber('2221000002222221'), 'mastercard') + + assert($.payment.validateCardNumber('378282246310005'), 'amex') + assert($.payment.validateCardNumber('371449635398431'), 'amex') + assert($.payment.validateCardNumber('378734493671000'), 'amex') + + assert($.payment.validateCardNumber('30569309025904'), 'dinersclub') + assert($.payment.validateCardNumber('38520000023237'), 'dinersclub') + assert($.payment.validateCardNumber('36700102000000'), 'dinersclub') + assert($.payment.validateCardNumber('36148900647913'), 'dinersclub') + + assert($.payment.validateCardNumber('6011111111111117'), 'discover') + assert($.payment.validateCardNumber('6011000990139424'), 'discover') + + assert($.payment.validateCardNumber('6271136264806203568'), 'unionpay') + assert($.payment.validateCardNumber('6236265930072952775'), 'unionpay') + assert($.payment.validateCardNumber('6204679475679144515'), 'unionpay') + assert($.payment.validateCardNumber('6216657720782466507'), 'unionpay') + + assert($.payment.validateCardNumber('3530111333300000'), 'jcb') + assert($.payment.validateCardNumber('3566002020360505'), 'jcb') + + assert($.payment.validateCardNumber('6362970000457013'), 'elo') + assert($.payment.validateCardNumber('5066991111111118'), 'elo') + + describe 'Validating a CVC', -> + it 'should fail if is empty', -> + topic = $.payment.validateCardCVC '' + assert.equal topic, false + + it 'should pass if is valid', -> + topic = $.payment.validateCardCVC '123' + assert.equal topic, true + + it 'should fail with non-digits', -> + topic = $.payment.validateCardNumber '12e' + assert.equal topic, false + + it 'should fail with less than 3 digits', -> + topic = $.payment.validateCardNumber '12' + assert.equal topic, false + + it 'should fail with more than 4 digits', -> + topic = $.payment.validateCardNumber '12345' + assert.equal topic, false + + describe 'Validating an expiration date', -> + it 'should fail expires is before the current year', -> + currentTime = new Date() + topic = $.payment.validateCardExpiry currentTime.getMonth() + 1, currentTime.getFullYear() - 1 + assert.equal topic, false + + it 'that expires in the current year but before current month', -> + currentTime = new Date() + topic = $.payment.validateCardExpiry currentTime.getMonth(), currentTime.getFullYear() + assert.equal topic, false + + it 'that has an invalid month', -> + currentTime = new Date() + topic = $.payment.validateCardExpiry 13, currentTime.getFullYear() + assert.equal topic, false + + it 'that is this year and month', -> + currentTime = new Date() + topic = $.payment.validateCardExpiry currentTime.getMonth() + 1, currentTime.getFullYear() + assert.equal topic, true + + it 'that is just after this month', -> + # Remember - months start with 0 in JavaScript! + currentTime = new Date() + topic = $.payment.validateCardExpiry currentTime.getMonth() + 1, currentTime.getFullYear() + assert.equal topic, true + + it 'that is after this year', -> + currentTime = new Date() + topic = $.payment.validateCardExpiry currentTime.getMonth() + 1, currentTime.getFullYear() + 1 + assert.equal topic, true + + it 'that is a two-digit year', -> + currentTime = new Date() + topic = $.payment.validateCardExpiry currentTime.getMonth() + 1, ('' + currentTime.getFullYear())[0...2] + assert.equal topic, true + + it 'that is a two-digit year in the past (i.e. 1990s)', -> + currentTime = new Date() + topic = $.payment.validateCardExpiry currentTime.getMonth() + 1, 99 + assert.equal topic, false + + it 'that has string numbers', -> + currentTime = new Date() + currentTime.setFullYear(currentTime.getFullYear() + 1, currentTime.getMonth() + 2) + topic = $.payment.validateCardExpiry currentTime.getMonth() + 1 + '', currentTime.getFullYear() + '' + assert.equal topic, true + + it 'that has non-numbers', -> + topic = $.payment.validateCardExpiry 'h12', '3300' + assert.equal topic, false + + it 'should fail if year or month is NaN', -> + topic = $.payment.validateCardExpiry '12', NaN + assert.equal topic, false + + it 'should support year shorthand', -> + assert.equal $.payment.validateCardExpiry('05', '20'), true + + describe 'Validating a CVC number', -> + it 'should validate a three digit number with no card type', -> + topic = $.payment.validateCardCVC('123') + assert.equal topic, true + + it 'should validate a three digit number with card type amex', -> + topic = $.payment.validateCardCVC('123', 'amex') + assert.equal topic, true + + it 'should validate a three digit number with card type other than amex', -> + topic = $.payment.validateCardCVC('123', 'visa') + assert.equal topic, true + + it 'should not validate a four digit number with a card type other than amex', -> + topic = $.payment.validateCardCVC('1234', 'visa') + assert.equal topic, false + + it 'should validate a four digit number with card type amex', -> + topic = $.payment.validateCardCVC('1234', 'amex') + assert.equal topic, true + + it 'should not validate a number larger than 4 digits', -> + topic = $.payment.validateCardCVC('12344') + assert.equal topic, false + + describe 'Parsing an expiry value', -> + it 'should parse string expiry', -> + topic = $.payment.cardExpiryVal('03 / 2025') + assert.deepEqual topic, month: 3, year: 2025 + + it 'should support shorthand year', -> + topic = $.payment.cardExpiryVal('05/04') + assert.deepEqual topic, month: 5, year: 2004 + + it 'should return NaN when it cannot parse', -> + topic = $.payment.cardExpiryVal('05/dd') + assert isNaN(topic.year) + + describe 'Getting a card type', -> + it 'should return Visa that begins with 40', -> + topic = $.payment.cardType '4012121212121212' + assert.equal topic, 'visa' + + it 'that begins with 2 should return MasterCard', -> + topic = $.payment.cardType '2221000002222221' + assert.equal topic, 'mastercard' + + it 'that begins with 5 should return MasterCard', -> + topic = $.payment.cardType '5555555555554444' + assert.equal topic, 'mastercard' + + it 'that begins with 34 should return American Express', -> + topic = $.payment.cardType '3412121212121212' + assert.equal topic, 'amex' + + it 'that begins with 457393 should return Elo', -> + topic = $.payment.cardType '4573931212121212' + assert.equal topic, 'elo' + + it 'that begins with 431274 should return Elo', -> + topic = $.payment.cardType '4312740000000000' + assert.equal topic, 'elo' + + it 'that begins with 650031 should return Elo', -> + topic = $.payment.cardType '6500310000000000' + assert.equal topic, 'elo' + + it 'that is not numbers should return null', -> + topic = $.payment.cardType 'aoeu' + assert.equal topic, null + + it 'that has unrecognized beginning numbers should return null', -> + topic = $.payment.cardType 'aoeu' + assert.equal topic, null + + it 'should return correct type for all test numbers', -> + assert.equal($.payment.cardType('4917300800000000'), 'visaelectron') + + assert.equal($.payment.cardType('6759649826438453'), 'maestro') + assert.equal($.payment.cardType('6220180012340012345'), 'maestro') + + assert.equal($.payment.cardType('6007220000000004'), 'forbrugsforeningen') + + assert.equal($.payment.cardType('5019717010103742'), 'dankort') + + assert.equal($.payment.cardType('4111111111111111'), 'visa') + assert.equal($.payment.cardType('4012888888881881'), 'visa') + assert.equal($.payment.cardType('4222222222222'), 'visa') + assert.equal($.payment.cardType('4462030000000000'), 'visa') + assert.equal($.payment.cardType('4484070000000000'), 'visa') + + assert.equal($.payment.cardType('5555555555554444'), 'mastercard') + assert.equal($.payment.cardType('5454545454545454'), 'mastercard') + assert.equal($.payment.cardType('2221000002222221'), 'mastercard') + + assert.equal($.payment.cardType('378282246310005'), 'amex') + assert.equal($.payment.cardType('371449635398431'), 'amex') + assert.equal($.payment.cardType('378734493671000'), 'amex') + + assert.equal($.payment.cardType('30569309025904'), 'dinersclub') + assert.equal($.payment.cardType('38520000023237'), 'dinersclub') + assert.equal($.payment.cardType('36700102000000'), 'dinersclub') + assert.equal($.payment.cardType('36148900647913'), 'dinersclub') + + assert.equal($.payment.cardType('6011111111111117'), 'discover') + assert.equal($.payment.cardType('6011000990139424'), 'discover') + + assert.equal($.payment.cardType('6271136264806203568'), 'unionpay') + assert.equal($.payment.cardType('6236265930072952775'), 'unionpay') + assert.equal($.payment.cardType('6204679475679144515'), 'unionpay') + assert.equal($.payment.cardType('6216657720782466507'), 'unionpay') + + assert.equal($.payment.cardType('3530111333300000'), 'jcb') + assert.equal($.payment.cardType('3566002020360505'), 'jcb') + + assert.equal($.payment.cardType('6363689826438453'), 'elo') + + describe 'Extending the card collection', -> + it 'should expose an array of standard card types', -> + cards = $.payment.cards + assert Array.isArray(cards) + + visa = card for card in cards when card.type is 'visa' + assert.notEqual visa, null + + it 'should support new card types', -> + wing = { + type: 'wing' + patterns: [501818] + length: [16] + luhn: false + } + $.payment.cards.unshift wing + + wingCard = '5018 1818 1818 1818' + assert.equal $.payment.cardType(wingCard), 'wing' + assert.equal $.payment.validateCardNumber(wingCard), true + + describe 'formatCardNumber', -> + it 'should format cc number correctly', (done) -> + $number = $('').payment('formatCardNumber') + $number.val('4242').prop('selectionStart', 4) + + e = $.Event('keypress') + e.which = 52 # '4' + $number.trigger(e) + + setTimeout -> + assert.equal $number.val(), '4242 4' + done() + + it 'should format amex cc number correctly', (done) -> + $number = $('').payment('formatCardNumber') + $number.val('3782').prop('selectionStart', 4) + + e = $.Event('keypress') + e.which = 56 # '8' + $number.trigger(e) + + setTimeout -> + assert.equal $number.val(), '3782 8' + done() + + it 'should format full-width cc number correctly', (done) -> + $number = $('').payment('formatCardNumber') + $number.val('\uff14\uff12\uff14\uff12') + + e = $.Event('input') + $number.trigger(e) + + setTimeout -> + assert.equal $number.val(), '4242' + done() + + describe 'formatCardExpiry', -> + it 'should format month shorthand correctly', (done) -> + $expiry = $('').payment('formatCardExpiry') + $expiry.val('') + + e = $.Event('keypress') + e.which = 52 # '4' + $expiry.trigger(e) + + setTimeout -> + assert.equal $expiry.val(), '04 / ' + done() + + it 'should format forward slash shorthand correctly', (done) -> + $expiry = $('').payment('formatCardExpiry') + $expiry.val('1') + + e = $.Event('keypress') + e.which = 47 # '/' + $expiry.trigger(e) + + setTimeout -> + assert.equal $expiry.val(), '01 / ' + done() + + it 'should only allow numbers', (done) -> + $expiry = $('').payment('formatCardExpiry') + $expiry.val('1') + + e = $.Event('keypress') + e.which = 100 # 'd' + $expiry.trigger(e) + + setTimeout -> + assert.equal $expiry.val(), '1' + done() + + it 'should format full-width expiry correctly', (done) -> + $expiry = $('').payment('formatCardExpiry') + $expiry.val('\uff10\uff18\uff11\uff15') + + e = $.Event('input') + $expiry.trigger(e) + + setTimeout -> + assert.equal $expiry.val(), '08 / 15' + done() + + it 'should format month expiry correctly when val is past 12', (done) -> + $expiry = $('').payment('formatCardExpiry') + $expiry.val('1') + + e = $.Event('keypress') + e.which = 52 # '4' + $expiry.trigger(e) + + setTimeout -> + assert.equal $expiry.val(), '01 / 4' + done() + + it 'should format month expiry corrrectly for 0 followed by single digit > 2', (done) -> + $expiry = $('').payment('formatCardExpiry') + $expiry.val('0') + + e = $.Event('keypress') + e.which = 53 # '5' + $expiry.trigger(e) + + setTimeout -> + assert.equal $expiry.val(), '05 / ' + done() diff --git a/public/vendor/jquery.payment/test/zepto.coffee b/public/vendor/jquery.payment/test/zepto.coffee new file mode 100644 index 00000000..d315b1a8 --- /dev/null +++ b/public/vendor/jquery.payment/test/zepto.coffee @@ -0,0 +1,9 @@ +window = require('jsdom').jsdom().defaultView +global.window = window +global.document = window.document +global.getComputedStyle = window.getComputedStyle +global.$ = require('zeptojs') +window.$ = global.$ + +require('../src/jquery.payment') +require('./specs')