Take the 2-minute tour ×
Stack Overflow is a question and answer site for professional and enthusiast programmers. It's 100% free, no registration required.

I'm looking to use regex to do some form manipulation based on the user's credit card input (note: this is only front-end validation for the sake of UX, another service and API handles actual credit card validation).

I'd like to create a $watch statement or some equivalent to match a user's input in the credit card field against several regex statements that are ascribed to different card types.

So, my question in a nutshell is: what's the best pattern to implement that can match against multiple regent statements without degrading performance with too many watchers? My first thought was to write multiple if or switch statements, but that seems like a problem for extensibility and a complicated bit of logic isn't often the best solution.

Thanks everyone!

Here's what I have so far:

    var defaultFormat = /(\d{1,4})/g;

    $scope.cards = [{
        type: 'maestro',
        pattern: /^(5018|5020|5038|6304|6759|676[1-3])/,
        format: defaultFormat,
        length: [12, 13, 14, 15, 16, 17, 18, 19],
        cvcLength: [3],
        luhn: true
    }, {
        type: 'dinersclub',
        pattern: /^(36|38|30[0-5])/,
        format: defaultFormat,
        length: [14],
        cvcLength: [3],
        luhn: true
    }, {
        type: 'laser',
        pattern: /^(6706|6771|6709)/,
        format: defaultFormat,
        length: [16, 17, 18, 19],
        cvcLength: [3],
        luhn: true
    }, {
        type: 'jcb',
        pattern: /^35/,
        format: defaultFormat,
        length: [16],
        cvcLength: [3],
        luhn: true
    }, {
        type: 'unionpay',
        pattern: /^62/,
        format: defaultFormat,
        length: [16, 17, 18, 19],
        cvcLength: [3],
        luhn: false
    }, {
        type: 'discover',
        pattern: /^(6011|65|64[4-9]|622)/,
        format: defaultFormat,
        length: [16],
        cvcLength: [3],
        luhn: true
    }, {
        type: 'mastercard',
        pattern: /^5[1-5]/,
        format: defaultFormat,
        length: [16],
        cvcLength: [3],
        luhn: true
    }, {
        type: 'amex',
        pattern: /^3[47]/,
        format: /(\d{1,4})(\d{1,6})?(\d{1,5})?/,
        length: [15],
        cvcLength: [3, 4],
        luhn: true
    }, {
        type: 'visa',
        pattern: /^4/,
        format: defaultFormat,
        length: [13, 16],
        cvcLength: [3],
        luhn: true
    }];

I also have a luhn algorithm that I can use to check the credit cards.

luhnCheck = function(num) {
var digit, digits, odd, sum, _i, _len;
odd = true;
sum = 0;
digits = (num + '').split('').reverse();
for (_i = 0, _len = digits.length; _i < _len; _i++) {
  digit = digits[_i];
  digit = parseInt(digit, 10);
  if ((odd = !odd)) {
    digit *= 2;
  }
  if (digit > 9) {
    digit -= 9;
  }
  sum += digit;
}
return sum % 10 === 0;
};
share|improve this question
    
Why not just use one $watch and get the type of your card when the model changes? –  Evandro Silva Jun 9 at 23:27
    
That could work; I'm a bit new to JS/Angular, so I'm wondering how you'd implement executing the regex statements? –  markthethomas Jun 9 at 23:29
add comment

2 Answers

up vote 1 down vote accepted

Plunker

You're right, you'd want to watch the value of a model in order to change the "type" stored in a variable somewhere.

Here's an example of a relevant watch statement:

    $scope.$watch( 'model', function()
    {
      var found = false;

      angular.forEach( $scope.cards, function( item, index )
      {
        if ( $scope.model.match( item.pattern ) )
        {
          $scope.card_type = item.type;
          found = true;
        }
      });

      // You could run your Luhn method here too

      if ( !found ) $scope.card_type = 'none';
    });
share|improve this answer
    
Thanks! I appreciate the help and especially the plunker. I can see your solution works and will probably implement a version of it. One question though, if you you don't mind: what role does the HTTP GET request play? Is there a way to do the same action without JSON-ifying anything or making an additional request? Thanks again! –  markthethomas Jun 10 at 0:22
    
@markimus Whoops good question, that was cruft left behind when I tried to throw the $scope.cards array into a .json file, but I think .json requires quotes around the keys, unlike JS. I've updated the Plunker; totally ignore the $http bit. –  Morgan Delaney Jun 10 at 16:03
    
Gotcha! I suppose I could have just nixed it and seen what would of happen, but I thought I would ask anyways. Thanks again! –  markthethomas Jun 10 at 16:05
add comment

This is an untested solution:

On the credit card input form field:

<input type="text" ng-change="matchCCTypes()" ng-model="ccnum">

Controller:

$scope.ccnum = "";
$scope.ccType = null;

$scope.matchCCTypes() = function() {
  $scope.ccType = null;
  angular.forEach($scope.cards, function(cardType) {
    // pseudo code:
    // if ($scope.ccnum matches current pattern) {
    //   $scope.ccType = cardType.type
    // }
  });
};

This way, you can avoid setting up a watcher. The function will run only when the ccnum field changes.

share|improve this answer
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.