How to Correctly Detect Credit Card Type

Avoid common mistakes when implementing card type detection.

Most card type detection tutorials and libraries use regular expressions without references, often omitting or incorrectly detecting card types. This guide explains the card type detection process, cites sources, and analyzes the detection algorithm and user interface of Creditcard.js, a more usable credit card form.

The first six digits of the credit card number encode the card type/issuer. Sometimes the relationship between card numbers and card type can be simplified into a rule like “Visa credit card numbers start with 4.” For most card types, the mapping to card numbers is less straightforward, often spanning multiple disjoint number ranges that can be difficult to represent programmatically. To further complicate card type detection, this mapping changes when card companies allocate new numbers, form partnerships with other card companies, or go out of business.

The following table contains the mapping from card types to card number prefixes. This table is adapted from Wikipedia’s bank card number page, the most frequently updated public resource on this data.

Most tutorials and JavaScript libraries use regular expressions to recognize these card number prefixes, but this pattern matching tool struggles to cleanly represent number ranges. The resulting complexity makes card type detection code difficult to write, read, and maintain. As an example of this complexity, consider this JavaScript regular expression used to detect Discover card numbers starting with 6011, 622126-622925, 644-649, and 65:

This regular expression bloats when matching the range 622126-622925. Number ranges are usually represented with regular expression character sets, e.g. /[1-5]/ to match the numbers 1 to 5. However, these character sets only cleanly match ranges with single digit variations. To match a range spanning multiple digits like 126-925, the regular expression must subdivide this range into the sections 126-129, 130-199, 200-899, 900-919, and 920-925 and match on each individually. This awkward splitting reinforces a reputation that regular expressions are difficult to read.

Some JavaScript libraries and tutorials unintentionally avoid this problematic regular expression by omitting Discover cards, including only more popular card types like Visa and MasterCard. Other JavaScript libraries shorten this regular expression by fudging the number ranges:

Regular expressions have clear limitations for detecting credit card type. The resulting code looks nothing like the intuitive representation of number ranges, increasing the tendency to copy and pasting without verification. As a result, many of the card type detection algorithms in tutorials and JavaScript libraries are outdated, incomplete, and incorrect.

Instead of using regular expressions to detect credit card types, Creditcard.js uses a specialized data structure called an inversion map from the Google Closure Library. This data structure maps integer ranges to values, a perfect fit for mapping card number prefixes to card types. This card detection code isn’t restricted to regular expression syntax, so it’s free to declaratively mirror the original card number ranges before being transformed and assembled into the final data structure. Here’s how the Discover card number ranges are represented:

Compared to the regular expression required to capture the same ranges, this declarative JavaScript is easier to read and verify. It’s also more straightforward to add or update number ranges when the mapping change. For example, a partnership between card companies changed the corresponding card type for the 622126-622925 card number range from China UnionPay to Discover. Unfortunately, due to a lack of citation and assumption that these mappings are static, some card type detection libraries and tutorials continue recognizing this range as UnionPay or don’t detect any card type.

To convert this declarative code into a complete card type detection function, these card types and number ranges are transformed and inserted into the InversionMap data structure. The transformation code first constructs an empty InversionMap and then inserts each indivdual number range to card type mapping using the spliceInversion method. Once the InversionMap is fully populated, it can be queried with card numbers using the at method. See below for an example of this card type detection in action.

The downside of using inversion maps instead of regular expressions is more lines of code. However, the increase in file size is relatively minimal — the complete card type detection code is 1.2kB minified and gzipped. To achieve this small file size, Creditcard.js uses the Google Closure Compiler’s advanced compilation mode, which aggressively removes unreachable code.

The detection algorithm is only half the challenge, and too often a poorly designed card type user interface will cause usability regressions instead of improvements. A common mistake is presenting the automatically detected card types as interactive buttons when they are actually read-only. The following is a screenshot from a payment form that makes this mistake:

Confusing Card Type User Interface

The row of card type icons look like clickable buttons because of their rounded borders, shadow, and gradient. However, clicking these buttons has no effect. These card type icons only update after customers begin typing their card number, when the detected card type icon is left unchanged while the other icons are dimmed. The leading position of the card type icons and the interactive appearance result in confused clicks and poor checkout usability.

Fortunately, the designers of this payment page updated the form by initially fading all icons and moving these below the card number input:

Revised Card Type User Interface

This updated form should decrease the number of clicks, though the icons still look interactive and may attract clicks from customers used to manually selecting their card type. When these clicks occur, the form focuses the card number input instead of becoming unresponsive.

The unintended clicks on the read-only card type icons can be eliminated by revealing the card type only after it is detected. Creditcard.js takes this approach by displaying the detected card type in the bottom right of the credit card form:

Card Number Security Code Name on Card

Expiration

American Express

Creditcard.js Card Type User Interface

The use of large text instead of small icons creates a more readable interface. The text is styled just enough to be noticeable (prototype versions of Creditcard.js experimented with transitioning the entire form background to the card type logo, which was distracting and made form labels straining to read). The appearance of the card type after the card number is typed establishes a logical dependency between these fields, reducing the likelihood users will attempt to directly interact with the card type.

Additional benefits of using text over icons are smaller file size and faster page load. The image containing these card type icons is almost as large as all of Creditcard.js. This image weighs in at 11kB and is well optimized: the icons are stored in a single png spritesheet, and the dimming effect is performed with CSS instead of a duplicated set of icons. The entire HTML, JS, and CSS of Creditcard.js is 13kB gzipped and minified. An additional image load only adds a small delay to page load, but these delays have a measurably negative effect on user experience and revenue.

The outward simplicity of the credit card form hides a disproportionate number of technical and design challenges. This guide on card type detection is the first in a series of articles looking into these implementation challenges.