masked credit card field

In this example below you will see how to do a masked credit card field with some HTML / CSS and Javascript

Thumbnail
This awesome code was written by edenf, you can see more from this user in the personal repository.
You can find the original code on Codepen.io
Copyright edenf ©

Technologies

  • HTML
  • CSS
  • JavaScript
<!DOCTYPE html>
<html lang="en" >

<head>
  <meta charset="UTF-8">
  <title>masked credit card field</title>
  
  
  <link rel='stylesheet prefetch' href='https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/css/bootstrap.min.css'>

      <link rel="stylesheet" href="css/style.css">

  
</head>

<body>

  <div class="container">
  <div class="input-group">
    <input id="cardInput" type="text" class="form-control" placeholder="Enter card number">
    <span class="input-group-addon" id="inputAddon"></span>
  </div>
</div>
  <script src='https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js'></script>
<script src='https://cdnjs.cloudflare.com/ajax/libs/jquery.maskedinput/1.4.1/jquery.maskedinput.min.js'></script>
<script src='https://raw.githubusercontent.com/text-mask/text-mask/master/vanilla/dist/vanillaTextMask.js'></script>

  

    <script  src="js/index.js"></script>




</body>

</html>

/*Downloaded from https://www.codeseek.co/edenf/masked-credit-card-field-odOrLO */
body {
  margin: 2rem;
}


/*Downloaded from https://www.codeseek.co/edenf/masked-credit-card-field-odOrLO */
// https://cdnjs.cloudflare.com/ajax/libs/jquery.maskedinput/1.4.1/jquery.maskedinput.min.js
!function (t, e) { var n; if (!t.getSelection && e.selection) { n = null, t.getSelection = function () { return null != n ? n : n = new o }, e.createRange = function () { return new Range }, e.attachEvent("onkeydown", function () { return t.getSelection().setRangeAt(0, new Range(!0)) }), e.attachEvent("onselectionchange", function () { var n, o; return t.getSelection().setRangeAt(0, new Range(!0)), n = e.selection.createRange().parentElement(), "INPUT" === n.tagName || "TEXTAREA" === n.tagName ? (o = t.getSelection().getRangeAt(0), n.selectionStart = o.selectionStart, n.selectionEnd = o.selectionEnd) : void 0 }), t.Range = function () { function t(t) { t ? this.range = e.selection.createRange() : (this.range = e.body.createTextRange(), this.collapse(!0)), this.init() } return t.END_TO_END = "EndToEnd", t.END_TO_START = "EndToStart", t.START_TO_END = "StartToEnd", t.START_TO_START = "StartToStart", t.prototype.init = function () { var t, e, n, o, i, a; return e = this.range.parentElement(), this.commonAncestorContainer = e, this.collapsed = 0 === this.compareBoundaryPoints("StartToEnd", this), a = this.range.duplicate(), a.moveToElementText(e), t = this.range.text.length > 0 ? 0 : 1, i = r.findLength("StartToStart", a, this.range), n = r.findNodeByPos(e, i, t), this.startContainer = n.el, this.startOffset = n.offset, o = r.findLength("StartToEnd", a, this.range), n = r.findNodeByPos(e, o, 1), this.endContainer = n.el, this.endOffset = n.offset, this.selectionStart = r.findLength("StartToStart", a, this.range, !0), this.selectionEnd = r.findLength("StartToEnd", a, this.range, !0) }, t.prototype.select = function () { return this.range.select() }, t.prototype.setStart = function (t, e) { var n, o; return r.getText(t).length >= e && e >= 0 ? (o = this.range.duplicate(), 3 === t.nodeType && (n = r.findPosFromNode(t), o.moveToElementText(t.parentNode), o.moveStart("character", n + e)), -1 === this.range.compareEndPoints("StartToEnd", o) && this.range.setEndPoint("EndToStart", o), this.range.setEndPoint("StartToStart", o)) : void 0 }, t.prototype.setEnd = function (t, e) { var n, o; return r.getText(t).length >= e && e >= 0 ? (o = this.range.duplicate(), 3 === t.nodeType && (n = r.findPosFromNode(t), o.moveToElementText(t.parentNode), o.moveStart("character", n + e)), this.range.setEndPoint("EndToStart", o)) : void 0 }, t.prototype.selectNodeContents = function (t) { return this.range.moveToElementText(t) }, t.prototype.collapse = function (t) { return t ? this.range.setEndPoint("EndToStart", this.range) : this.range.setEndPoint("StartToEnd", this.range) }, t.prototype.compareBoundaryPoints = function (t, e) { return this.range.compareEndPoints(t, e.range) }, t.prototype.cloneRange = function () { var e; return e = new t, e.range = this.range.duplicate(), e.init(), e }, t.prototype.detach = function () { return delete this.range }, t.prototype.getBoundingClientRect = function () { var t, e; return e = this.range.getBoundingClientRect(), t = { width: e.right - e.left, height: e.bottom - e.top, left: e.left, right: e.right, bottom: e.bottom, top: e.top } }, t.prototype.toString = function () { return this.range.text || "" }, t }(); var o; o = function () { function t() { this.selection = e.selection, this.ranges = [], this.init() } return t.prototype.init = function () { var t, e, n, o, r; return this.rangeCount = this.ranges.length, this.rangeCount ? (e = this.ranges[0], null == this.prev && (this.prev = e), 0 === e.compareBoundaryPoints(Range.END_TO_END, this.prev) ? (o = ["end", "start"], t = o[0], n = o[1]) : (r = ["start", "end"], t = r[0], n = r[1]), this.anchorNode = e["" + t + "Container"], this.anchorOffset = e["" + t + "Offset"], this.focusNode = e["" + n + "Container"], this.focusOffset = e["" + n + "Offset"], this.isCollapsed = this.anchorNode === this.focusNode) : void 0 }, t.prototype.getRangeAt = function (t) { return this.ranges[t] }, t.prototype.setRangeAt = function (t, e) { return this.prev = this.ranges[t], this.ranges[t] = e, this.init() }, t.prototype.removeAllRanges = function () { return this.ranges = [], this.init() }, t.prototype.addRange = function (t) { var e, n, o, r, i; for (this.ranges.push(t), this.init(), r = this.ranges, i = [], n = 0, o = r.length; o > n; n++) e = r[n], i.push(e.select()); return i }, t.prototype.deleteFromDocument = function () { return this.selection.clear() }, t.prototype.toString = function () { return this.ranges[0].toString() }, t }(); var r; r = { convertLineBreaks: function (t) { return t.replace(/\r\n/g, "\n") }, stripLineBreaks: function (t) { return t.replace(/\r\n/g, "") }, getText: function (t) { return t.innerText || t.nodeValue }, findLength: function (t, e, n, o) { var i; switch (i = e.duplicate(), t) { case "StartToStart": i.setEndPoint("EndToStart", n); break; case "StartToEnd": i.setEndPoint("EndToEnd", n) } return o ? r.convertLineBreaks(i.text).length : r.stripLineBreaks(i.text).length }, findNodeByPos: function (t, e, n) { var o, r; return null == n && (n = 0), r = { length: 0, el: t, offset: 0 }, (o = function (t, e, n, r) { var i, a, s, h, c; for (h = t.childNodes, c = [], a = 0, s = h.length; s > a; a++) if (i = h[a], !r.found) if (3 === i.nodeType) { if (r.length + i.length + n > e) { r.found = !0, r.el = i, r.offset = e - r.length; break } c.push(r.length += i.length) } else c.push(o(i, e, n, r)); return c })(t, e, n, r), r }, findPosFromNode: function (t) { var e, n, o; return n = { pos: 0 }, o = t.parentNode, (e = function (t, n, o) { var r, i, a, s, h; for (s = t.childNodes, h = [], i = 0, a = s.length; a > i; i++) if (r = s[i], !o.found) { if (r === n) { o.found = !0; break } 3 === r.nodeType ? h.push(o.pos += r.length) : r.hasChildNodes() ? h.push(e(r, n, o)) : h.push(void 0) } return h })(o, t, n), n.pos } } } }(window, document);
/*----------------*/
jQuery.fn.setSelection = function(selectionStart, selectionEnd) {
	if(this.lengh == 0) return this;
	input = this[0];

	if (input.createTextRange) {
		var range = input.createTextRange();
		range.collapse(true);
		range.moveEnd('character', selectionEnd);
		range.moveStart('character', selectionStart);
		range.select();
	} else if (input.setSelectionRange) {
		input.focus();
		input.setSelectionRange(selectionStart, selectionEnd);
	}

	return this;
}
jQuery.fn.setCursorPosition = function(position){
	if(this.lengh == 0) return this;
	return $(this).setSelection(position, position);
}
/*---------------*/

;(function(globCtx){

	var testOrder;
	var types = {};
	var customCards = {};
	var VISA = 'visa';
	var MASTERCARD = 'mastercard';
	var AMERICAN_EXPRESS = 'american-express';
	var DINERS_CLUB = 'diners-club';
	var DISCOVER = 'discover';
	var JCB = 'jcb';
	var UNIONPAY = 'unionpay';
	var MAESTRO = 'maestro';
	var MIR = 'mir';
	var CVV = 'CVV';
	var CID = 'CID';
	var CVC = 'CVC';
	var CVN = 'CVN';
	var CVP2 = 'CVP2';
	var ORIGINAL_TEST_ORDER = [
		VISA,
		MASTERCARD,
		AMERICAN_EXPRESS,
		DINERS_CLUB,
		DISCOVER,
		JCB,
		UNIONPAY,
		MAESTRO,
		MIR
	];

	function clone(originalObject) {
		var dupe;

		if (!originalObject) { return null; }

		dupe = JSON.parse(JSON.stringify(originalObject));
		delete dupe.prefixPattern;
		delete dupe.exactPattern;

		return dupe;
	}

	testOrder = clone(ORIGINAL_TEST_ORDER);

	types[VISA] = {
		niceType: 'Visa',
		type: VISA,
		prefixPattern: /^4$/,
		exactPattern: /^4\d*$/,
		gaps: [4, 8, 12],
		mask: '9999 9999 9999 9999? 999',
		lengths: [16, 18, 19],
		code: {
			name: CVV,
			size: 3
		}
	};
	types[MASTERCARD] = {
		niceType: 'Mastercard',
		type: MASTERCARD,
		prefixPattern: /^(5|5[1-5]|2|22|222|222[1-9]|2[3-6]|27|27[0-2]|2720)$/,
		exactPattern: /^(5[1-5]|222[1-9]|22[3-9]|2[3-6]|27[0-1]|2720)\d*$/,
		gaps: [4, 8, 12],
		mask: '9999 9999 9999 9999',
		lengths: [16],
		code: {
			name: CVC,
			size: 3
		}
	};
	types[AMERICAN_EXPRESS] = {
		niceType: 'American Express',
		type: AMERICAN_EXPRESS,
		prefixPattern: /^(3|34|37)$/,
		exactPattern: /^3[47]\d*$/,
		isAmex: true,
		gaps: [4, 10],
		lengths: [15],
		mask: '9999 999999 99999',
		code: {
			name: CID,
			size: 4
		}
	};
	types[DINERS_CLUB] = {
		niceType: 'Diners Club',
		type: DINERS_CLUB,
		prefixPattern: /^(3|3[0689]|30[0-5])$/,
		exactPattern: /^3(0[0-5]|[689])\d*$/,
		gaps: [4, 10],
		mask: '9999 999999 9999 ?',
		lengths: [14, 16, 19],
		code: {
			name: CVV,
			size: 3
		}
	};
	types[DISCOVER] = {
		niceType: 'Discover',
		type: DISCOVER,
		prefixPattern: /^(6|60|601|6011|65|64|64[4-9])$/,
		exactPattern: /^(6011|65|64[4-9])\d*$/,
		gaps: [4, 8, 12],
		mask: '9999 9999 9999 9999? ',
		lengths: [16, 19],
		code: {
			name: CID,
			size: 3
		}
	};
	types[JCB] = {
		niceType: 'JCB',
		type: JCB,
		prefixPattern: /^(2|21|213|2131|1|18|180|1800|3|35)$/,
		exactPattern: /^(2131|1800|35)\d*$/,
		gaps: [4, 8, 12],
		lengths: [16, 17, 18, 19],
		mask: '9999 9999 9999 9999? ',
		code: {
			name: CVV,
			size: 3
		}
	};
	types[UNIONPAY] = {
		niceType: 'UnionPay',
		type: UNIONPAY,
		prefixPattern: /^((6|62|62\d|(621(?!83|88|98|99))|622(?!06)|627[02,06,07]|628(?!0|1)|629[1,2])|622018)$/,
		exactPattern: /^(((620|(621(?!83|88|98|99))|622(?!06|018)|62[3-6]|627[02,06,07]|628(?!0|1)|629[1,2]))\d*|622018\d{12})$/,
		gaps: [4, 8, 12],
		mask: '9999 9999 9999 9999? ',
		lengths: [16, 17, 18, 19],
		code: {
			name: CVN,
			size: 3
		}
	};
	types[MAESTRO] = {
		niceType: 'Maestro',
		type: MAESTRO,
		prefixPattern: /^(5|5[06-9]|6\d*)$/,
		exactPattern: /^(5[06-9]|6[37])\d*$/,
		gaps: [4, 8, 12],
		mask: '9999 9999 9999 *',
		lengths: [12, 13, 14, 15, 16, 17, 18, 19],
		code: {
			name: CVC,
			size: 3
		}
	};
	types[MIR] = {
		niceType: 'Mir',
		type: MIR,
		prefixPattern: /^(2|22|220|220[0-4])$/,
		exactPattern: /^(220[0-4])\d*$/,
		gaps: [4, 8, 12],
		mask: '9999 9999 9999?',
		lengths: [16, 17, 18, 19],
		code: {
			name: CVP2,
			size: 3
		}
	};

	function findType(type) {
		return customCards[type] || types[type];
	}

	function creditCardType(cardNumber) {
		var type, value, i;
		var prefixResults = [];
		var exactResults = [];

		if (!(typeof cardNumber === 'string' || cardNumber instanceof String)) {
			return [];
		}

		for (i = 0; i < testOrder.length; i++) {
			type = testOrder[i];
			value = findType(type);

			if (cardNumber.length === 0) {
				prefixResults.push(clone(value));
				continue;
			}

			if (value.exactPattern.test(cardNumber)) {
				exactResults.push(clone(value));
			} else if (value.prefixPattern.test(cardNumber)) {
				prefixResults.push(clone(value));
			}
		}

		return exactResults.length ? exactResults : prefixResults;
	}

	creditCardType.getTypeInfo = function (type) {
		return clone(findType(type));
	};

	function getCardPosition(name, ignoreErrorForNotExisting) {
		var position = testOrder.indexOf(name);

		if (!ignoreErrorForNotExisting && position === -1) {
			throw new Error('"' + name + '" is not a supported card type.');
		}

		return position;
	}

	creditCardType.removeCard = function (name) {
		var position = getCardPosition(name);

		testOrder.splice(position, 1);
	};

	creditCardType.addCard = function (config) {
		var existingCardPosition = getCardPosition(config.type, true);

		customCards[config.type] = config;

		if (existingCardPosition === -1) {
			testOrder.push(config.type);
		}
	};

	creditCardType.changeOrder = function (name, position) {
		var currentPosition = getCardPosition(name);

		testOrder.splice(currentPosition, 1);
		testOrder.splice(position, 0, name);
	};

	creditCardType.resetModifications = function () {
		testOrder = clone(ORIGINAL_TEST_ORDER);
		customCards = {};
	};

	creditCardType.types = {
		VISA: VISA,
		MASTERCARD: MASTERCARD,
		AMERICAN_EXPRESS: AMERICAN_EXPRESS,
		DINERS_CLUB: DINERS_CLUB,
		DISCOVER: DISCOVER,
		JCB: JCB,
		UNIONPAY: UNIONPAY,
		MAESTRO: MAESTRO,
		MIR: MIR
	};

	globCtx.creditCardType = creditCardType;

})(this);


var CreditCardFormatter = function(cardInput){
	var inputEl = cardInput;
	
	var getCreditCardCleanValue = function(cardNum){
		var cleanCardNum = cardNum;
		if (cardNum.length) {
			var digits = cardNum.match(/\d+/g);
			if (digits && digits.length){
				cleanCardNum = digits.join('');
			}
		}
		return cleanCardNum;
	};

	var getCreditCardTypes = function(cleanCardNum){
		return creditCardType(cleanCardNum);
	};

	var setCardMask = function(cardInput){

		var cardNum = cardInput.val(),
			 cleanCardNum = getCreditCardCleanValue(cardNum),
			 cardTypes = getCreditCardTypes(cleanCardNum),
			 exactType = cardTypes.length == 1 ? cardTypes[0] : null;

		// ignore ambigous cases
		if (!exactType){
			return;
		}
		// don't reinit masking if card type has not been changed
		var isOldType = cardInput.data('cardType') && (cardInput.data('cardType') === exactType.type);
		if (isOldType){
			return;
		}
		
		//test only
		//inputAddon.html(cardTypes.map(t=> t.niceType).join(', '));
		
		// change masking
		cardInput.mask(exactType.mask, { autoclear: false, placeholder: '_' });
		// save current card type
		cardInput.data('cardType', exactType.type);

		//restore caret position
		cardInput.setCursorPosition(cleanCardNum.length, cleanCardNum.length);
	};
	
	return {
		setCardMask: setCardMask,
		getType: getCreditCardTypes
	};
};


$(function(){

	var cardInput = $('#cardInput'),
		 inputAddon = $('#inputAddon');

	var cardFieldFormatter = new CreditCardFormatter(cardInput);
	
	cardInput.on('keyup', function(e){
		e.preventDefault();
		cardFieldFormatter.setCardMask(cardInput);
	});
});

Comments