Custom AngularJS filter to determine credit card type

There's one thing that I never understood while doing checkout on various eCommerce websites. Why on Earth am I being asked for my payment card type? (I also wonder why I am asked for the actual number, but oh well). From a credit card number, it can easily be determined what card the actual user has who is ready to spend some money. I personally hate forms, so the less information I need to type in, the more appealing a site is. And fields such as the type of credit card really make no sense to me.

I can see a few reasons why a site would utilise a selector but none of these reasons are really valid:

  1. They want to remind the user about the accepted cards. -- Great but put icons instead. Or just spell it out, 'we accept AMEX and Visa'
  2. Sanity check, to make sure that the number matches the type. -- Parse the number.
  3. Statistical purposes. -- Again, parse the number and create statistics behind the scenes

Because I work with AngularJS these days, and because I read a great article on creating a shopping cart app using only AngularJS I thought I'd put together a custom filter to determine the type of the card using the credit card number only.

AngularJS is equipped with filters that allow a subset of items from an array to be returned. This is absolutely magical - long lists can be filtered easily, there's no need to implement a backend search functionality. Filters can also transform the models, imagine if there is a date in an array returned from a database for example. Modifying the date to adhere to specific date formatting rules is as easy as specifying the format as a filter:

{{ my.date | date: 'yy-M-dd' }} //will return 13-10-08  

As mentioned before there are various built in filters, for limiting the dataset and for transforming it. Seems like adding a custom filter to validate credit cards is a great idea.

Let's talk a bit about credit/bank cards. The first digit of a credit card is called an MII - a Major Industry Identifier. This number represents the category of the organisation that has issued the card. The first six digits (including the MII) which are referred to as the Issuer Identification Number (IIN) identify the institution that has issued the card - and this is the information that is required to determine the credit card type. There are lots of online resources to get the lookup information from. Some of them actually go into discussing how to identify the bank that has issued the credit card.

Credit card validity is also ensured by the Luhn algorithm - simply put, this checksum mechanism allows the verification of credit card numbers using some maths. I have incorporated this already existing piece of code into my filter.

Okay, this should be enough background information. What we are going to build is a simple AngularJS filter that will tell the type of credit card the user has input as well as the validity of the numbers.

The HTML will be kept to the absolute basic:

<input type="text" ng-model="credit-card-number" placeholder="Credit Card Number" class="input-lg"/>  
  <span>{{ credit-card-number | validate }}</span>

The above is a basic AngularJS data-binding. If you start to input a number (make sure you remove the | validate bit as that will cause some exceptions), the <span> tag will automatically start to output the number.

Let's add our filter:

angular.module('myApp', ['filters']);

angular.module('filters', []).  
filter('validate', [function () {  
    return function (ccnumber) {
      if (!ccnumber) { return ''; }
      var len = ccnumber.length;
      var cardType, valid;
      mul = 0,
      prodArr = [[0, 1, 2, 3, 4, 5, 6, 7, 8, 9], [0, 2, 4, 6, 8, 1, 3, 5, 7, 9]],
      sum = 0;

      while (len--) {
          sum += prodArr[mul][parseInt(ccnumber.charAt(len), 10)];
          mul ^= 1;
      }

      if (sum % 10 === 0 && sum > 0) {
        valid = "valid"
      } else {
        valid = "not valid"
      }
      ccnumber = ccnumber.toString().replace(/\s+/g, '');

      if(/^(34)|^(37)/.test(ccnumber)) {
        cardType = "American Express";
      }
      if(/^(62)|^(88)/.test(ccnumber)) {
        cardType = "China UnionPay";
      }
      if(/^30[0-5]/.test(ccnumber)) {
        cardType = "Diners Club Carte Blanche";
      }
      if(/^(2014)|^(2149)/.test(ccnumber)) {
        cardType = "Diners Club enRoute";
      }
      if(/^36/.test(ccnumber)) {
        cardType = "Diners Club International";
      }
      if(/^(6011)|^(622(1(2[6-9]|[3-9][0-9])|[2-8][0-9]{2}|9([01][0-9]|2[0-5])))|^(64[4-9])|^65/.test(ccnumber)) {
        cardType = "Discover Card";
      }
      if(/^35(2[89]|[3-8][0-9])/.test(ccnumber)) {
        cardType = "JCB";
      }
      if(/^(6304)|^(6706)|^(6771)|^(6709)/.test(ccnumber)) {
        cardType = "Laser";
      }
      if(/^(5018)|^(5020)|^(5038)|^(5893)|^(6304)|^(6759)|^(6761)|^(6762)|^(6763)|^(0604)/.test(ccnumber)) {
        cardType = "Maestro";
      }
      if(/^5[1-5]/.test(ccnumber)) {
        cardType = "MasterCard";
      }
      if (/^4/.test(ccnumber)) {
        cardType = "Visa"
      }
      if (/^(4026)|^(417500)|^(4405)|^(4508)|^(4844)|^(4913)|^(4917)/.test(ccnumber)) {
        cardType = "Visa Electron"
      }
      return ccnumber + " is a(n) " + cardType + " and it's " + valid;
    };
}]);

There's nothing tricky here - I am only using regular expression tests to determine the type of the card. Feel free to give this a go. To get some very good test data, visit validcreditcardnumber.com - they have some randomly generated - but valid - credit card numbers from various issuers.

I would also like to mention here that I have come across a great service that has utilised a similar feature for checkout and it worked like a charm - although probably not written in AngularJS. I wish all checkouts would be as easy as the one by Gumroad. Have a go at their demo: https://gumroad.com/l/demo. Finally, here's the full source code on GitHub for those of you who would like to improve the code.

Show Comments