如何根据数字检测信用卡类型?

我想知道如何仅根据信用卡的号码来判断信用卡的类型。有谁知道确定可靠的方法吗?

540613 次浏览

信用卡/借记卡号码被称为主帐号。PAN的前六位数字取自开证行的发行人识别号码 (IINs以前称为BIN -银行识别号码-因此您可能会在一些文档中看到该术语的引用)。这六位数字遵循国际标准ISO / IEC 7812,可用于从数字中确定卡片的类型。

不幸的是,实际的ISO/IEC 7812数据库并不是公开的,但是,有一些非官方的列表,包括商业的和免费的,包括在维基百科上

无论如何,要从数字中检测类型,您可以使用如下所示的正则表达式:原创表达的信用

Visa卡号以4开头。

在2016年之前,万事达卡号码以数字51到55开头,但这只会检测到万事达卡信用卡;还有其他使用万事达卡系统发行的卡不属于这个IIN范围。2016年,他们将在222100-272099范围内增加数字。

美国运通卡号码以34或37开头。

大莱俱乐部的卡号以300到305、36或38开头。大莱卡的开头是5,有16位数字。这是大莱俱乐部和万事达卡的合资企业,应该像万事达卡一样处理。

发现以6011或65开头的卡号。

杰西博: ^(?:2131|1800|35[0-9]{3})[0-9]{3,}$ JCB卡以2131、1800或35开头。

不幸的是,万事达卡系统处理的一些卡类型不在万事达卡的IIN范围内(编号从51…55开始);最重要的例子是Maestro卡,其中许多都是从其他银行的IIN系列发行的,因此遍布整个数字空间。因此,最好假设你所接受的非其他类型的卡一定是万事达卡. #

重要的:卡号长度不同;例如,Visa过去曾发行过13位pan和16位pan卡。Visa目前的文件显示,它可能发行或可能已经发行了12到19位数的数字。因此,您不应该检查卡号的长度,除非验证它至少有7位数字(对于一个完整的IIN加上一个检查数字,它应该匹配由Luhn算法预测的值)。

还有一个提示:在处理持卡人PAN之前,从输入中去掉任何空白和标点符号。为什么?因为它通常更容易在组中输入数字,类似于它们在实际信用卡正面的显示方式,即

4444 4444 4444 4444

比正确输入容易得多吗

4444444444444444

因为用户输入了您不希望看到的字符而惩罚用户实际上没有任何好处。

我建议你让字段足够宽,可以显示32个字符,最多允许64个字符;这为扩张提供了充足的空间。

下面这张图可以让我们更深入地了解:

万事达卡将从上述付款开始实施新的BIN范围。

信用卡验证

信用卡的第一个数字可以用来大致了解供应商:

  • 签证:49、44或47
  • Visa电子:42、45、48、49
  • 万事达卡:51
  • 美国运通:34
  • 就餐者:30、36、38
  • JCB: 35

这里是codeproject上的完整的c#或VB代码的各种CC相关的东西

  • IsValidNumber
  • GetCardTypeFromNumber
  • GetCardTestNumber
  • PassesLuhnTest

这篇文章已经发布了几年,没有任何负面评论。

看看这个:

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;
}
// 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">&nbsp; <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
},
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";
}

下面是使用Regex, c#检查信用卡类型的函数

最近我需要这样的功能,我将Zend框架信用卡验证器移植到ruby。 红宝石宝石:https://github.com/Fivell/credit_card_validations zend框架:https://github.com/zendframework/zf2/blob/master/library/Zend/Validator/CreditCard.php

它们都使用INN范围来检测类型。在这里你可以看到关于酒店

根据这一点,您可以选择检测信用卡(没有regexp,但声明一些关于前缀和可能长度的规则)

所以我们有关于最常用卡片的下一个规则

########  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}
]


}

然后通过搜索前缀和比较长度来检测信用卡品牌。也不要忘记luhn算法(它在这里http://en.wikipedia.org/wiki/Luhn描述)。

更新

更新的规则列表可以在这里找到https://raw.githubusercontent.com/Fivell/credit_card_validations/master/lib/data/brands.yaml

这是一个php类函数,通过CCnumber返回CCtype。
这段代码不验证卡或不运行Luhn算法,只尝试根据这个页面中的表查找信用卡类型。基本上使用CCnumber长度和CCcard前缀来确定CCcard类型。

<?php
class CreditcardType
{
public static $creditcardTypes = [
[
'Name' => 'American Express',
'cardLength' => [15],
'cardPrefix' => ['34', '37'],
], [
'Name' => 'Maestro',
'cardLength' => [12, 13, 14, 15, 16, 17, 18, 19],
'cardPrefix' => ['5018', '5020', '5038', '6304', '6759', '6761', '6763'],
], [
'Name' => 'Mastercard',
'cardLength' => [16],
'cardPrefix' => ['51', '52', '53', '54', '55'],
], [
'Name' => 'Visa',
'cardLength' => [13, 16],
'cardPrefix' => ['4'],
], [
'Name' => 'JCB',
'cardLength' => [16],
'cardPrefix' => ['3528', '3529', '353', '354', '355', '356', '357', '358'],
], [
'Name' => 'Discover',
'cardLength' => [16],
'cardPrefix' => ['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'],
], [
'Name' => 'Solo',
'cardLength' => [16, 18, 19],
'cardPrefix' => ['6334', '6767'],
], [
'Name' => 'Unionpay',
'cardLength' => [16, 17, 18, 19],
'cardPrefix' => ['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'],
], [
'Name' => 'Diners Club',
'cardLength' => [14],
'cardPrefix' => ['300', '301', '302', '303', '304', '305', '36'],
], [
'Name' => 'Diners Club US',
'cardLength' => [16],
'cardPrefix' => ['54', '55'],
], [
'Name' => 'Diners Club Carte Blanche',
'cardLength' => [14],
'cardPrefix' => ['300', '305'],
], [
'Name' => 'Laser',
'cardLength' => [16, 17, 18, 19],
'cardPrefix' => ['6304', '6706', '6771', '6709'],
],
];


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;
}
}


在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
}
}
}

单元测试:

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]);
});
});
});
});

只需一点点的勺子喂养:

$("#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("~/images/visa.png")'>");


}


else if (regMasterCard.test($(this).val())) {
$("#CCImage").html("<img height='40px' src='@Url.Content("~/images/mastercard.png")'>");


}


else if (regAmex.test($(this).val())) {


$("#CCImage").html("<img height='40px' src='@Url.Content("~/images/amex.png")'>");


}
else if (regDiscover.test($(this).val())) {


$("#CCImage").html("<img height='40px' src='@Url.Content("~/images/discover.png")'>");


}
else {
$("#CCImage").html("NA");


}


});

更新:2016年6月15日(目前作为最终解决方案)

请注意,我甚至放弃投票给一个被投票最多的,但为了明确这些是regexp实际上是有效的,我用数千个真实的BIN代码测试了它。# EYZ0

2131,180,35 (3528-3589)开始

34岁,37岁开始

300-305, 309, 36, 38-39开始

4开始

2221 - 2720年,51-55开始

Maestro总是在范围内增长:60 - 69,以/开始,而不是其他东西,但开始5必须编码为万事达卡。大师卡必须在代码的末尾被检测到,因为其他一些卡的范围在60-69之间。请看代码。

发现编码非常困难,请从6011, 622126-622925, 644-649, 65开始

javascript中我使用了这个函数。当你把它分配给一个onkeyup事件并尽快给出结果时,这是很好的。

function cc_brand_id(cur_val) {
// 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
var sel_brand = "unknown";
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
}
}


return sel_brand;
}

在这里你可以玩它:

http://jsfiddle.net/upN3L/69/

对于PHP使用这个函数,它也会检测一些子VISA/MC卡:

/**
* Obtain a brand constant from a PAN
*
* @param string $pan               Credit card number
* @param bool   $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
}

匹配各自的卡供应商的正则表达式规则:

  • (4\d{12}(?:\d{3})?)为VISA。
  • 万事达卡(5[1-5]\d{14})
  • (3[47]\d{13})代表美国运通。
  • ((?:5020|5038|6304|6579|6761)\d{12}(?:\d\d)?)代表Maestro。
  • 大莱俱乐部(3(?:0[0-5]|[68][0-9])[0-9]{11})
  • (6(?:011|5[0-9]{2})[0-9]{12})表示发现。
  • (35[2-8][89]\d\d\d{10})代表JCB。

不要试图检测信用卡类型作为处理支付的一部分。您正在冒着拒绝有效事务的风险。

如果您需要向您的支付处理器提供信息(例如PayPal信用卡对象需要命名为卡类型),那么从可用的最少信息中猜测它,例如:

$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的情况下是所有受支持的)卡片方案。实际上,您可能希望完全跳过异常,并默认使用最流行的卡片类型。让支付网关/处理器告诉您在响应您的请求时是否存在验证错误。

现实是你的支付网关不关心你所提供的价值

压缩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;
}
}
};

我用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,则未检测到信用卡类型。

“creditcard”类应该添加到creditcard输入字段中。

在卡牌范围识别(CRR)中,使用一系列正则表达式或其他硬编码范围的算法的一个缺点是,根据我的经验,bin / iin会随着时间的推移而变化。信用卡的联合品牌是一个持续的复杂问题。不同的购卡商/商家可能会要求你不同地对待同一张卡,这取决于例如地理位置。

此外,在过去几年,例如银联卡在更广泛的流通中,现有的模式无法应对新的范围,有时与它们所取代的更广泛的范围交织在一起 了解您的系统需要覆盖的地理位置可能会有所帮助,因为某些范围仅限于在特定国家使用。例如,62范围包括美国的一些AAA子范围,但如果您的商户基地在美国以外,您可能可以将所有62视为银联 你也可能会被要求根据商家的位置区别对待一张卡片。例如,某些英国卡在国内被视为借记卡,但在国际上被视为信用卡。< / p >

有一套非常有用的规则由一家主要的收单银行维护。例如https://www.barclaycard.co.uk/business/files/BIN-Rules-EIRE.pdfhttps://www.barclaycard.co.uk/business/files/BIN-Rules-UK.pdf。(截至2017年6月的有效链接,感谢用户提供了更新的参考链接。)但需要注意的是,虽然这些CRR规则可能代表发卡领域,因为它适用于该实体收购的商家,但它不包括例如标识为CUP/UPI的范围。

这些注释适用于磁条(MagStripe)或PKE (Pan Key Entry)场景。在ICC/EMV领域,情况又有所不同。

更新:本页上的其他答案(以及链接的维基百科页面)的JCB始终是16长。然而,在我的公司,我们有一个专门的工程师团队,他们在多个银行和地区认证我们的POS设备和软件。这个团队从JCB获得的最新认证卡包,有一个19长PAN的合格案例。

Stripe提供了这个很棒的javascript库用于卡方案检测。让我添加一些代码片段,并向您展示如何使用它。

首先包括它到你的网页作为

<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/jquery.payment/1.2.3/jquery.payment.js " ></script>

其次,利用功能cardType来检测卡片方案。

$(document).ready(function() {
var type = $.payment.cardType("4242 4242 4242 4242"); //test card number
console.log(type);
});

这里有更多示例和演示的参考链接。

  1. Stripe blog for jquery.payment.js .js
  2. Github repository

anatoly用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;
}
}

我搜索了很多信用卡格式和电话号码格式。我发现了很多好的建议,但没有一个真正适合我的需求,所以我创建了这段代码。你可以这样使用它:

var sf = smartForm.formatCC(myInputString);
var cardType = sf.cardType;
Usman Y回答的Swift 2.1版本。 使用print语句进行验证,因此调用字符串值

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 ""


}

下面是一些用Python编写的布尔函数的示例,如果根据函数名检测到卡片,则返回True

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

在swift中,您可以创建一个枚举来检测信用卡类型。

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
}
}

调用方法CreditCardType。cardTypeForCreditCardNumber(“#卡号”)返回CreditCardType enum值。

卡号的前六位(包括初始MII) 数字)称为发行人识别号码 (IIN)。这些 识别向该卡发行该卡的发卡机构 持有人。其余的数字由发卡机构分配。的 卡号的长度是它的位数。许多发卡机构打印

.

.

基于上述事实,我想保留一段JAVA代码来识别卡片品牌。

样卡类型

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";

卡前缀

// 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"
};

检查输入的数字是否有任何给定的前缀。

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;
}

最后,效用方法

/**
* 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;
}

参考

follow Luhn’s algorithm


private  boolean validateCreditCardNumber(String str) {


int[] ints = new int[str.length()];
for (int i = 0; i < str.length(); i++) {
ints[i] = Integer.parseInt(str.substring(i, i + 1));
}
for (int i = ints.length - 2; i >= 0; i = i - 2) {
int j = ints[i];
j = j * 2;
if (j > 9) {
j = j % 10 + 1;
}
ints[i] = j;
}
int sum = 0;
for (int i = 0; i < ints.length; i++) {
sum += ints[i];
}
if (sum % 10 == 0) {
return true;
} else {
return false;
}




}


then call this method


Edittext mCreditCardNumberEt;


mCreditCardNumberEt.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {


}


@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {


int cardcount=   s.toString().length();
if(cardcount>=16) {
boolean cardnumbervalid=   validateCreditCardNumber(s.toString());
if(cardnumbervalid) {
cardvalidtesting.setText("Valid Card");
cardvalidtesting.setTextColor(ContextCompat.getColor(context,R.color.green));
}
else {
cardvalidtesting.setText("Invalid Card");
cardvalidtesting.setTextColor(ContextCompat.getColor(context,R.color.red));
}
}
else if(cardcount>0 &&cardcount<16) {
cardvalidtesting.setText("Invalid Card");
cardvalidtesting.setTextColor(ContextCompat.getColor(context,R.color.red));
}


else {
cardvalidtesting.setText("");


}




}


@Override
public void afterTextChanged(Editable s) {


}
});

试试kotlin。添加Regex并添加到when语句中。

private fun getCardType(number: String): String {


val visa = Regex("^4[0-9]{12}(?:[0-9]{3})?$")
val mastercard = Regex("^5[1-5][0-9]{14}$")
val amx = Regex("^3[47][0-9]{13}$")


return when {
visa.matches(number) -> "Visa"
mastercard.matches(number) -> "Mastercard"
amx.matches(number) -> "American Express"
else -> "Unknown"
}
}

一个javascript改进的@Anatoliy答案

function getCardType (number) {
const numberFormated = number.replace(/\D/g, '')
var patterns = {
VISA: /^4[0-9]{12}(?:[0-9]{3})?$/,
MASTER: /^5[1-5][0-9]{14}$/,
AMEX: /^3[47][0-9]{13}$/,
ELO: /^((((636368)|(438935)|(504175)|(451416)|(636297))\d{0,10})|((5067)|(4576)|(4011))\d{0,12})$/,
AURA: /^(5078\d{2})(\d{2})(\d{11})$/,
JCB: /^(?:2131|1800|35\d{3})\d{11}$/,
DINERS: /^3(?:0[0-5]|[68][0-9])[0-9]{11}$/,
DISCOVERY: /^6(?:011|5[0-9]{2})[0-9]{12}$/,
HIPERCARD: /^(606282\d{10}(\d{3})?)|(3841\d{15})$/,
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+$/,
}
for (var key in patterns) {
if (patterns[key].test(numberFormated)) {
return key
}
}
}


console.log(getCardType("4539 5684 7526 2091"))

斯威夫特5 +

extension String {
    

func isMatch(_ Regex: String) -> Bool {
        

do {
let regex = try NSRegularExpression(pattern: Regex)
let results = regex.matches(in: self, range: NSRange(self.startIndex..., in: self))
return results.map {
String(self[Range($0.range, in: self)!])
}.count > 0
} catch {
return false
}
        

}
    

func getCreditCardType() -> String? {
        

let VISA_Regex = "^4[0-9]{6,}$"
let MasterCard_Regex = "^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,}$"
let AmericanExpress_Regex = "^3[47][0-9]{5,}$"
let DinersClub_Regex = "^3(?:0[0-5]|[68][0-9])[0-9]{4,}$"
let Discover_Regex = "^6(?:011|5[0-9]{2})[0-9]{3,}$"
let JCB_Regex = "^(?:2131|1800|35[0-9]{3})[0-9]{3,}$"
        

if self.isMatch(VISA_Regex) {
return "VISA"
} else if self.isMatch(MasterCard_Regex) {
return "MasterCard"
} else if self.isMatch(AmericanExpress_Regex) {
return "AmericanExpress"
} else if self.isMatch(DinersClub_Regex) {
return "DinersClub"
} else if self.isMatch(Discover_Regex) {
return "Discover"
} else if self.isMatch(JCB_Regex) {
return "JCB"
} else {
return nil
}
        

}
    

}

使用。

"1234123412341234".getCreditCardType()