如何根据数字检测信用卡types?
我试图找出如何检测纯粹根据其数量的信用卡types。 有谁知道一个确定的,可靠的方式来find这个?
信用卡/借记卡号码被称为PAN或主帐号 。 PAN的前六位数字取自属于发行银行的IIN或发卡行识别号码 (IIN之前称为BIN – 银行识别号码 – 因此您可能会在某些文件中看到对该术语的参考)。 这六位数字符合ISO / IEC 7812国际标准,可用于从号码中确定卡的types。
不幸的是,实际的ISO / IEC 7812数据库没有公开提供,但是有非官方的清单,包括商业和免费的,包括维基百科 。
无论如何,要从数字中检测types,可以使用如下所示的正则expression式: 对原始expression式进行计分
签证: ^4[0-9]{6,}$
Visa卡号以4开头。
万事达卡: ^5[1-5][0-9]{5,}|222[1-9][0-9]{3,}|22[3-9][0-9]{4,}|2[3-6][0-9]{5,}|27[01][0-9]{4,}|2720[0-9]{3,}$
2016年之前,数字51到55, 但这只会检测万事达信用卡 ; 还有其他使用MasterCard系统发行的卡不属于这个IIN范围。 在2016年,他们将增加数字(222100-272099)。
美国运通: ^3[47][0-9]{5,}$
美国运通卡号码以34或37开头。
大来卡: ^3(?:0[0-5]|[68][0-9])[0-9]{4,}$
Diners Club卡号以300到305,36或38开头。大来卡以5开头,有16位数字。 这是Diners Club和MasterCard的合资企业,应该像万事达卡一样处理。
发现: ^6(?:011|5[0-9]{2})[0-9]{3,}$
发现卡号以6011或65开头。
JCB: ^(?:2131|1800|35[0-9]{3})[0-9]{3,}$
JCB卡以2131,1800或35开头。
不幸的是,有一些MasterCard系统处理的卡types不在MasterCard的IIN范围内(数字从51 … 55开始)。 最重要的例子是Maestro卡,其中许多卡是从其他银行的IIN卡发行的,因此遍布整个数字空间。 因此, 最好假设任何你不接受的其他types的卡都必须是万事达卡 。
重要提示 :卡号的长度有所不同, 例如,Visa过去曾经发行过13位数字的PAN卡和16位数字的PAN卡。 维萨的文件目前表明,它可能会发出或可能已经发出12至19位数字。 因此,您不应该检查卡号的长度,除了validation它是否至less有7位数 (对于完整的IIN加上一个校验位,应该与Luhnalgorithm预测的值相匹配)。
还有一个提示: 在处理持卡人PAN之前,从input中去掉任何空格和标点符号 。 为什么? 因为input组中的数字通常容易得多 ,类似于它们在实际信用卡的正面显示的方式,即,
4444 4444 4444 4444
比正确input要容易得多
4444444444444444
对用户进行惩罚是没有任何好处的,因为他们已经input了你不期望的字符。
这也意味着确保您的input字段至less有 24个字符的空间,否则进入空间的用户将用完房间。 我build议你使字段足够宽,以显示32个字符,并允许多达64; 这为扩展提供了充足的空间。
这里有一个更加深入的视angular:
更新(2014年):校验和方法不再是validation卡的真实性的有效方法,如本答案的评论中所述。
更新(2016年):万事达卡将实施新的BIN范围启动Ach付款 。
在javascript中:
function detectCardType(number) { var re = { electron: /^(4026|417500|4405|4508|4844|4913|4917)\d+$/, maestro: /^(5018|5020|5038|5612|5893|6304|6759|6761|6762|6763|0604|6390)\d+$/, dankort: /^(5019)\d+$/, interpayment: /^(636)\d+$/, unionpay: /^(62|88)\d+$/, visa: /^4[0-9]{12}(?:[0-9]{3})?$/, mastercard: /^5[1-5][0-9]{14}$/, amex: /^3[47][0-9]{13}$/, diners: /^3(?:0[0-5]|[68][0-9])[0-9]{11}$/, discover: /^6(?:011|5[0-9]{2})[0-9]{12}$/, jcb: /^(?:2131|1800|35\d{3})\d{11}$/ } for(var key in re) { if(re[key].test(number)) { return key } } }
unit testing:
describe('CreditCard', function() { describe('#detectCardType', function() { var cards = { '8800000000000000': 'UNIONPAY', '4026000000000000': 'ELECTRON', '4175000000000000': 'ELECTRON', '4405000000000000': 'ELECTRON', '4508000000000000': 'ELECTRON', '4844000000000000': 'ELECTRON', '4913000000000000': 'ELECTRON', '4917000000000000': 'ELECTRON', '5019000000000000': 'DANKORT', '5018000000000000': 'MAESTRO', '5020000000000000': 'MAESTRO', '5038000000000000': 'MAESTRO', '5612000000000000': 'MAESTRO', '5893000000000000': 'MAESTRO', '6304000000000000': 'MAESTRO', '6759000000000000': 'MAESTRO', '6761000000000000': 'MAESTRO', '6762000000000000': 'MAESTRO', '6763000000000000': 'MAESTRO', '0604000000000000': 'MAESTRO', '6390000000000000': 'MAESTRO', '3528000000000000': 'JCB', '3589000000000000': 'JCB', '3529000000000000': 'JCB', '6360000000000000': 'INTERPAYMENT', '4916338506082832': 'VISA', '4556015886206505': 'VISA', '4539048040151731': 'VISA', '4024007198964305': 'VISA', '4716175187624512': 'VISA', '5280934283171080': 'MASTERCARD', '5456060454627409': 'MASTERCARD', '5331113404316994': 'MASTERCARD', '5259474113320034': 'MASTERCARD', '5442179619690834': 'MASTERCARD', '6011894492395579': 'DISCOVER', '6011388644154687': 'DISCOVER', '6011880085013612': 'DISCOVER', '6011652795433988': 'DISCOVER', '6011375973328347': 'DISCOVER', '345936346788903': 'AMEX', '377669501013152': 'AMEX', '373083634595479': 'AMEX', '370710819865268': 'AMEX', '371095063560404': 'AMEX' }; Object.keys(cards).forEach(function(number) { it('should detect card ' + number + ' as ' + cards[number], function() { Basket.detectCardType(number).should.equal(cards[number]); }); }); }); });
更新date:2016年6月15日 (目前为最终解决scheme)
请注意,我甚至给出了一个顶级投票的投票,但要清楚这些是实际正则expression式的正则expression式,我testing了成千上万的真正的BIN代码。 最重要的是使用起始string(^),否则会在现实世界中给出错误的结果!
JCB ^(?:2131|1800|35)[0-9]{0,}$
起始于: 2131,1800,35(3528-3589)
美国运通 ^3[47][0-9]{0,}$
开始于: 34,37
Diners Club ^3(?:0[0-59]{1}|[689])[0-9]{0,}$
开始于: 300-305,309,36,38-39
Visa ^4[0-9]{0,}$
开始于: 4
MasterCard ^(5[1-5]|222[1-9]|22[3-9]|2[3-6]|27[01]|2720)[0-9]{0,}$
: 2221-2720,51-55
Maestro ^(5[06789]|6)[0-9]{0,}$
Maestro始终在60-69的范围内增长,开始于/不是别的,但是开始5必须被编码为万事顺卡。 必须在代码的末尾检测到Maestro卡,因为其他人的卡在60-69的范围内。 请看代码。
发现 ^(6011|65|64[4-9]|62212[6-9]|6221[3-9]|622[2-8]|6229[01]|62292[0-5])[0-9]{0,}$
发现编码相当困难,以: 6011,622126-622925,644-649,65
在JavaScript中我使用这个function。 当你把它分配给一个onkeyup事件并且尽快给出结果的时候,这是很好的。
function cc_brand_id(cur_val) { var sel_brand; // the regular expressions check for possible matches as you type, hence the OR operators based on the number of chars // regexp string length {0} provided for soonest detection of beginning of the card numbers this way it could be used for BIN CODE detection also //JCB jcb_regex = new RegExp('^(?:2131|1800|35)[0-9]{0,}$'); //2131, 1800, 35 (3528-3589) // American Express amex_regex = new RegExp('^3[47][0-9]{0,}$'); //34, 37 // Diners Club diners_regex = new RegExp('^3(?:0[0-59]{1}|[689])[0-9]{0,}$'); //300-305, 309, 36, 38-39 // Visa visa_regex = new RegExp('^4[0-9]{0,}$'); //4 // MasterCard mastercard_regex = new RegExp('^(5[1-5]|222[1-9]|22[3-9]|2[3-6]|27[01]|2720)[0-9]{0,}$'); //2221-2720, 51-55 maestro_regex = new RegExp('^(5[06789]|6)[0-9]{0,}$'); //always growing in the range: 60-69, started with / not something else, but starting 5 must be encoded as mastercard anyway //Discover discover_regex = new RegExp('^(6011|65|64[4-9]|62212[6-9]|6221[3-9]|622[2-8]|6229[01]|62292[0-5])[0-9]{0,}$'); ////6011, 622126-622925, 644-649, 65 // get rid of anything but numbers cur_val = cur_val.replace(/\D/g, ''); // checks per each, as their could be multiple hits //fix: ordering matter in detection, otherwise can give false results in rare cases if (cur_val.match(jcb_regex)) { sel_brand = "jcb"; } else if (cur_val.match(amex_regex)) { sel_brand = "amex"; } else if (cur_val.match(diners_regex)) { sel_brand = "diners_club"; } else if (cur_val.match(visa_regex)) { sel_brand = "visa"; } else if (cur_val.match(mastercard_regex)) { sel_brand = "mastercard"; } else if (cur_val.match(discover_regex)) { sel_brand = "discover"; } else if (cur_val.match(maestro_regex)) { if (cur_val[0] == '5') { //started 5 must be mastercard sel_brand = "mastercard"; } else { sel_brand = "maestro"; //maestro is all 60-69 which is not something else, thats why this condition in the end } } else { sel_brand = "unknown"; } return sel_brand; }
在这里你可以玩它:
对于PHP使用这个function,这也检测一些子VISA / MC卡:
/** * Obtain a brand constant from a PAN * * @param type $pan Credit card number * @param type $include_sub_types Include detection of sub visa brands * @return string */ public static function getCardBrand($pan, $include_sub_types = false) { //maximum length is not fixed now, there are growing number of CCs has more numbers in length, limiting can give false negatives atm //these regexps accept not whole cc numbers too //visa $visa_regex = "/^4[0-9]{0,}$/"; $vpreca_regex = "/^428485[0-9]{0,}$/"; $postepay_regex = "/^(402360|402361|403035|417631|529948){0,}$/"; $cartasi_regex = "/^(432917|432930|453998)[0-9]{0,}$/"; $entropay_regex = "/^(406742|410162|431380|459061|533844|522093)[0-9]{0,}$/"; $o2money_regex = "/^(422793|475743)[0-9]{0,}$/"; // MasterCard $mastercard_regex = "/^(5[1-5]|222[1-9]|22[3-9]|2[3-6]|27[01]|2720)[0-9]{0,}$/"; $maestro_regex = "/^(5[06789]|6)[0-9]{0,}$/"; $kukuruza_regex = "/^525477[0-9]{0,}$/"; $yunacard_regex = "/^541275[0-9]{0,}$/"; // American Express $amex_regex = "/^3[47][0-9]{0,}$/"; // Diners Club $diners_regex = "/^3(?:0[0-59]{1}|[689])[0-9]{0,}$/"; //Discover $discover_regex = "/^(6011|65|64[4-9]|62212[6-9]|6221[3-9]|622[2-8]|6229[01]|62292[0-5])[0-9]{0,}$/"; //JCB $jcb_regex = "/^(?:2131|1800|35)[0-9]{0,}$/"; //ordering matter in detection, otherwise can give false results in rare cases if (preg_match($jcb_regex, $pan)) { return "jcb"; } if (preg_match($amex_regex, $pan)) { return "amex"; } if (preg_match($diners_regex, $pan)) { return "diners_club"; } //sub visa/mastercard cards if ($include_sub_types) { if (preg_match($vpreca_regex, $pan)) { return "v-preca"; } if (preg_match($postepay_regex, $pan)) { return "postepay"; } if (preg_match($cartasi_regex, $pan)) { return "cartasi"; } if (preg_match($entropay_regex, $pan)) { return "entropay"; } if (preg_match($o2money_regex, $pan)) { return "o2money"; } if (preg_match($kukuruza_regex, $pan)) { return "kukuruza"; } if (preg_match($yunacard_regex, $pan)) { return "yunacard"; } } if (preg_match($visa_regex, $pan)) { return "visa"; } if (preg_match($mastercard_regex, $pan)) { return "mastercard"; } if (preg_match($discover_regex, $pan)) { return "discover"; } if (preg_match($maestro_regex, $pan)) { if ($pan[0] == '5') {//started 5 must be mastercard return "mastercard"; } return "maestro"; //maestro is all 60-69 which is not something else, thats why this condition in the end } return "unknown"; //unknown for this system }
public string GetCreditCardType(string CreditCardNumber) { Regex regVisa = new Regex("^4[0-9]{12}(?:[0-9]{3})?$"); Regex regMaster = new Regex("^5[1-5][0-9]{14}$"); Regex regExpress = new Regex("^3[47][0-9]{13}$"); Regex regDiners = new Regex("^3(?:0[0-5]|[68][0-9])[0-9]{11}$"); Regex regDiscover = new Regex("^6(?:011|5[0-9]{2})[0-9]{12}$"); Regex regJCB= new Regex("^(?:2131|1800|35\\d{3})\\d{11}$"); if(regVisa.IsMatch(CreditCardNumber)) return "VISA"; else if (regMaster.IsMatch(CreditCardNumber)) return "MASTER"; else if (regExpress.IsMatch(CreditCardNumber)) return "AEXPRESS"; else if (regDiners.IsMatch(CreditCardNumber)) return "DINERS"; else if (regDiscover.IsMatch(CreditCardNumber)) return "DISCOVERS"; else if (regJCB.IsMatch(CreditCardNumber)) return "JCB"; else return "invalid"; }
这里是使用正则expression式来检查信用卡types的function,c#
看一下这个:
http://www.breakingpar.com/bkp/home.nsf/0/87256B280015193F87256CC70060A01B
function isValidCreditCard(type, ccnum) { /* Visa: length 16, prefix 4, dashes optional. Mastercard: length 16, prefix 51-55, dashes optional. Discover: length 16, prefix 6011, dashes optional. American Express: length 15, prefix 34 or 37. Diners: length 14, prefix 30, 36, or 38. */ var re = new Regex({ "visa": "/^4\d{3}-?\d{4}-?\d{4}-?\d", "mc": "/^5[1-5]\d{2}-?\d{4}-?\d{4}-?\d{4}$/", "disc": "/^6011-?\d{4}-?\d{4}-?\d{4}$/", "amex": "/^3[47]\d{13}$/", "diners": "/^3[068]\d{12}$/"}[type.toLowerCase()]) if (!re.test(ccnum)) return false; // Remove all dashes for the checksum checks to eliminate negative numbers ccnum = ccnum.split("-").join(""); // Checksum ("Mod 10") // Add even digits in even length strings or odd digits in odd length strings. var checksum = 0; for (var i=(2-(ccnum.length % 2)); i<=ccnum.length; i+=2) { checksum += parseInt(ccnum.charAt(i-1)); } // Analyze odd digits in even length strings or even digits in odd length strings. for (var i=(ccnum.length % 2) + 1; i<ccnum.length; i+=2) { var digit = parseInt(ccnum.charAt(i-1)) * 2; if (digit < 10) { checksum += digit; } else { checksum += (digit-9); } } if ((checksum % 10) == 0) return true; else return false; }
最近我需要这样的function,我正在将Zend Framework Credit Card Validator移植到ruby。 rubygem: https : //github.com/Fivell/credit_card_validations zend框架: https : //github.com/zendframework/zf2/blob/master/library/Zend/Validator/CreditCard.php
它们都使用INN范围来检测types。 在这里你可以阅读有关INN
据此,你可以检测信用卡交替(没有正则expression式,但声明一些关于前缀和可能的长度的规则)
所以我们有大多数用过的卡的下一个规则
######## most used brands ######### visa: [ {length: [13, 16], prefixes: ['4']} ], mastercard: [ {length: [16], prefixes: ['51', '52', '53', '54', '55']} ], amex: [ {length: [15], prefixes: ['34', '37']} ], ######## other brands ######## diners: [ {length: [14], prefixes: ['300', '301', '302', '303', '304', '305', '36', '38']}, ], #There are Diners Club (North America) cards that begin with 5. These are a joint venture between Diners Club and MasterCard, and are processed like a MasterCard # will be removed in next major version diners_us: [ {length: [16], prefixes: ['54', '55']} ], discover: [ {length: [16], prefixes: ['6011', '644', '645', '646', '647', '648', '649', '65']} ], jcb: [ {length: [16], prefixes: ['3528', '3529', '353', '354', '355', '356', '357', '358', '1800', '2131']} ], laser: [ {length: [16, 17, 18, 19], prefixes: ['6304', '6706', '6771']} ], solo: [ {length: [16, 18, 19], prefixes: ['6334', '6767']} ], switch: [ {length: [16, 18, 19], prefixes: ['633110', '633312', '633304', '633303', '633301', '633300']} ], maestro: [ {length: [12, 13, 14, 15, 16, 17, 18, 19], prefixes: ['5010', '5011', '5012', '5013', '5014', '5015', '5016', '5017', '5018', '502', '503', '504', '505', '506', '507', '508', '6012', '6013', '6014', '6015', '6016', '6017', '6018', '6019', '602', '603', '604', '605', '6060', '677', '675', '674', '673', '672', '671', '670', '6760', '6761', '6762', '6763', '6764', '6765', '6766', '6768', '6769']} ], # Luhn validation are skipped for union pay cards because they have unknown generation algoritm unionpay: [ {length: [16, 17, 18, 19], prefixes: ['622', '624', '625', '626', '628'], skip_luhn: true} ], dankrot: [ {length: [16], prefixes: ['5019']} ], rupay: [ {length: [16], prefixes: ['6061', '6062', '6063', '6064', '6065', '6066', '6067', '6068', '6069', '607', '608'], skip_luhn: true} ] }
然后通过search前缀和比较长度可以检测信用卡品牌。 另外不要忘记luhn algoritm(这里是http://en.wikipedia.org/wiki/Luhn )。
这里有完整的C#或VB代码,用于代码项目中所有与CC有关的事情 。
- IsValidNumber
- GetCardTypeFromNumber
- GetCardTestNumber
- PassesLuhnTest
这篇文章已经过去了几年没有负面的评论。
紧凑的JavaScript版本
var getCardType = function (number) { var cards = { visa: /^4[0-9]{12}(?:[0-9]{3})?$/, mastercard: /^5[1-5][0-9]{14}$/, amex: /^3[47][0-9]{13}$/, diners: /^3(?:0[0-5]|[68][0-9])[0-9]{11}$/, discover: /^6(?:011|5[0-9]{2})[0-9]{12}$/, jcb: /^(?:2131|1800|35\d{3})\d{11}$/ }; for (var card in cards) { if (cards[card].test(number)) { return card; } } };
这里是一个php类函数通过CCnumber返回CCtype。
此代码不validation卡或不运行Luhnalgorithm只尝试find基于本页中的表的信用卡types。 基本上使用CC号码长度和CC卡号前缀来确定CC卡types。
<?php class CreditcardType { public static $creditcardTypes = array( array('Name'=>'American Express','cardLength'=>array(15),'cardPrefix'=>array('34', '37')) ,array('Name'=>'Maestro','cardLength'=>array(12, 13, 14, 15, 16, 17, 18, 19),'cardPrefix'=>array('5018', '5020', '5038', '6304', '6759', '6761', '6763')) ,array('Name'=>'Mastercard','cardLength'=>array(16),'cardPrefix'=>array('51', '52', '53', '54', '55')) ,array('Name'=>'Visa','cardLength'=>array(13,16),'cardPrefix'=>array('4')) ,array('Name'=>'JCB','cardLength'=>array(16),'cardPrefix'=>array('3528', '3529', '353', '354', '355', '356', '357', '358')) ,array('Name'=>'Discover','cardLength'=>array(16),'cardPrefix'=>array('6011', '622126', '622127', '622128', '622129', '62213', '62214', '62215', '62216', '62217', '62218', '62219', '6222', '6223', '6224', '6225', '6226', '6227', '6228', '62290', '62291', '622920', '622921', '622922', '622923', '622924', '622925', '644', '645', '646', '647', '648', '649', '65')) ,array('Name'=>'Solo','cardLength'=>array(16, 18, 19),'cardPrefix'=>array('6334', '6767')) ,array('Name'=>'Unionpay','cardLength'=>array(16, 17, 18, 19),'cardPrefix'=>array('622126', '622127', '622128', '622129', '62213', '62214', '62215', '62216', '62217', '62218', '62219', '6222', '6223', '6224', '6225', '6226', '6227', '6228', '62290', '62291', '622920', '622921', '622922', '622923', '622924', '622925')) ,array('Name'=>'Diners Club','cardLength'=>array(14),'cardPrefix'=>array('300', '301', '302', '303', '304', '305', '36')) ,array('Name'=>'Diners Club US','cardLength'=>array(16),'cardPrefix'=>array('54', '55')) ,array('Name'=>'Diners Club Carte Blanche','cardLength'=>array(14),'cardPrefix'=>array('300','305')) ,array('Name'=>'Laser','cardLength'=>array(16, 17, 18, 19),'cardPrefix'=>array('6304', '6706', '6771', '6709')) ); private function __construct() {} public static function getType($CCNumber) { $CCNumber= trim($CCNumber); $type='Unknown'; foreach (CreditcardType::$creditcardTypes as $card){ if (! in_array(strlen($CCNumber),$card['cardLength'])) { continue; } $prefixes = '/^('.implode('|',$card['cardPrefix']).')/'; if(preg_match($prefixes,$CCNumber) == 1 ){ $type= $card['Name']; break; } } return $type; } } ?>
Anatoliy在PHP中的答案:
public static function detectCardType($num) { $re = array( "visa" => "/^4[0-9]{12}(?:[0-9]{3})?$/", "mastercard" => "/^5[1-5][0-9]{14}$/", "amex" => "/^3[47][0-9]{13}$/", "discover" => "/^6(?:011|5[0-9]{2})[0-9]{12}$/", ); if (preg_match($re['visa'],$num)) { return 'visa'; } else if (preg_match($re['mastercard'],$num)) { return 'mastercard'; } else if (preg_match($re['amex'],$num)) { return 'amex'; } else if (preg_match($re['discover'],$num)) { return 'discover'; } else { return false; } }
不要尝试检测信用卡types作为处理付款的一部分。 您冒着有效交易的风险。
如果您需要向您的支付处理器提供信息(例如,PayPal信用卡对象要求提供卡types的名称),那么请从最less的信息中猜出
$credit_card['pan'] = preg_replace('/[^0-9]/', '', $credit_card['pan']); $inn = (int) mb_substr($credit_card['pan'], 0, 2); // @see http://en.wikipedia.org/wiki/List_of_Bank_Identification_Numbers#Overview if ($inn >= 40 && $inn <= 49) { $type = 'visa'; } else if ($inn >= 51 && $inn <= 55) { $type = 'mastercard'; } else if ($inn >= 60 && $inn <= 65) { $type = 'discover'; } else if ($inn >= 34 && $inn <= 37) { $type = 'amex'; } else { throw new \UnexpectedValueException('Unsupported card type.'); }
这个实现(仅使用前两位数字)就足以识别所有主要(在PayPal的情况下,所有支持的)卡scheme。 实际上,您可能要完全跳过这个例外,并且默认使用最受欢迎的卡片types。 让支付网关/处理器告诉您是否存在响应您的请求的validation错误。
事实上,您的支付网关并不关心您提供的价值 。
信用卡的第一个号码可以用来近似供应商:
- 签证:49,44或47
- 签证电子:42,45,48,49
- 万事达卡:51
- 美国运通:34
- 食客:30,36,38
- JCB:35
在卡片范围识别(CRR)中,使用一系列正则expression式或其他硬编码范围的algorithm的缺点是BIN / IIN在我的经验中确实会随时间而改变。 牌的联合品牌是一个持续的复杂化。 不同的卡片获取者/商家可能需要您使用不同的卡片,具体取决于地理定位。
此外,在过去几年,例如银联卡在更广泛的stream通中,现有的模式并不适应新的范围,有时这些新的范围与更广泛的范围交错。
了解您的系统需要覆盖的地理位置可能会有所帮助,因为某些范围仅限于特定的国家/地区使用。 例如,范围62在美国包括一些AAA子范围,但是如果您的商户在美国以外,那么您可以将全部62个视为银联。
您也可能会被要求根据商家的位置对卡进行不同的处理。 例如把某些英国卡作为国内借款,而作为国际信贷。
一个主要的获得银行维护一套非常有用的规则。 例如https://www.barclaycard.co.uk/business/files/BIN-Rules-EIRE.pdf和https://www.barclaycard.co.uk/business/files/BIN-Rules-UK.pdf 。 (截至2017年6月的有效链接,这要感谢提供了更新参考链接的用户)。但要注意的是,尽pipe这些CRR规则可能代表适用于其商家的发卡范围,但它不包括例如确定为CUP / UPI的范围。
这些注释适用于磁条(MagStripe)或PKE(Pan Key Entry)场景。 ICC / EMV世界的情况又不同了。
更新:此页面上的其他答案(以及WikiPedia页面上的链接)都有JCB,总是有16个长。 但是,在我的公司,我们有一个专门的工程师团队,他们在多个收单银行和地区对我们的POS设备和软件进行authentication。 这个团队从JCB获得的最新的authentication包有19个长的PAN。
我的解决scheme与jQuery:
function detectCreditCardType() { var type = new Array; type[1] = '^4[0-9]{12}(?:[0-9]{3})?$'; // visa type[2] = '^5[1-5][0-9]{14}$'; // mastercard type[3] = '^6(?:011|5[0-9]{2})[0-9]{12}$'; // discover type[4] = '^3[47][0-9]{13}$'; // amex var ccnum = $('.creditcard').val().replace(/[^\d.]/g, ''); var returntype = 0; $.each(type, function(idx, re) { var regex = new RegExp(re); if(regex.test(ccnum) && idx>0) { returntype = idx; } }); return returntype; }
在返回0的情况下,信用卡types未被检测到。
“信用卡”类应添加到信用卡input字段。
Swift 2.1版本的Usman Y的回答。 使用print语句来validation如此调用某个string值
print(self.validateCardType(self.creditCardField.text!)) func validateCardType(testCard: String) -> String { let regVisa = "^4[0-9]{12}(?:[0-9]{3})?$" let regMaster = "^5[1-5][0-9]{14}$" let regExpress = "^3[47][0-9]{13}$" let regDiners = "^3(?:0[0-5]|[68][0-9])[0-9]{11}$" let regDiscover = "^6(?:011|5[0-9]{2})[0-9]{12}$" let regJCB = "^(?:2131|1800|35\\d{3})\\d{11}$" let regVisaTest = NSPredicate(format: "SELF MATCHES %@", regVisa) let regMasterTest = NSPredicate(format: "SELF MATCHES %@", regMaster) let regExpressTest = NSPredicate(format: "SELF MATCHES %@", regExpress) let regDinersTest = NSPredicate(format: "SELF MATCHES %@", regDiners) let regDiscoverTest = NSPredicate(format: "SELF MATCHES %@", regDiscover) let regJCBTest = NSPredicate(format: "SELF MATCHES %@", regJCB) if regVisaTest.evaluateWithObject(testCard){ return "Visa" } else if regMasterTest.evaluateWithObject(testCard){ return "MasterCard" } else if regExpressTest.evaluateWithObject(testCard){ return "American Express" } else if regDinersTest.evaluateWithObject(testCard){ return "Diners Club" } else if regDiscoverTest.evaluateWithObject(testCard){ return "Discover" } else if regJCBTest.evaluateWithObject(testCard){ return "JCB" } return "" }
Stripe has provided this fantastic javascript library for card scheme detection. Let me add few code snippets and show you how to use it.
Firstly Include it to your web page as
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/jquery.payment/1.2.3/jquery.payment.js " ></script>
Secondly use the function cardType for detecting the card scheme.
$(document).ready(function() { var type = $.payment.cardType("4242 4242 4242 4242"); //test card number console.log(type); });
Here are the reference links for more examples and demos.
- Stripe blog for jquery.payment.js
- Github repository
I searched around quite a bit for credit card formatting and phone number formatting. Found lots of good tips but nothing really suited my exact desires so I created this bit of code . 你这样使用它:
var sf = smartForm.formatCC(myInputString); var cardType = sf.cardType;
In swift you can create an enum to detect the credit card type.
enum CreditCardType: Int { // Enum which encapsulates different card types and method to find the type of card. case Visa case Master case Amex case Discover func validationRegex() -> String { var regex = "" switch self { case .Visa: regex = "^4[0-9]{6,}$" case .Master: regex = "^5[1-5][0-9]{5,}$" case .Amex: regex = "^3[47][0-9]{13}$" case .Discover: regex = "^6(?:011|5[0-9]{2})[0-9]{12}$" } return regex } func validate(cardNumber: String) -> Bool { let predicate = NSPredicate(format: "SELF MATCHES %@", validationRegex()) return predicate.evaluateWithObject(cardNumber) } // Method returns the credit card type for given card number static func cardTypeForCreditCardNumber(cardNumber: String) -> CreditCardType? { var creditCardType: CreditCardType? var index = 0 while let cardType = CreditCardType(rawValue: index) { if cardType.validate(cardNumber) { creditCardType = cardType break } else { index++ } } return creditCardType } }
Call the method CreditCardType.cardTypeForCreditCardNumber("#card number") which returns CreditCardType enum value.
// abobjects.com, parvez ahmad ab bulk mailer use below script function isValidCreditCard2(type, ccnum) { if (type == "Visa") { // Visa: length 16, prefix 4, dashes optional. var re = /^4\d{3}?\d{4}?\d{4}?\d{4}$/; } else if (type == "MasterCard") { // Mastercard: length 16, prefix 51-55, dashes optional. var re = /^5[1-5]\d{2}?\d{4}?\d{4}?\d{4}$/; } else if (type == "Discover") { // Discover: length 16, prefix 6011, dashes optional. var re = /^6011?\d{4}?\d{4}?\d{4}$/; } else if (type == "AmEx") { // American Express: length 15, prefix 34 or 37. var re = /^3[4,7]\d{13}$/; } else if (type == "Diners") { // Diners: length 14, prefix 30, 36, or 38. var re = /^3[0,6,8]\d{12}$/; } if (!re.test(ccnum)) return false; return true; /* // Remove all dashes for the checksum checks to eliminate negative numbers ccnum = ccnum.split("-").join(""); // Checksum ("Mod 10") // Add even digits in even length strings or odd digits in odd length strings. var checksum = 0; for (var i=(2-(ccnum.length % 2)); i<=ccnum.length; i+=2) { checksum += parseInt(ccnum.charAt(i-1)); } // Analyze odd digits in even length strings or even digits in odd length strings. for (var i=(ccnum.length % 2) + 1; i<ccnum.length; i+=2) { var digit = parseInt(ccnum.charAt(i-1)) * 2; if (digit < 10) { checksum += digit; } else { checksum += (digit-9); } } if ((checksum % 10) == 0) return true; else return false; */ } jQuery.validator.addMethod("isValidCreditCard", function(postalcode, element) { return isValidCreditCard2($("#cardType").val(), $("#cardNum").val()); }, "<br>credit card is invalid"); Type</td> <td class="text"> <form:select path="cardType" cssclass="fields" style="border: 1px solid #D5D5D5;padding: 0px 0px 0px 0px;width: 130px;height: 22px;"> <option value="SELECT">SELECT</option> <option value="MasterCard">Mastercard</option> <option value="Visa">Visa</option> <option value="AmEx">American Express</option> <option value="Discover">Discover</option> </form:select> <font color="#FF0000">*</font> $("#signupForm").validate({ rules:{ companyName:{required: true}, address1:{required: true}, city:{required: true}, state:{required: true}, zip:{required: true}, country:{required: true}, chkAgree:{required: true}, confPassword:{required: true}, lastName:{required: true}, firstName:{required: true}, ccAddress1:{required: true}, ccZip:{ postalcode : true }, phone:{required: true}, email:{ required: true, email: true }, userName:{ required: true, minlength: 6 }, password:{ required: true, minlength: 6 }, cardNum:{ isValidCreditCard : true },
Just a little spoon feeding:
$("#CreditCardNumber").focusout(function () { var regVisa = /^4[0-9]{12}(?:[0-9]{3})?$/; var regMasterCard = /^5[1-5][0-9]{14}$/; var regAmex = /^3[47][0-9]{13}$/; var regDiscover = /^6(?:011|5[0-9]{2})[0-9]{12}$/; if (regVisa.test($(this).val())) { $("#CCImage").html("<img height='40px' src='@Url.Content("~http://img.dovov.comvisa.png")'>"); } else if (regMasterCard.test($(this).val())) { $("#CCImage").html("<img height='40px' src='@Url.Content("~http://img.dovov.commastercard.png")'>"); } else if (regAmex.test($(this).val())) { $("#CCImage").html("<img height='40px' src='@Url.Content("~http://img.dovov.comamex.png")'>"); } else if (regDiscover.test($(this).val())) { $("#CCImage").html("<img height='40px' src='@Url.Content("~http://img.dovov.comdiscover.png")'>"); } else { $("#CCImage").html("NA"); } });
Here is an example of some boolean functions written in Python that return True
if the card is detected as per the function name.
def is_american_express(cc_number): """Checks if the card is an american express. If us billing address country code, & is_amex, use vpos https://en.wikipedia.org/wiki/Bank_card_number#cite_note-GenCardFeatures-3 :param cc_number: unicode card number """ return bool(re.match(r'^3[47][0-9]{13}$', cc_number)) def is_visa(cc_number): """Checks if the card is a visa, begins with 4 and 12 or 15 additional digits. :param cc_number: unicode card number """ # Standard Visa is 13 or 16, debit can be 19 if bool(re.match(r'^4', cc_number)) and len(cc_number) in [13, 16, 19]: return True return False def is_mastercard(cc_number): """Checks if the card is a mastercard. Begins with 51-55 or 2221-2720 and 16 in length. :param cc_number: unicode card number """ if len(cc_number) == 16 and cc_number.isdigit(): # Check digit, before cast to int return bool(re.match(r'^5[1-5]', cc_number)) or int(cc_number[:4]) in range(2221, 2721) return False def is_discover(cc_number): """Checks if the card is discover, re would be too hard to maintain. Not a supported card. :param cc_number: unicode card number """ if len(cc_number) == 16: try: # return bool(cc_number[:4] == '6011' or cc_number[:2] == '65' or cc_number[:6] in range(622126, 622926)) return bool(cc_number[:4] == '6011' or cc_number[:2] == '65' or 622126 <= int(cc_number[:6]) <= 622925) except ValueError: return False return False def is_jcb(cc_number): """Checks if the card is a jcb. Not a supported card. :param cc_number: unicode card number """ # return bool(re.match(r'^(?:2131|1800|35\d{3})\d{11}$', cc_number)) # wikipedia return bool(re.match(r'^35(2[89]|[3-8][0-9])[0-9]{12}$', cc_number)) # PawelDecowski def is_diners_club(cc_number): """Checks if the card is a diners club. Not a supported card. :param cc_number: unicode card number """ return bool(re.match(r'^3(?:0[0-6]|[68][0-9])[0-9]{11}$', cc_number)) # 0-5 = carte blance, 6 = international def is_laser(cc_number): """Checks if the card is laser. Not a supported card. :param cc_number: unicode card number """ return bool(re.match(r'^(6304|670[69]|6771)', cc_number)) def is_maestro(cc_number): """Checks if the card is maestro. Not a supported card. :param cc_number: unicode card number """ possible_lengths = [12, 13, 14, 15, 16, 17, 18, 19] return bool(re.match(r'^(50|5[6-9]|6[0-9])', cc_number)) and len(cc_number) in possible_lengths # Child cards def is_visa_electron(cc_number): """Child of visa. Checks if the card is a visa electron. Not a supported card. :param cc_number: unicode card number """ return bool(re.match(r'^(4026|417500|4508|4844|491(3|7))', cc_number)) and len(cc_number) == 16 def is_total_rewards_visa(cc_number): """Child of visa. Checks if the card is a Total Rewards Visa. Not a supported card. :param cc_number: unicode card number """ return bool(re.match(r'^41277777[0-9]{8}$', cc_number)) def is_diners_club_carte_blanche(cc_number): """Child card of diners. Checks if the card is a diners club carte blance. Not a supported card. :param cc_number: unicode card number """ return bool(re.match(r'^30[0-5][0-9]{11}$', cc_number)) # github PawelDecowski, jquery-creditcardvalidator def is_diners_club_carte_international(cc_number): """Child card of diners. Checks if the card is a diners club international. Not a supported card. :param cc_number: unicode card number """ return bool(re.match(r'^36[0-9]{12}$', cc_number)) # jquery-creditcardvalidator
The regular expression rules that match the respective card vendors :
-
(4\d{12}(?:\d{3})?)
for VISA. -
(5[1-5]\d{14})
for MasterCard. -
(3[47]\d{13})
for AMEX. -
((?:5020|5038|6304|6579|6761)\d{12}(?:\d\d)?)
for Maestro. -
(3(?:0[0-5]|[68][0-9])[0-9]{11})
for Diners Club. -
(6(?:011|5[0-9]{2})[0-9]{12})
for Discover. -
(35[2-8][89]\d\d\d{10})
for JCB.
The first six digits of a card number (including the initial MII digit) are known as the issuer identification number (IIN). These identify the card issuing institution that issued the card to the card holder. The rest of the number is allocated by the card issuer. The card number's length is its number of digits. Many card issuers print the entire IIN and account number on their card.
Based on the above facts I would like to keep a snippet of JAVA code to identify card brand.
Sample card types
public static final String AMERICAN_EXPRESS = "American Express"; public static final String DISCOVER = "Discover"; public static final String JCB = "JCB"; public static final String DINERS_CLUB = "Diners Club"; public static final String VISA = "Visa"; public static final String MASTERCARD = "MasterCard"; public static final String UNKNOWN = "Unknown";
Card Prefixes
// Based on http://en.wikipedia.org/wiki/Bank_card_number#Issuer_identification_number_.28IIN.29 public static final String[] PREFIXES_AMERICAN_EXPRESS = {"34", "37"}; public static final String[] PREFIXES_DISCOVER = {"60", "62", "64", "65"}; public static final String[] PREFIXES_JCB = {"35"}; public static final String[] PREFIXES_DINERS_CLUB = {"300", "301", "302", "303", "304", "305", "309", "36", "38", "39"}; public static final String[] PREFIXES_VISA = {"4"}; public static final String[] PREFIXES_MASTERCARD = { "2221", "2222", "2223", "2224", "2225", "2226", "2227", "2228", "2229", "223", "224", "225", "226", "227", "228", "229", "23", "24", "25", "26", "270", "271", "2720", "50", "51", "52", "53", "54", "55" };
Check to see if the input number has any of the given prefixes.
public String getBrand(String number) { String evaluatedType; if (StripeTextUtils.hasAnyPrefix(number, PREFIXES_AMERICAN_EXPRESS)) { evaluatedType = AMERICAN_EXPRESS; } else if (StripeTextUtils.hasAnyPrefix(number, PREFIXES_DISCOVER)) { evaluatedType = DISCOVER; } else if (StripeTextUtils.hasAnyPrefix(number, PREFIXES_JCB)) { evaluatedType = JCB; } else if (StripeTextUtils.hasAnyPrefix(number, PREFIXES_DINERS_CLUB)) { evaluatedType = DINERS_CLUB; } else if (StripeTextUtils.hasAnyPrefix(number, PREFIXES_VISA)) { evaluatedType = VISA; } else if (StripeTextUtils.hasAnyPrefix(number, PREFIXES_MASTERCARD)) { evaluatedType = MASTERCARD; } else { evaluatedType = UNKNOWN; } return evaluatedType; }
Finally, The Utility method
/** * Check to see if the input number has any of the given prefixes. * * @param number the number to test * @param prefixes the prefixes to test against * @return {@code true} if number begins with any of the input prefixes */ public static boolean hasAnyPrefix(String number, String... prefixes) { if (number == null) { return false; } for (String prefix : prefixes) { if (number.startsWith(prefix)) { return true; } } return false; }
参考
- Stripe Card Builder
I use https://github.com/bendrucker/creditcards-types/ to detect the credit card type from number. One issue I ran into is discover test number 6011 1111 1111 1117
from https://www.cybersource.com/developers/other_resources/quick_references/test_cc_numbers/ we can see it is a discover number because it starts by 6011. But the result I get from the creditcards-types is "Maestro". I opened the issue to the author. He replied me very soon and provide this pdf doc downloads/IPP_VAR_Compliance.pdf From the doc we can see clearly that 6011 1111 1111 1117 does not fall into the range of discover credit card.