Skip to main content

Custom AngularJS filter to determine credit card type

4 min read

Older Article

This article was published 13 years ago. Some information may be outdated or no longer applicable.

There’s one thing I’ve never understood about eCommerce checkouts. Why am I being asked for my payment card type? (I also wonder why I’m asked for the actual number, but oh well.) From a credit card number, you can easily determine what card the user holds. I personally hate forms, so the less I need to type, the more appealing a site is. A “card type” dropdown makes zero sense to me.

I can see a few reasons why a site might include one, but none of them hold up:

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

Because I’ve been working with AngularJS and because I read a great article on creating a shopping cart app using only AngularJS, I thought I’d build a custom filter to determine the card type from the credit card number alone.

AngularJS ships with filters that let a subset of items from an array be returned. It’s almost magical: long lists get filtered instantly without needing any backend search. Filters can also transform models. Imagine a date in an array returned from a database. Formatting that date is as simple as specifying the format as a filter:

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

There are several built-in filters for limiting and transforming data. Adding a custom filter to validate credit cards fits right in.

Let’s talk about credit/bank cards. The first digit is called an MII (Major Industry Identifier). It represents the category of the organisation that issued the card. The first six digits (including the MII), known as the Issuer Identification Number (IIN), identify the institution that issued the card. That’s the information we need to determine the credit card type. There are lots of online resources for the lookup data. Some even go into identifying the bank that issued the credit card.

Credit card validity is also ensured by the Luhn algorithm. In short, this checksum mechanism verifies credit card numbers using some maths. I’ve incorporated this existing implementation into my filter.

Enough background. What we’re building is a simple AngularJS filter that identifies the credit card type from user input and checks whether the numbers are valid.

The HTML stays minimal:

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

That’s basic AngularJS data-binding. If you start typing a number (make sure you remove the | validate bit first, as it’ll throw exceptions), the <span> tag automatically echoes the number back.

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;
    };
  },
]);

Nothing tricky here. I’m just running regular expression tests to determine the card type. Give it a go. For solid test data, visit validcreditcardnumber.com, which has randomly generated (but valid) credit card numbers from various issuers.

I should also mention that I’ve come across a great service that uses a similar feature for checkout, and it worked beautifully. I wish all checkouts were as smooth as Gumroad’s. And here’s the full source code on GitHub for anyone who’d like to improve the code.