diff --git a/bower.json b/bower.json
index 7b80af7445..868184f00a 100644
--- a/bower.json
+++ b/bower.json
@@ -34,7 +34,8 @@
"jSignature": "brinley/jSignature#^2.1.0",
"select2": "select2-dist#^4.0.3",
"mousetrap": "^1.6.0",
- "tablesorter": "jquery.tablesorter#^2.28.4"
+ "tablesorter": "jquery.tablesorter#^2.28.4",
+ "card": "^2.1.1"
},
"resolutions": {
"jquery": "~1.11"
diff --git a/gulpfile.js b/gulpfile.js
index e1e7bb4edc..c79457f692 100644
--- a/gulpfile.js
+++ b/gulpfile.js
@@ -95,6 +95,10 @@ elixir(function(mix) {
bowerDir + '/bootstrap-daterangepicker/daterangepicker.js'
], 'public/js/daterangepicker.min.js');
+ mix.scripts([
+ bowerDir + '/card/dist/card.js',
+ ], 'public/js/card.min.js');
+
mix.scripts([
bowerDir + '/tablesorter/dist/js/jquery.tablesorter.combined.js',
bowerDir + '/tablesorter/dist/js/widgets/widget-grouping.min.js',
diff --git a/public/css/card.css b/public/css/card.css
new file mode 100644
index 0000000000..ee5d3d6ff7
--- /dev/null
+++ b/public/css/card.css
@@ -0,0 +1,2497 @@
+var card =
+/******/ (function(modules) { // webpackBootstrap
+/******/ // The module cache
+/******/ var installedModules = {};
+
+/******/ // The require function
+/******/ function __webpack_require__(moduleId) {
+
+/******/ // Check if module is in cache
+/******/ if(installedModules[moduleId])
+/******/ return installedModules[moduleId].exports;
+
+/******/ // Create a new module (and put it into the cache)
+/******/ var module = installedModules[moduleId] = {
+/******/ exports: {},
+/******/ id: moduleId,
+/******/ loaded: false
+/******/ };
+
+/******/ // Execute the module function
+/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
+
+/******/ // Flag the module as loaded
+/******/ module.loaded = true;
+
+/******/ // Return the exports of the module
+/******/ return module.exports;
+/******/ }
+
+
+/******/ // expose the modules object (__webpack_modules__)
+/******/ __webpack_require__.m = modules;
+
+/******/ // expose the module cache
+/******/ __webpack_require__.c = installedModules;
+
+/******/ // __webpack_public_path__
+/******/ __webpack_require__.p = "";
+
+/******/ // Load entry module and return exports
+/******/ return __webpack_require__(0);
+/******/ })
+/************************************************************************/
+/******/ ([
+/* 0 */
+/***/ function(module, exports, __webpack_require__) {
+
+ /* WEBPACK VAR INJECTION */(function(global) {var Card, QJ, extend, payment,
+ __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
+
+ __webpack_require__(1);
+
+ QJ = __webpack_require__(5);
+
+ payment = __webpack_require__(6);
+
+ extend = __webpack_require__(7);
+
+ Card = (function() {
+ var bindVal;
+
+ Card.prototype.initializedDataAttr = "data-jp-card-initialized";
+
+ Card.prototype.cardTemplate = '' + '
' + '
' + '
' + '
' + '
visa
' + '
MasterCard
' + '
Maestro
' + '
' + '
discover
' + '
' + '
' + '
' + '
{{cvc}}
' + '
{{number}}
' + '
{{name}}
' + '
{{expiry}}
' + '
' + '
' + '
' + '
' + '
{{cvc}}
' + '
' + '
' + '
' + '
';
+
+ Card.prototype.template = function(tpl, data) {
+ return tpl.replace(/\{\{(.*?)\}\}/g, function(match, key, str) {
+ return data[key];
+ });
+ };
+
+ Card.prototype.cardTypes = ['jp-card-amex', 'jp-card-dankort', 'jp-card-dinersclub', 'jp-card-discover', 'jp-card-jcb', 'jp-card-laser', 'jp-card-maestro', 'jp-card-mastercard', 'jp-card-unionpay', 'jp-card-visa', 'jp-card-visaelectron', 'jp-card-elo'];
+
+ Card.prototype.defaults = {
+ formatting: true,
+ formSelectors: {
+ numberInput: 'input[name="number"]',
+ expiryInput: 'input[name="expiry"]',
+ cvcInput: 'input[name="cvc"]',
+ nameInput: 'input[name="name"]'
+ },
+ cardSelectors: {
+ cardContainer: '.jp-card-container',
+ card: '.jp-card',
+ numberDisplay: '.jp-card-number',
+ expiryDisplay: '.jp-card-expiry',
+ cvcDisplay: '.jp-card-cvc',
+ nameDisplay: '.jp-card-name'
+ },
+ messages: {
+ validDate: 'valid\nthru',
+ monthYear: 'month/year'
+ },
+ placeholders: {
+ number: '•••• •••• •••• ••••',
+ cvc: '•••',
+ expiry: '••/••',
+ name: 'Full Name'
+ },
+ masks: {
+ cardNumber: false
+ },
+ classes: {
+ valid: 'jp-card-valid',
+ invalid: 'jp-card-invalid'
+ },
+ debug: false
+ };
+
+ function Card(opts) {
+ this.maskCardNumber = __bind(this.maskCardNumber, this);
+ var toInitialize;
+ this.options = extend(true, this.defaults, opts);
+ if (!this.options.form) {
+ console.log("Please provide a form");
+ return;
+ }
+ this.$el = QJ(this.options.form);
+ if (!this.options.container) {
+ console.log("Please provide a container");
+ return;
+ }
+ this.$container = QJ(this.options.container);
+ toInitialize = QJ.isDOMElement(this.$container) ? this.$container : this.$container[0];
+ if (toInitialize.getAttribute(this.initializedDataAttr)) {
+ return;
+ }
+ toInitialize.setAttribute(this.initializedDataAttr, true);
+ this.render();
+ this.attachHandlers();
+ this.handleInitialPlaceholders();
+ }
+
+ Card.prototype.render = function() {
+ var $cardContainer, baseWidth, name, obj, selector, ua, _ref, _ref1;
+ QJ.append(this.$container, this.template(this.cardTemplate, extend({}, this.options.messages, this.options.placeholders)));
+ _ref = this.options.cardSelectors;
+ for (name in _ref) {
+ selector = _ref[name];
+ this["$" + name] = QJ.find(this.$container, selector);
+ }
+ _ref1 = this.options.formSelectors;
+ for (name in _ref1) {
+ selector = _ref1[name];
+ selector = this.options[name] ? this.options[name] : selector;
+ obj = QJ.find(this.$el, selector);
+ if (!obj.length && this.options.debug) {
+ console.error("Card can't find a " + name + " in your form.");
+ }
+ this["$" + name] = obj;
+ }
+ if (this.options.formatting) {
+ Payment.formatCardNumber(this.$numberInput);
+ Payment.formatCardCVC(this.$cvcInput);
+ Payment.formatCardExpiry(this.$expiryInput);
+ }
+ if (this.options.width) {
+ $cardContainer = QJ(this.options.cardSelectors.cardContainer)[0];
+ baseWidth = parseInt($cardContainer.clientWidth || window.getComputedStyle($cardContainer).width);
+ $cardContainer.style.transform = "scale(" + (this.options.width / baseWidth) + ")";
+ }
+ if (typeof navigator !== "undefined" && navigator !== null ? navigator.userAgent : void 0) {
+ ua = navigator.userAgent.toLowerCase();
+ if (ua.indexOf('safari') !== -1 && ua.indexOf('chrome') === -1) {
+ QJ.addClass(this.$card, 'jp-card-safari');
+ }
+ }
+ if (/MSIE 10\./i.test(navigator.userAgent)) {
+ QJ.addClass(this.$card, 'jp-card-ie-10');
+ }
+ if (/rv:11.0/i.test(navigator.userAgent)) {
+ return QJ.addClass(this.$card, 'jp-card-ie-11');
+ }
+ };
+
+ Card.prototype.attachHandlers = function() {
+ var expiryFilters, numberInputFilters;
+ numberInputFilters = [this.validToggler('cardNumber')];
+ if (this.options.masks.cardNumber) {
+ numberInputFilters.push(this.maskCardNumber);
+ }
+ bindVal(this.$numberInput, this.$numberDisplay, {
+ fill: false,
+ filters: numberInputFilters
+ });
+ QJ.on(this.$numberInput, 'payment.cardType', this.handle('setCardType'));
+ expiryFilters = [
+ function(val) {
+ return val.replace(/(\s+)/g, '');
+ }
+ ];
+ expiryFilters.push(this.validToggler('cardExpiry'));
+ bindVal(this.$expiryInput, this.$expiryDisplay, {
+ join: function(text) {
+ if (text[0].length === 2 || text[1]) {
+ return "/";
+ } else {
+ return "";
+ }
+ },
+ filters: expiryFilters
+ });
+ bindVal(this.$cvcInput, this.$cvcDisplay, {
+ filters: this.validToggler('cardCVC')
+ });
+ QJ.on(this.$cvcInput, 'focus', this.handle('flipCard'));
+ QJ.on(this.$cvcInput, 'blur', this.handle('unflipCard'));
+ return bindVal(this.$nameInput, this.$nameDisplay, {
+ fill: false,
+ filters: this.validToggler('cardHolderName'),
+ join: ' '
+ });
+ };
+
+ Card.prototype.handleInitialPlaceholders = function() {
+ var el, name, selector, _ref, _results;
+ _ref = this.options.formSelectors;
+ _results = [];
+ for (name in _ref) {
+ selector = _ref[name];
+ el = this["$" + name];
+ if (QJ.val(el)) {
+ QJ.trigger(el, 'paste');
+ _results.push(setTimeout(function() {
+ return QJ.trigger(el, 'keyup');
+ }));
+ } else {
+ _results.push(void 0);
+ }
+ }
+ return _results;
+ };
+
+ Card.prototype.handle = function(fn) {
+ return (function(_this) {
+ return function(e) {
+ var args;
+ args = Array.prototype.slice.call(arguments);
+ args.unshift(e.target);
+ return _this.handlers[fn].apply(_this, args);
+ };
+ })(this);
+ };
+
+ Card.prototype.validToggler = function(validatorName) {
+ var isValid;
+ if (validatorName === "cardExpiry") {
+ isValid = function(val) {
+ var objVal;
+ objVal = Payment.fns.cardExpiryVal(val);
+ return Payment.fns.validateCardExpiry(objVal.month, objVal.year);
+ };
+ } else if (validatorName === "cardCVC") {
+ isValid = (function(_this) {
+ return function(val) {
+ return Payment.fns.validateCardCVC(val, _this.cardType);
+ };
+ })(this);
+ } else if (validatorName === "cardNumber") {
+ isValid = function(val) {
+ return Payment.fns.validateCardNumber(val);
+ };
+ } else if (validatorName === "cardHolderName") {
+ isValid = function(val) {
+ return val !== "";
+ };
+ }
+ return (function(_this) {
+ return function(val, $in, $out) {
+ var result;
+ result = isValid(val);
+ _this.toggleValidClass($in, result);
+ _this.toggleValidClass($out, result);
+ return val;
+ };
+ })(this);
+ };
+
+ Card.prototype.toggleValidClass = function(el, test) {
+ QJ.toggleClass(el, this.options.classes.valid, test);
+ return QJ.toggleClass(el, this.options.classes.invalid, !test);
+ };
+
+ Card.prototype.maskCardNumber = function(val, el, out) {
+ var mask, numbers;
+ mask = this.options.masks.cardNumber;
+ numbers = val.split(' ');
+ if (numbers.length >= 3) {
+ numbers.forEach(function(item, idx) {
+ if (idx !== numbers.length - 1) {
+ return numbers[idx] = numbers[idx].replace(/\d/g, mask);
+ }
+ });
+ return numbers.join(' ');
+ } else {
+ return val.replace(/\d/g, mask);
+ }
+ };
+
+ Card.prototype.handlers = {
+ setCardType: function($el, e) {
+ var cardType;
+ cardType = e.data;
+ if (!QJ.hasClass(this.$card, cardType)) {
+ QJ.removeClass(this.$card, 'jp-card-unknown');
+ QJ.removeClass(this.$card, this.cardTypes.join(' '));
+ QJ.addClass(this.$card, "jp-card-" + cardType);
+ QJ.toggleClass(this.$card, 'jp-card-identified', cardType !== 'unknown');
+ return this.cardType = cardType;
+ }
+ },
+ flipCard: function() {
+ return QJ.addClass(this.$card, 'jp-card-flipped');
+ },
+ unflipCard: function() {
+ return QJ.removeClass(this.$card, 'jp-card-flipped');
+ }
+ };
+
+ bindVal = function(el, out, opts) {
+ var joiner, o, outDefaults;
+ if (opts == null) {
+ opts = {};
+ }
+ opts.fill = opts.fill || false;
+ opts.filters = opts.filters || [];
+ if (!(opts.filters instanceof Array)) {
+ opts.filters = [opts.filters];
+ }
+ opts.join = opts.join || "";
+ if (!(typeof opts.join === "function")) {
+ joiner = opts.join;
+ opts.join = function() {
+ return joiner;
+ };
+ }
+ outDefaults = (function() {
+ var _i, _len, _results;
+ _results = [];
+ for (_i = 0, _len = out.length; _i < _len; _i++) {
+ o = out[_i];
+ _results.push(o.textContent);
+ }
+ return _results;
+ })();
+ QJ.on(el, 'focus', function() {
+ return QJ.addClass(out, 'jp-card-focused');
+ });
+ QJ.on(el, 'blur', function() {
+ return QJ.removeClass(out, 'jp-card-focused');
+ });
+ QJ.on(el, 'keyup change paste', function(e) {
+ var elem, filter, i, join, outEl, outVal, val, _i, _j, _len, _len1, _ref, _results;
+ val = (function() {
+ var _i, _len, _results;
+ _results = [];
+ for (_i = 0, _len = el.length; _i < _len; _i++) {
+ elem = el[_i];
+ _results.push(QJ.val(elem));
+ }
+ return _results;
+ })();
+ join = opts.join(val);
+ val = val.join(join);
+ if (val === join) {
+ val = "";
+ }
+ _ref = opts.filters;
+ for (_i = 0, _len = _ref.length; _i < _len; _i++) {
+ filter = _ref[_i];
+ val = filter(val, el, out);
+ }
+ _results = [];
+ for (i = _j = 0, _len1 = out.length; _j < _len1; i = ++_j) {
+ outEl = out[i];
+ if (opts.fill) {
+ outVal = val + outDefaults[i].substring(val.length);
+ } else {
+ outVal = val || outDefaults[i];
+ }
+ _results.push(outEl.textContent = outVal);
+ }
+ return _results;
+ });
+ return el;
+ };
+
+ return Card;
+
+ })();
+
+ module.exports = Card;
+
+ global.Card = Card;
+
+ /* WEBPACK VAR INJECTION */}.call(exports, (function() { return this; }())))
+
+/***/ },
+/* 1 */
+/***/ function(module, exports, __webpack_require__) {
+
+ // style-loader: Adds some css to the DOM by adding a