Home » Angular Development » Angular Custom Credit Card Validation: Tutorial
Angular Custom Credit Card Validation: Tutorial
In this tutorial, we will learn how to do custom debit or credit card validation and masking in Angular 10.
Prerequisites:
- Prior knowledge of TypeScript
- Prior knowledge of JavaScript
- Visual Studio Code
- Development machine with Node 8.9+ & NPM 5.5.1+ installed
Step-by-Step tutorial on Custom Credit Card Validation and Masking
Step 1: Installing Angular CLI 10
First step, where we’ll have to install latest version of Angular CLI
$ npm install -g @angular/cli
Step 2: Creating your Angular 10 Project
In this second step, we will use Angular CLI to start our Angular Project
Go to CMD or Terminal and use this command:
$ ng new CreditDebitCardValidation
This CLI will ask you “whether you would like to add Angular routing” Say No.
It will ask “which stylesheet format you would like to use”. Choose CSS.
Now your project is ready Angular CLI will generate required files and folders along with NPM packages and routing too.
Now open your project in Visual studio code and go to your root folder and run the local development server using below command:
$ npm start
Now run localhost:4200/ in your browser
Step 3: Add HTML form and bootstrap CSS CDN link
Here, we’re not creating any other component, we’re using app component for our needs.
So, Let’s add bootstrap CDN link to index.html page
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>CreditDebitCardValidation</title>
<base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="favicon.ico">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css">
</head>
<body>
<app-root></app-root>
</body>
</html>
Now, Let’s add form fields to enter our credit/debit card details to the app.component.html file And import form module to the app.modute.ts
Like below:
<div class="container" style="width: 50%;">
<h2>Credit/Debit card validation</h2>
<div class="panel panel-default">
<div class="panel-heading">Enter Card Details</div>
<div class="panel-body">
<div class="right-payment-sec">
<form [formGroup]="paymentForm" (ngSubmit)="SaveCardDetails()">
<div class="row">
<div class="col-lg-12 col-xs-12">
<div class="form-group">
<label for="exampleInputEmail1">Name On Card</label>
<input type="text" class="form-control" formControlName="name" [(ngModel)]="paymentmodel.fullName" id="exampleInputEmail1" placeholder="Name On Card">
</div>
</div>
<div class="col-lg-12 col-xs-12">
<div class="form-group">
<label for="exampleInputEmail1">Card Number</label>
<input type="text" class="form-control" [formControl]="getCardNumberControl()"
[(ngModel)]="paymentmodel.cardNumber" id="exampleInputEmail1" placeholder="XXXX-XXXX-XXXX-XXXX">
</div>
</div>
<div class="col-lg-6 col-md-6 xol-sm-6 col-xs-12">
<div class="form-group">
<label for="exampleInputEmail1">Expiration Month</label>
<select class="form-control" id="" formControlName="expiryMonth" [(ngModel)]="paymentmodel.cardMonth" placeholder="Select Month">
<option [value]="null">Select</option>
<option *ngFor="let month of monthlist"
[value]="month.value">{{month.text}}</option>
</select>
</div>
</div>
<div class="col-lg-6 col-md-6 xol-sm-6 col-xs-12">
<div class="form-group">
<label for="exampleInputEmail1">Expiration Year</label>
<select class="form-control" id="" formControlName="expiryYear" [(ngModel)]="paymentmodel.cardYear" placeholder="Expiration Year">
<option [value]="null">Select</option>
<option *ngFor="let year of years" [value]="year.value">
{{year.text}}</option>
</select>
</div>
</div>
<div class="col-lg-6 col-md-6 xol-sm-6 col-xs-12">
<div class="form-group">
<label for="exampleInputEmail1">Security Code</label>
<input type="text" class="form-control" formControlName="ccv" [(ngModel)]="paymentmodel.cvv" id="exampleInputEmail1" placeholder="Security Code">
</div>
</div>
<div class="col-lg-6 col-md-6 xol-sm-6 col-xs-12">
<div class="form-group">
<label for="exampleInputEmail1">Zip Code</label>
<input type="text" class="form-control" formControlName="zipCode" [(ngModel)]="paymentmodel.zipCode" id="exampleInputEmail1" placeholder="Zip Code">
</div>
</div>
<div class="col-lg-6 col-md-6 xol-sm-6 col-xs-12">
<div class="form-group">
<button type="submit" class="btn btn-primary" style="margin-right: 10px ;">Save</button>
<button type="button" class="btn btn-primary" (click)="ClearCardDetails()">Clear</button>
</div>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
Import module to the app.module.ts file:
imports: [
BrowserModule,
ReactiveFormsModule,
FormsModule,
TextMaskModule
],
Now, save our app and browser on http://localhost:4200/
Output look like below simple form:
Step 4: Add Interface, validation, text masking for credit card number and get dropdown data for state and month
Step 4.1: Declaration
- First, Let’s declare some interface models.
- We need PaymentModel for store form data, Year model for showing year list on the dropdown and month model for same as year.
- Also, Declare formGroup and FormBuilder.
export class AppComponent {
title = 'CreditDebitCardValidation';
paymentForm : FormGroup
monthlist: IMonth[] = [];
month:IMonth = <IMonth>{};
year:IYear = <IYear>{};
years: IYear[] = [];
paymentmodel:IPaymentModel = <IPaymentModel>{}
isSubmitted:boolean = false;
cardValidate:boolean = false;
cardDetailsValidate:boolean = false;
cardType:string = "";
public constructor(
private formBuilder: FormBuilder
) { }
ngOnInit() {
this.IntializePaymentForm();
this.GetMonths();
this.GetYears();
}
Add app.interface.ts file to add models:
export interface IPaymentModel{
fullName:string;
cardNumber:string;
cardMonth:string;
cardYear:string;
cvv:string;
zipCode:string;
}
export interface IMonth {
text: string,
value: string,
}
export interface IYear {
text: string,
value: string,
}
Step 4.2: Initialize the form controls
- Then, Let’s initialize the form controls and add validation
- Add function like below to initialize the form controls in the app.component.ts file.
//#region form intilize
IntializePaymentForm() {
this.paymentForm = this.formBuilder.group({
cardnumber: [
"",
[Validators.required, Validators.minLength(12), luhnValidator()],
],
expiryMonth: ["", Validators.required],
expiryYear: ["", Validators.required],
zipCode: ["", Validators.required],
ccv: ["", Validators.required],
name: ["", Validators.required]
});
this.paymentmodel.cardYear = "null";
this.paymentmodel.cardMonth = "null";
}
get f() {
return this.paymentForm.controls;
}
//#endregion
Here, to validate card number we’re using custom luhnValidators() function which are based on Luhn algorithm, The Luhn algorithm is a simple, public domain checksum algorithm that can be used to validate a variety of identification numbers.
For that, we’ve to create two a simple helper functions and will import these files to the app.components.ts files.
Let’s create a first one luhn.validators.ts file in the Helpers folder.
import { ValidatorFn, AbstractControl } from '@angular/forms';
import { luhnCheck } from '../helpers/luhn.helper';
export function luhnValidator(): ValidatorFn {
return (control: AbstractControl) => {
const isValid = luhnCheck(control.value);
return isValid ? null: {'luhnCheck': isValid};
};
}
And second one is the luhn.helper.ts, like below:
export const luhnCheck = (cardNumber: string): boolean => {
if (!cardNumber.length) {
return;
}
// Remove all whitespaces from card number.
cardNumber = cardNumber.replace(/\s/g, '');
// 1. Remove last digit;
const lastDigit = Number(cardNumber[cardNumber.length - 1]);
// 2. Reverse card number
const reverseCardNumber = cardNumber.slice(0, cardNumber.length - 1).split('').reverse().map(x => Number(x));
let sum = 0;
// 3. + 4. Multiply by 2 every digit on odd position. Subtract 9 if digit > 9
for (let i = 0; i <= reverseCardNumber.length - 1; i += 2) {
reverseCardNumber[i] = reverseCardNumber[i] * 2;
if (reverseCardNumber[i] > 9) {
reverseCardNumber[i] = reverseCardNumber[i] - 9;
}
}
// 5. Make the sum of obtained values from step 4.
sum = reverseCardNumber.reduce((acc, currValue) => (acc + currValue), 0);
// 6. Calculate modulo 10 of the sum from step 5. and the last digit. If it's 0, you have a valid card number :)
return ((sum + lastDigit) % 10 === 0);
}
Step 4.3: Get Dropdown data for year and month form field
Now, Add simple code for get years and month for the select form field.
//#region Set MonthLsit
GetMonths() {
for (let i = 1; i <= 12; i++) {
this.month = <IMonth>{};
if(i.toString().length == 1)
{
this.month.text = "0"+i.toString();
this.month.value = "0"+i.toString();
}
else
{
this.month.text = i.toString();
this.month.value = i.toString();
}
this.monthlist.push(this.month);
}
}
//#endregion
//#region Set Year List
GetYears() {
let year = new Date().getFullYear();
for (let i = year; i <= year + 20; i++) {
this.year = <IYear>{};
this.year.text = i.toString();
this.year.value = i.toString();
this.years.push(this.year);
}
}
//#endregion
And then, Initialize above method on the NgOnInit();
ngOnInit() {
this.IntializePaymentForm();
this.GetMonths();
this.GetYears();
}
Step 4.4: Text Masking on the credit card number
Now, Let’s add TextMasking on the credit card number. For that, we need to import “TextMaskModule”
- Using npm i angular2-text-mask command install this module and then import in the app.module.ts file.
- Use of this module like below on the credit card number input filed:
<input type="text" class="form-control"
[formControl]="getCardNumberControl()"
[textMask]="{mask: cardMaskFunction, guide: false, showMask: true}"
[(ngModel)]="paymentmodel.cardNumber" id="exampleInputEmail1"
placeholder="XXXX-XXXX-XXXX-XXXX">
Here, on above input field there are two methods are used.
- cardMaskFunction() used to mask credit card number and getCardNumberControl() is used to get card number form control.
- Let’s add on app.component.ts file
cardMaskFunction(rawValue: string): Array<RegExp> {
const card = getValidationConfigFromCardNo(rawValue);
if (card) {
return card.mask;
}
return [/\d/];
}
getCardNumberControl(): AbstractControl | null {
return this.paymentForm && this.paymentForm.get("cardnumber");
}
Step 4.5: Add custom helper file for text masking based on length of credit card number to get card type
getValidationConfigFromCardNo() is the custom helper function to get card type based on length of entered credit card number and based on card type it will be masked credit card number and this function also helps us to format credit card number.
For that we’ve to create two helper files in the Helpers folder and then import it to the app.component.ts file.
First, we add cardmodels.ts file in the Helpers folder to find out card brand.
export enum CardBrandEnum {
VISA = 'VISA',
MASTERCARD = 'MASTERCARD',
AMERICANEXPRESS = 'AMERICANEXPRESS',
DISCOVER = 'DISCOVER',
DINERSCLUB = 'DINERSCLUB',
JCB = 'JCB',
MAESTRO = 'MAESTRO',
UNIONPAY = 'UNIONPAY',
DANKORT = 'DANKORT',
FORBRUGSFORENINGEN = 'FORBRUGSFORENINGEN'
};
The Second, add file to the help masking card number based on entered card number.
import { CardBrandEnum } from './cardmodels';
const digitMask = (numDigits: number) => Array(numDigits).fill(/\d/);
export const getValidationConfigFromCardNo = (
rawValue: string
): CardValidation =>
cards.find(card => {
const patterns = card.patterns.map(
pattern => new RegExp(`^${pattern}`, 'g')
);
const matchResult = patterns
.map(pattern => rawValue.match(pattern))
.filter(result => result);
return !!matchResult.length;
}) || null;
const defaultFormat = /(\d{1,4})/g;
export interface CardValidation {
type: CardBrandEnum;
patterns: number[];
mask: any;
format: RegExp;
length: number[];
cvvLength: number[];
luhn: boolean;
}
So, After adding all above files and method our HTML page will have code like below:
<div class="container" style="width: 50%;">
<h2>Credit/Debit card validation</h2>
<div class="panel panel-default">
<div class="panel-heading">Enter Card Details</div>
<div class="panel-body">
<div class="right-payment-sec">
<form [formGroup]="paymentForm" (ngSubmit)="SaveCardDetails()">
<div class="row">
<div class="col-lg-12 col-xs-12">
<div class="form-group">
<label for="exampleInputEmail1">Name On Card</label>
<input type="text" class="form-control" formControlName="name" [(ngModel)]="paymentmodel.fullName" id="exampleInputEmail1" placeholder="Name On Card">
<div *ngIf="(f.name.errors || f.name.dirty || f.name.touched)" class="invalid-feedback">
<span *ngIf="f.name.errors?.required">Insert name on
card</span>
</div>
</div>
</div>
<div class="col-lg-12 col-xs-12">
<div class="form-group">
<label for="exampleInputEmail1">Card Number</label>
<input type="text" class="form-control"
[formControl]="getCardNumberControl()"
[textMask]="{mask: cardMaskFunction, guide: false, showMask: true}"
[(ngModel)]="paymentmodel.cardNumber" id="exampleInputEmail1"
placeholder="XXXX-XXXX-XXXX-XXXX">
And output will be look like below:
Step 5: Additionally, Add a couple of method to validate card by entered expiration year and expiration month, and Method for get card brand name
Open app.component.ts page and add below two methods:
This is for validate card by entered expiration years and expiration months:
validateCCcard(month, year) {
debugger;
let ptDatePattern = "^((0[1-9])|(1[0-2]))/([0-9]{4})$";
let datevalue = month + "/" + year;
if (datevalue.match(ptDatePattern)) return true;
else {
let todayDate = new Date();
// const year = Number(value.substr(2, 4));
// const month = Number(value.substr(0, 2));
let controlDate = new Date(year, month - 1, 1, 23, 59, 59);
let dateCompare = new Date(
todayDate.getFullYear(),
todayDate.getMonth(),
1,
0,
0,
0
);
// let controlDate = new Date(control.value);
if (dateCompare > controlDate) {
return true;
}
}
return false;
}
This is for Get card type (Card brand name):
getCardType(cur_val) {
// the regular expressions check for possible matches as you type, hence the OR operators based on the number of chars
// regexp string length {0} provided for soonest detection of beginning of the card numbers this way it could be used for BIN CODE detection also
if(cur_val != null && cur_val != undefined && cur_val != ""){
//JCB
var jcb_regex = new RegExp("^(?:2131|1800|35)[0-9]{0,}$"); //2131, 1800, 35 (3528-3589)
// American Express
var amex_regex = new RegExp("^3[47][0-9]{0,}$"); //34, 37
// Diners Club
var diners_regex = new RegExp("^3(?:0[0-59]{1}|[689])[0-9]{0,}$"); //300-305, 309, 36, 38-39
// Visa
var visa_regex = new RegExp("^4[0-9]{0,}$"); //4
// MasterCard
var mastercard_regex = new RegExp(
"^(5[1-5]|222[1-9]|22[3-9]|2[3-6]|27[01]|2720)[0-9]{0,}$"
); //2221-2720, 51-55
var maestro_regex = new RegExp("^(5[06789]|6)[0-9]{0,}$"); //always growing in the range: 60-69, started with / not something else, but starting 5 must be encoded as mastercard anyway
//Discover
var discover_regex = new RegExp(
"^(6011|65|64[4-9]|62212[6-9]|6221[3-9]|622[2-8]|6229[01]|62292[0-5])[0-9]{0,}$"
);
////6011, 622126-622925, 644-649, 65
// get rid of anything but numbers
cur_val = cur_val.replace(/\D/g, "");
// checks per each, as their could be multiple hits
//fix: ordering matter in detection, otherwise can give false results in rare cases
var sel_brand = "unknown";
if (cur_val.match(jcb_regex)) {
sel_brand = "JCB";
} else if (cur_val.match(amex_regex)) {
sel_brand = "Amex";
} else if (cur_val.match(diners_regex)) {
sel_brand = "Diners_club";
} else if (cur_val.match(visa_regex)) {
sel_brand = "Visa";
} else if (cur_val.match(mastercard_regex)) {
sel_brand = "Mastercard";
} else if (cur_val.match(discover_regex)) {
sel_brand = "Discover";
} else if (cur_val.match(maestro_regex)) {
if (cur_val[0] == "5") {
//started 5 must be mastercard
sel_brand = "MasterCard";
} else {
sel_brand = "Maestro"; //maestro is all 60-69 which is not something else, thats why this condition in the end
}
}
return sel_brand;
}
}
//#endregion
Lastly, create a submit method to get entered data and will show on HTML page at below of our form.
SaveCardDetails(){
this.isSubmitted= true;
if(this.paymentForm.valid){
this.cardType = this.getCardType(this.paymentmodel.cardNumber)
this.cardValidate = this.validateCCcard(this.paymentmodel.cardMonth, this.paymentmodel.cardYear)
if(this.cardValidate)
this.cardDetailsValidate = true;
}
else
this.cardDetailsValidate = false;
console.log(this.paymentmodel, this.cardType,this.cardValidate)
}
ClearCardDetails(){
this.paymentmodel = <IPaymentModel>{};
this.paymentForm.reset();
}
Output
That’s it. Over To You!
Looking for a Sample Source Code? Here you go: GitHub.
That’s it for now. Today you have learned how to masking and validate credit card in Angular. Happy Coding…
At last, If you’re dealing with large-scale applications or enterprise software, it is beneficial to take experts’ help. If you’re looking for an expert helping hand, contact Samarpan Infotech and hire Angular developer with having minimum of 5+ years of experience working on enterprise software.
Explore more angular tutorials
I am working as Jr. Full Stack Developer (.NET/Angular) at Samarpan Infotech. I've good analytical thinking and collaboration skills, and I love working with a team.