(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: '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));
