Angular Custom Credit Card Validation: Tutorial

credit card validation and masking in angular
Ajay Thakor
15-Apr-2022
Reading Time: 8 minutes

In this tutorial, we will learn how to do custom debit or credit card validation and masking in Angular 10.

Prerequisites:

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
Creating your Angular 10 Project credit card validation Angular Custom Credit Card Validation: Tutorial

Now run localhost:4200/ in your browser

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:

credit card validation output 1 Angular Custom Credit Card Validation: Tutorial

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:

credit card validation output 2 Angular Custom Credit Card Validation: Tutorial

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