正则表达式信用卡号码测试

Posted

技术标签:

【中文标题】正则表达式信用卡号码测试【英文标题】:Regex credit card number tests 【发布时间】:2012-03-08 02:36:48 【问题描述】:

我正在测试一个应用程序,其中 Regex 模式匹配信用卡,然后应该突出显示这些数字。我正在使用站点http://regexpal.com/ 为我的测试创建测试信用卡号。我的要求是有有效的信用卡号码,它们之间可以有“-”和/或“,”。我没有成功建立这样一个号码,就像我使用网站测试它时那样

http://regexpal.com.

在以下场景中我需要很少的信用号码

    有效的信用卡号,任何数字之间可以有“-”。 有效的信用卡号,任何数字之间可以有“,”。 有效的信用卡号,任何数字之间可以有“,”或“-”的组合。

【问题讨论】:

你用什么语言写这个?根据语言的不同,在使用正则表达式进行验证之前,使用查找和替换 -, 可能更容易进行删除。 这与 java 脚本有关,因为该站点使用 RegexPal 0.1.4 — javascript 正则表达式测试器 你可以参考这个正则表达式教程 - youtube.com/watch?v=M1iFcFUn3qw 【参考方案1】:

先从字符串中删除所有,-以及其他非数字。

然后使用这个匹配 Visa、MasterCard、American Express、Diners Club、Discover 和 JCB 卡的正则表达式:

^(?:4[0-9]12(?:[0-9]3)?|[25][1-7][0-9]14|6(?:011|5[0-9][0-9])[0-9]12|3[47][0-9]13|3(?:0[0-5]|[68][0-9])[0-9]11|(?:2131|1800|35\d3)\d11)$

【讨论】:

您可以考虑修改您的答案以包含有效的信用卡正则表达式,您可以在此处找到:regular-expressions.info/creditcard.html 如果信用卡号不是连续的,例如 4111111111111111,我把它放在 4111 1111 1111 1111 中怎么办?这给出了错误。 @saadk 在验证之前从输入中删除非数字字符。 这需要尽快更新以处理万事达卡 BIN 加 2。mastercard.us/en-us/issuers/get-support/… 万事达卡将于 2016 年 10 月 14 日添加 222100-272099。因此,如果我没看错的话,这将不再捕获他们所有的卡片。【参考方案2】:

常见的信用卡供应商正则表达式:

美国运通卡:^3[47][0-9]13$ BCGlobal:^(6541|6556)[0-9]12$ 全权委托卡:^389[0-9]11$ 大来卡:^3(?:0[0-5]|[68][0-9])[0-9]11$ 发现卡:^65[4-9][0-9]13|64[4-9][0-9]13|6011[0-9]12|(622(?:12[6-9]|1[3-9][0-9]|[2-8][0-9][0-9]|9[01][0-9]|92[0-5])[0-9]10)$ Insta 支付卡:^63[7-9][0-9]13$ JCB卡:^(?:2131|1800|35\d3)\d11$ 韩国本地卡:^9[0-9]15$ 激光卡:^(6304|6706|6709|6771)[0-9]12,15$ 大师卡:^(5018|5020|5038|6304|6759|6761|6763)[0-9]8,15$ 万事达卡:^(5[1-5][0-9]14|2(22[1-9][0-9]12|2[3-9][0-9]13|[3-6][0-9]14|7[0-1][0-9]13|720[0-9]12))$ 单卡:^(6334|6767)[0-9]12|(6334|6767)[0-9]14|(6334|6767)[0-9]15$ 转接卡:^(4903|4905|4911|4936|6333|6759)[0-9]12|(4903|4905|4911|4936|6333|6759)[0-9]14|(4903|4905|4911|4936|6333|6759)[0-9]15|564182[0-9]10|564182[0-9]12|564182[0-9]13|633110[0-9]10|633110[0-9]12|633110[0-9]13$ 银联卡:^(62[0-9]14,17)$ 签证卡:^4[0-9]12(?:[0-9]3)?$ 维萨万事达卡:^(?:4[0-9]12(?:[0-9]3)?|5[1-5][0-9]14)$

【讨论】:

将你的答案格式化为一个列表,我会按字母顺序排列这个列表,但我会让你决定。 如何在这些正则表达式中允许空格和连字符? 值得注意的是,截至 2016 年 10 月 14 日,这里的万事达卡正则表达式将不够用,因为他们将添加一个新的 222100-272099 范围。我刚刚为我们的验证添加了以下内容:^(?:5[1-5][0-9]\d1|222[1-9]|2[3-6][0-9]\d 1|27[01][0-9]|2720)([\ \-]?)\d4\1\d4\1\d4$ 请注意,Visa 万事达卡与 Visa 万事达卡相匹配,并且本身不是卡类型。 只是一个友好的警告,如果您尝试以这种方式匹配特定的方案和卡片长度,您将进入一个受伤的世界。例如,Switch 自 2002 年以来就已不存在,Laser 在 2014 年退出,Visa 将发行 19 位数字卡,而 MasterCard 现在发行 2xxxxx 范围,只是为了强调这种方法的几个问题。正则表达式适用于基本的“它看起来像卡号吗”,但仅此而已。【参考方案3】:

Rupay 借记卡:^6[0-9]15$

【讨论】:

@trimul-rao 你能检查一下吗?这不适用于我的 Rupay 卡。【参考方案4】:

Rupay 卡的正则表达式:

(508[5-9][0-9]12)|(6069[8-9][0-9]11)|(607[0-8][0-9] 12)|(6079[0-8][0-9]11)|(608[0-5][0-9]12)|(6521[5-9][0-9] 11)|(652[2-9][0-9]12)|(6530[0-9]12)|(6531[0-4][0-9]11)

使用 bin 系列: 508500 – 508999, 606985 – 606999, 607000 - 607899, 607900 - 607984, 608001 -- 608500, 652150 --- 652199, 652200 --- 652999, 653000 --- 653099, 653100 --- 653149,

【讨论】:

【参考方案5】:

我想出了一个允许破折号和空格的正则表达式。在这里测试:https://regex101.com/r/Rx2iWD/1

要允许逗号(我认为这很不寻常),只需将其添加到 sep 定义中即可。

php 中:

$ccPatt = '/
    (?(DEFINE)
        (?<sep> [ -]?)
    )
    (?<!\d)(?:
      \d4 (?&sep) \d4 (?&sep) \d4 (?&sep) \d4               # 16 digits
    | \d3 (?&sep) \d3 (?&sep) \d3 (?&sep) \d (?&sep) \d3    # 13 digits
    | \d4 (?&sep) \d6 (?&sep) \d4                             # 14 digits
    | \d4 (?&sep) \d6 (?&sep) \d5                             # 15 digit card
    )(?!\d)
/xu';

【讨论】:

【参考方案6】:

接受的答案很好,但为了适应新的万事达卡 BIN,我相信它需要更新为:

^(?:4[0-9]12(?:[0-9]3)?|[25][1-7][0-9]14|6(?:011|5[0-9][0-9])[0-9]12|3[47][0-9]13|3(?:0[0-5]|[68][0-9])[0-9]11|(?:2131|1800|35\d3)\d11)$

(关键部分是[25][1-7][0-9]14,因为第一个数字现在可以是 2 或 5,第二个数字最多可以是 7)

如果我错了,请纠正我!

【讨论】:

【参考方案7】:

First Data 为 Amex 验证 15 位数字,为 Visa、mc、discover、diners 和 jcb 验证 16 位数字,因此我仅在卡号为 15 或 16 位时才将卡号发送给他们:

^[0-9]15(?:[0-9]1)?$

【讨论】:

【参考方案8】:

领先卡网络的正则表达式

万事达卡(2-Bin, 5-Bin 两者):"(?:5[1-5][0-9]2|222[1-9]|22 [3-9][0-9]|2[3-6][0-9]2|27[01][0-9]|2720)[0-9]12"

签证:“^4[0-9]6,$”

晚餐俱乐部:“(^30[0-5][0-9]11$)|(^(36|38)[0-9]12$) "

美国运通:“^[34|37][0-9]14$”

JCB:“(^3[0-9]15$)|(^(2131|1800)[0-9]11$)”

发现:“^6011-?\d4-?\d4-?\d4$”

【讨论】:

您好,我有一个关于您的美国运通正则表达式的问题:我知道美国运通卡有 15 位数字,并且以 34 或 37 开头,您的正则表达式匹配以 34 开头的字符串或者 37 THEN 14 位,总和不是 16 吗?我在 Rubular 中对其进行了测试,它正在运行,如果我的推理有误,您能告诉我吗? @Gnagno [34|37] 是错误的,因为 [] 用于任何正则表达式引擎中的字符类,这将 NOT 匹配 3437,这将匹配34|7。正确的语法应该是 (34|37) 这里 | 确实是交替,括号只是分隔选项开始和停止的位置。【参考方案9】:

除了以上所有内容,这里还有一个新万事达卡的正则表达式,其中包括 2221-2720 BIN:

^5[1-5][0-9]0,14|^(222[1-9]|2[3-6]\\d2|27[0-1]\\d|2720)[0-9]0,12

请注意,如果用户开始输入与 MasterCard 相对应的卡位,则此正则表达式将匹配。例如,如果用户键入 "222185",那么正则表达式将匹配,因为没有其他类型的卡以 "2221" 开头。如果您想在输入卡片的第一个数字时显示卡片类型,此正则表达式可能会派上用场。

或者,如果您想要 "post factum" 匹配,您可以将最后一部分从 0,140,12 更改为 1412

^5[1-5][0-9]14|^(222[1-9]|2[3-6]\\d2|27[0-1]\\d|2720)[0-9]12

【讨论】:

5[1-5][0-9]0,15 应该是 5[1-5][0-9]0,14,如果你想要完全匹配,除了 12 之外,底部模式应该是 14 @azizbekian - 如果卡号是 5555 5555 5555 4444,这行得通吗? @Neel,是的,它会的。 5[1-5][0-9]14 这部分正则表达式将匹配您的输入。 不匹配,可以试试吗? @azizbekian - regex101.com @Neel,您是否在匹配之前从字符串中删除了所有空格?与5555555555554444 匹配,5555 5555 5555 4444 匹配。【参考方案10】:

2019

做。不是。采用。正则表达式!!!(带 3 个感叹号)


来自 cmets,我必须强调 PeteWiFi 的评论:

只是一个友好的警告,如果你尝试和 以这种方式匹配特定的方案和卡片长度。例如,开关 自 2002 年以来不存在,激光于 2014 年被撤回,签证到期 发行 19 位数字卡,万事达卡现在发行 2xxxx 范围,只是为了强调这种方法的几个问题。一种 正则表达式适用于基本的“它看起来像卡号”,但不是 远不止于此。

如果您想使用正则表达式来了解卡品牌用于视觉用途(例如显示 Visa 徽标或标签),那很好。但是如果你的代码逻辑依赖于它,那么就不要使用正则表达式,也不要使用 3rd 方插件/库!

检测卡号的正则表达式既快速又简单(我知道,这太吸引人了!)。但是,从长远来看,您的项目会遇到许多严重且难以解决的错误。发卡机构不断推出新的卡号模式,或退出旧卡号模式,或可能彻底倒闭。谁知道呢。


解决方案

根据一些经常更新的官方页面(例如this page on wikipedia)构建您自己的解决方案(最好是非正则表达式)。

至于“-”、“.”、“空格”等所有杂音,只要去掉所有这些非数字,就可以使用这个(基于这个answer):

$number = preg_replace("/[^0-9]/", "", "4111-1111 1111.1111");
// Output: 4111111111111111

还不相信?

This page goes into deep technical details why regex is hell。 (注意文章使用了“地狱”这个词,因为一旦你进去了就不能出去了)

编辑

这是我开发的一个解决方案(用 PHP):

// Based on https://en.wikipedia.org/wiki/Payment_card_number
// This constant is used in get_card_brand()
// Note: We're not using regex anymore, with this approach way we can easily read/write/change bin series in this array for future changes
// Key     (string)           brand, keep it unique in the array
// Value   (array)            for each element in the array:
//   Key   (string)           prefix of card number, minimum 1 digit maximum 6 digits per prefix. You can use "dash" for range. Example: "34" card number starts with 34. Range Example: "34-36" (which means first 6 digits starts with 340000-369999) card number starts with 34, 35 or 36
//   Value (array of strings) valid length of card number. You can set multiple ones. You can also use "dash" for range. Example: "16" means length must be 16 digits. Range Example: "15-17" length must be 15, 16 or 17. Multiple values example: ["12", "15-17"] card number can be 12 or 15 or 16 or 17 digits
define('CARD_NUMBERS', [
    'american_express' => [
        '34' => ['15'],
        '37' => ['15'],
    ],
    'diners_club' => [
        '36'      => ['14-19'],
        '300-305' => ['16-19'],
        '3095'    => ['16-19'],
        '38-39'   => ['16-19'],
    ],
    'jcb' => [
        '3528-3589' => ['16-19'],
    ],
    'discover' => [
        '6011'          => ['16-19'],
        '622126-622925' => ['16-19'],
        '624000-626999' => ['16-19'],
        '628200-628899' => ['16-19'],
        '64'            => ['16-19'],
        '65'            => ['16-19'],
    ],
    'dankort' => [
        '5019' => ['16'],
        //'4571' => ['16'],// Co-branded with Visa, so it should appear as Visa
    ],
    'maestro' => [
        '6759'   => ['12-19'],
        '676770' => ['12-19'],
        '676774' => ['12-19'],
        '50'     => ['12-19'],
        '56-69'  => ['12-19'],
    ],
    'mastercard' => [
        '2221-2720' => ['16'],
        '51-55'     => ['16'],
    ],
    'unionpay' => [
        '81' => ['16'],// Treated as Discover cards on Discover network
    ],
    'visa' => [
        '4' => ['13-19'],// Including related/partner brands: Dankort, Electron, etc. Note: majority of Visa cards are 16 digits, few old Visa cards may have 13 digits, and Visa is introducing 19 digits cards
    ],
]);

/**
 * Pass card number and it will return brand if found
 * Examples:
 *     get_card_brand('4111111111111111');                    // Output: "visa"
 *     get_card_brand('4111.1111 1111-1111');                 // Output: "visa" function will remove following noises: dot, space and dash
 *     get_card_brand('411111######1111');                    // Output: "visa" function can handle hashed card numbers
 *     get_card_brand('41');                                  // Output: "" because invalid length
 *     get_card_brand('41', false);                           // Output: "visa" because we told function to not validate length
 *     get_card_brand('987', false);                          // Output: "" no match found
 *     get_card_brand('4111 1111 1111 1111 1111 1111');       // Output: "" no match found
 *     get_card_brand('4111 1111 1111 1111 1111 1111', false);// Output: "visa" because we told function to not validate length
 * Implementation Note: This function doesn't use regex, instead it compares digit by digit. 
 *                      Because we're not using regex in this function, it's easier to add/edit/delete new bin series to global constant CARD_NUMBERS
 * Performance Note: This function is extremely fast, less than 0.0001 seconds
 * @param  String|Int $cardNumber     (required) Card number to know its brand. Examples: 4111111111111111 or 4111 1111-1111.1111 or 411111###XXX1111
 * @param  Boolean    $validateLength (optional) If true then will check length of the card which must be correct. If false then will not check length of the card. For example you can pass 41 with $validateLength = false still this function will return "visa" correctly
 * @return String                                returns card brand if valid, otherwise returns empty string
 */
function get_card_brand($cardNumber, $validateLength = true) 
    $foundCardBrand = '';
    
    $cardNumber = (string)$cardNumber;
    $cardNumber = str_replace(['-', ' ', '.'], '', $cardNumber);// Trim and remove noise
    
    if(in_array(substr($cardNumber, 0, 1), ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'])) // Try to find card number only if first digit is a number, if not then there is no need to check
        $cardNumber = preg_replace('/[^0-9]/', '0', $cardNumber);// Set all non-digits to zero, like "X" and "#" that maybe used to hide some digits
        $cardNumber = str_pad($cardNumber, 6, '0', STR_PAD_RIGHT);// If $cardNumber passed is less than 6 digits, will append 0s on right to make it 6
        
        $firstSixDigits   = (int)substr($cardNumber, 0, 6);// Get first 6 digits
        $cardNumberLength = strlen($cardNumber);// Total digits of the card
        
        foreach(CARD_NUMBERS as $brand => $rows) 
            foreach($rows as $prefix => $lengths) 
                $prefix    = (string)$prefix;
                $prefixMin = 0;
                $prefixMax = 0;
                if(strpos($prefix, '-') !== false) // If "dash" exist in prefix, then this is a range of prefixes
                    $prefixArray = explode('-', $prefix);
                    $prefixMin = (int)str_pad($prefixArray[0], 6, '0', STR_PAD_RIGHT);
                    $prefixMax = (int)str_pad($prefixArray[1], 6, '9', STR_PAD_RIGHT);
                 else // This is fixed prefix
                    $prefixMin = (int)str_pad($prefix, 6, '0', STR_PAD_RIGHT);
                    $prefixMax = (int)str_pad($prefix, 6, '9', STR_PAD_RIGHT);
                

                $isValidPrefix = $firstSixDigits >= $prefixMin && $firstSixDigits <= $prefixMax;// Is string starts with the prefix

                if($isValidPrefix && !$validateLength) 
                    $foundCardBrand = $brand;
                    break 2;// Break from both loops
                
                if($isValidPrefix && $validateLength) 
                    foreach($lengths as $length) 
                        $isValidLength = false;
                        if(strpos($length, '-') !== false) // If "dash" exist in length, then this is a range of lengths
                            $lengthArray = explode('-', $length);
                            $minLength = (int)$lengthArray[0];
                            $maxLength = (int)$lengthArray[1];
                            $isValidLength = $cardNumberLength >= $minLength && $cardNumberLength <= $maxLength;
                         else // This is fixed length
                            $isValidLength = $cardNumberLength == (int)$length;
                        
                        if($isValidLength) 
                            $foundCardBrand = $brand;
                            break 3;// Break from all 3 loops
                        
                    
                
            
        
    
    
    return $foundCardBrand;

【讨论】:

我同意验证不仅应包含模式匹配,还应包括验证 Wikipedia 中指出的校验位。从长远来看,正则表达式的可读性和维护也存在问题。 我知道它作为答案已经很老了,但是(至少在 js 中)您也应该使用以下正则表达式模式来考虑点:/[\D.]/ 这是正确的解决方案。使用 RegEx 并更新它们是一场噩梦。 有趣的事实是您从 DO. NOT. USE. REGEX !!! (with 3 exclamation marks) 开始,但随后我们在您的代码中看到了正则表达式。无论如何,使用复杂正则表达式的正确方法是通过类/函数生成它,它保持验证逻辑并返回一个有用的正则表达式。这种方式正则表达式很容易更新,因为您更新了生成它的代码。 @SławomirLenart 您只阅读了第一行和 PHP 解决方案(可能您跳过了第 1/2 段)。我一直使用正则表达式,但不用于检测卡号。【参考方案11】:

这是我检测卡网络的方法(2020年更新):

function getCardBrandId($pan)
    
        $regs = [
            ELECTRON => "/^(4026|417500|4405|4508|4844|4913|4917)\d+$/",
            MAESTRO  => "/^(?:50|5[6-9]|6[0-9])\d+$/",
            DANKORT  => "/^(5019|4571)\d+$/",
            CUP      => "/^(62|81)\d+$/",
            VISA     => "/^4[0-9]\d+$/",
            DINERS   => "/^(?:5[45]|36|30[0-5]|3095|3[8-9])\d+$/",
            MC       => "/^(?:5[1-5]|222[1-9]|22[3-9][0-9]|2[3-6][0-9][0-9]|27[0-1][0-9]|2720)\d+$/",
            AMEX     => "/^(34|37)\d+$/",
            DISCOVER => "/^6(?:011|22(12[6-9]|1[3-9][0-9]|[2-8][0-9][0-9]|9[01][0-9]|92[0-5])|5|4|2[4-6][0-9]3|28[2-8][0-9]2)\d+$/",
            JCB      => "/^(?:35[2-8][0-9])\d+$/",
            INTERPAY => "/^(636)\d+$/",
            KOREAN   => "/^9[0-9]\d+$/",
            MIR      => "/^(?:220[0-4])\d+$/",
        ];


        foreach ($regs as $brand => $reg) 
            if (preg_match($reg, $pan)) 
                return $brand;
            
        

        return "Unknown";
    

【讨论】:

MAESTRO 正则表达式缺少分隔符并且DISCOVER 模式中没有$,这是有意的吗? 必须返回VISA,去掉“减号” 哇,您的 MAESTRO 正则表达式匹配 5080741549588144562433243243243243241【参考方案12】:

对于任何尝试使用 Swift (iOS) 进行此操作的人,我构建了一个不使用 RegEx 的小项目,它进行 CC 前缀验证、校验位验证(使用 Luhn 算法)和其他一些很酷的东西。修改以添加新的卡类型和号码范围非常简单,无需了解复杂的 RegEx。这类似于@evilReiko 在他的回答中所做的。

100% 免费。完全开源。

https://github.com/ethanwa/credit-card-scanner-and-validator

【讨论】:

【参考方案13】:

所有卡类型的正则表达式

^(3[47][0-9]13|(6541|6556)[0-9]12|389[0-9]11|3(?:0[0-5]|[68][0-9])[0-9]11|65[4-9][0-9]13|64[4-9][0-9]13|6011[0-9]12|(622(?:12[6-9]|1[3-9][0-9]|[2-8][0-9][0-9]|9[01][0-9]|92[0-5])[0-9]10)|63[7-9][0-9]13|(?:2131|1800|35\d3)\d11|9[0-9]15|(6304|6706|6709|6771)[0-9]12,15|(5018|5020|5038|6304|6759|6761|6763)[0-9]8,15|(5[1-5][0-9]14|2(22[1-9][0-9]12|2[3-9][0-9]13|[3-6][0-9]14|7[0-1][0-9]13|720[0-9]12))|(6334|6767)[0-9]12|(6334|6767)[0-9]14|(6334|6767)[0-9]15|(4903|4905|4911|4936|6333|6759)[0-9]12|(4903|4905|4911|4936|6333|6759)[0-9]14|(4903|4905|4911|4936|6333|6759)[0-9]15|564182[0-9]10|564182[0-9]12|564182[0-9]13|633110[0-9]10|633110[0-9]12|633110[0-9]13|(62[0-9]14,17)|4[0-9]12(?:[0-9]3)?|(?:4[0-9]12(?:[0-9]3)?|5[1-5][0-9]14))$

检查可以在这里 https://regex101.com/r/37S1iV/1

【讨论】:

【参考方案14】:

我发现人们在 2021 年使用正则表达式来完成这项任务真的很烦人。 大多数信用卡号码都遵循 Luhn 算法,因此只需获取与银行等相关的字符,对于实际的信用卡号码,请使用此通用解决方案。 你不能简单地暴力破解这个问题。 我用现代风格重构了 python 3 中的 Luhn 算法。 更好地使用这个相对较快的循环,性能也可能比正则表达式更好。

    def check_luhn(cardNo: str) -> bool:
        res = 0
         
        for i, digit in enumerate(cardNo[::-1]):
            d = ord(digit) - ord('0')

            # multiply if it's a second digit
            d = d * 2 if i % 2 == 1 else d

            # if digit get's higher than 10 sum the resulting digits
            res += d // 10 + d % 10
         
        return res % 10 == 0

【讨论】:

以上是关于正则表达式信用卡号码测试的主要内容,如果未能解决你的问题,请参考以下文章

正则表达式

正则表达式-1

精通 JS正则表达式

js正则表达式

精通正则表达式

正则表达式语法