Credit card regular expressions

Following on from my previous post about credit card authentication with regular expressions, I thought I’d share my other discovery.
The BIN Ranges list I found showed a list of Visa cards which were designated ‘ATMOnly. Figuring these would be a good thing to filter out during validation.
There’s plenty of regular expression help about joining pattern matches with logical OR clauses, but what about wanting to specify patterns which prevent certain things coming through?

Any card number beginning 4 is a Visa card. This is simple to do in a regular expression

^4.*

This will match anything beginning with 4. Not very useful though as it’ll match anything, including text like ‘4CORNERS’. So we’ll refine it to only look for numbers after the 4. There are two ways of doing this:

  1. The more formal set definition
    • [0-9]
  2. The shortcut
    • d
^4[0-9]*
^4d*

So that’s better now it only accepts numbers, but there’s a limit to the number of digits in a card: 16 normally but can be 18 or 19 in some cases (there is an entry on the Barclaycard BIN Ranges document that says some Maestro can be as low as 12!)

^4[0-9]{15-18}

This would accept a 17 digit card number which isn’t valid, so we have to start grouping conditions to get these two possible outcomes using the pipe character ‘|’ as a logical OR operator.

^4([0-9]{15}|[0-9]{17-18})

The next challenge is to stop the ATM Only cards being let through. The list of these I found from Barclaycard’s BIN Ranges document, however I stress you should check with your own card processing service to see what their particular rules of operation are before copying any of this blindly.

In order to prevent certain matches being made, we need to make use of a ‘look-ahead’ operator. When a regular expression engine is checking to see if there’s a match, it works from left to right normally. So when it’s checked ‘^4’ is present, it’s moved its pointer to after the 4, ready to check what’s next. In my previous example, what we asked it to look for next were 15, 17 or 18 more digits. However, we want to jump in before that to make sure that none of the ATM Only prefixes are present.

To do this we use the ‘(?!)‘ operator. What this does is has a look for a matching pattern without moving the pointer along the string, and fails if the match is found. Formally it’s defined as “Match if suffix is not found”; suffix refering to the fact the string will be checked from the pointer onwards, looking right.
One of the ATM Only card ranges starts 490300-1, meaning anything starting 490300 or 490301. To check this is NOT present, we do the following:

^4(?!9030[0-1]{1})([0-9]{15}|[0-9]{17,18})

That’s the basic idea. Here’s the full list, but I stress this is a) probably out of date already and b) may not be complete or may be incorrect for your application.

patterns = "(^4)" +
                                        "(?!9030[0-1]{1})" +
                                        "(?!9031([0-2]{1}[0-9]{1}|3[0-4]{1}))" +
                                        "(?!903[4-9]{1}[0-9]{1})" +
                                        "(?!9040[0-9]{1})" +
                                        "(?!90419)" +
                                        "(?!90451)" +
                                        "(?!90459)" +
                                        "(?!90467)" +
                                        "(?!9047[5-8]{1})" +
                                        "(?!905[0-9]{2})" +
                                        "(?!91001)" +
                                        "(?!9110[0-2]{1})" +
                                        "(?!9110([3-6]{1}[0-9]{1}|7[0-3]{1}))" +
                                        "(?!911(8[3-9]{1}|9[0-9]{1}))" +
                                        "(?!92183)" +
                                        "(?!928[0-9]{2})" +
                                        "(?!987[0-9]{2})" +
                                        "[0-9]{15,19}$";

Disclaimer: This code is not production-ready nor should it be used directly without thought or modification to fit your own purposes. No responsibility or liability is accepted for any fraud, losses or inconvenience for following this advice which has been given in good faith as an documented example only. That should cover it 🙂

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.