在PHP中,验证信用卡的最佳方法是什么?

73

在没有其他信息的情况下,给定一个信用卡号码,PHP中确定其是否为有效号码的最佳方法是什么?

现在我需要一些可以使用美国运通、Discover、MasterCard和Visa的内容,但如果它还可以适用于其他类型的话,那就更好了。

9个回答

155

验证卡号有三个部分:

  1. PATTERN - 是否与发行者的模式匹配(例如VISA / Mastercard /等)
  2. CHECKSUM - 它是否实际进行了校验和(例如仅在“34”之后加入13个随机数字来使其成为AMEX卡号)
  3. 确实存在 - 它是否实际上有一个关联的账户(如果没有商家账户,您不太可能得到此信息)

Pattern

  • MASTERCARD 前缀= 51-55,长度= 16(Mod10检查和)
  • VISA 前缀= 4,长度= 13或16(Mod10)
  • AMEX 前缀= 34或37,长度= 15(Mod10)
  • Diners Club/Carte 前缀= 300-305、36或38,长度= 14(Mod10)
  • Discover 前缀= 6011、622126-622925、644-649、65,长度= 16(Mod10)
  • 等等。 (前缀的详细列表

Checksum

大多数卡使用Luhn算法进行校验和:

Wikipedia上描述的Luhn算法

Wikipedia链接中有许多实现的链接,包括PHP:

<?
/* Luhn algorithm number checker - (c) 2005-2008 shaman - www.planzero.org *
 * This code has been released into the public domain, however please      *
 * give credit to the original author where possible.                      */

function luhn_check($number) {

  // Strip any non-digits (useful for credit card numbers with spaces and hyphens)
  $number=preg_replace('/\D/', '', $number);

  // Set the string length and parity
  $number_length=strlen($number);
  $parity=$number_length % 2;

  // Loop through each digit and do the maths
  $total=0;
  for ($i=0; $i<$number_length; $i++) {
    $digit=$number[$i];
    // Multiply alternate digits by two
    if ($i % 2 == $parity) {
      $digit*=2;
      // If the sum is two digits, add them together (in effect)
      if ($digit > 9) {
        $digit-=9;
      }
    }
    // Total up the digits
    $total+=$digit;
  }

  // If the total mod 10 equals 0, the number is valid
  return ($total % 10 == 0) ? TRUE : FALSE;

}
?>

1
在模式下,您可以添加“Discover”,前缀为“6”(可能是“60”),长度为16。 - James Curran
1
根据http://www.beachnet.com/~hstiles/cardtype.html,MasterCard的前缀为51-55而不是51或55。 - Ben
8
此函数会从字符串中删除所有非数字字符,因此卡号“FRED”会被视为有效。在调用该函数之前,请确保您已验证卡号仅包含数字! - Matt Connolly
3
一组由纯零组成的信用卡号也可以被识别为有效。 - Gazillion
2
@BijuPDais 如文中所述 - 要检查它是否真的存在,您可能需要成为商家并实际尝试对该卡进行结算。许多行动(例如酒店)会向信用卡收取一笔小额款项,然后退还。在所有验证方法中 - 那是唯一确定卡是否有效的真正测试! - Ray Hayes
显示剩余5条评论

30

来自《PHP中你不能没有的十个正则表达式》:

function check_cc($cc, $extra_check = false){
    $cards = array(
        "visa" => "(4\d{12}(?:\d{3})?)",
        "amex" => "(3[47]\d{13})",
        "jcb" => "(35[2-8][89]\d\d\d{10})",
        "maestro" => "((?:5020|5038|6304|6579|6761)\d{12}(?:\d\d)?)",
        "solo" => "((?:6334|6767)\d{12}(?:\d\d)?\d?)",
        "mastercard" => "(5[1-5]\d{14})",
        "switch" => "(?:(?:(?:4903|4905|4911|4936|6333|6759)\d{12})|(?:(?:564182|633110)\d{10})(\d\d)?\d?)",
    );
    $names = array("Visa", "American Express", "JCB", "Maestro", "Solo", "Mastercard", "Switch");
    $matches = array();
    $pattern = "#^(?:".implode("|", $cards).")$#";
    $result = preg_match($pattern, str_replace(" ", "", $cc), $matches);
    if($extra_check && $result > 0){
        $result = (validatecard($cc))?1:0;
    }
    return ($result>0)?$names[sizeof($matches)-2]:false;
}

样例输入:

$cards = array(
    "4111 1111 1111 1111",
);

foreach($cards as $c){
    $check = check_cc($c, true);
    if($check!==false)
        echo $c." - ".$check;
    else
        echo "$c - Not a match";
    echo "<br/>";
}

这提供给我们

4111 1111 1111 1111 - Visa

2
validatecard 函数在哪里? - joseantgv
此答案对于新的Mastercard 2系列标识将失败 https://www.mastercard.us/content/mccom/en-us/issuers/get-support/2-series-bin-expansion.html/ - Samy Massoud

14

在代码中最好不要进行验证。将卡片信息直接发送到您的支付网关,然后处理其响应。如果您不首先进行Luhn检查之类的操作,这可以帮助支付网关检测欺诈行为--让他们看到失败的尝试。


12
唯一的问题在于每次交易都有成本。虽然可能很小,但它会累加起来,如果有人通过您的系统运行大量欺诈卡号,则费用可能变得不堪重负。 - John Conde

6

PHP 代码

function validateCC($cc_num, $type) {

    if($type == "American") {
    $denum = "American Express";
    } elseif($type == "Dinners") {
    $denum = "Diner's Club";
    } elseif($type == "Discover") {
    $denum = "Discover";
    } elseif($type == "Master") {
    $denum = "Master Card";
    } elseif($type == "Visa") {
    $denum = "Visa";
    }

    if($type == "American") {
    $pattern = "/^([34|37]{2})([0-9]{13})$/";//American Express
    if (preg_match($pattern,$cc_num)) {
    $verified = true;
    } else {
    $verified = false;
    }


    } elseif($type == "Dinners") {
    $pattern = "/^([30|36|38]{2})([0-9]{12})$/";//Diner's Club
    if (preg_match($pattern,$cc_num)) {
    $verified = true;
    } else {
    $verified = false;
    }


    } elseif($type == "Discover") {
    $pattern = "/^([6011]{4})([0-9]{12})$/";//Discover Card
    if (preg_match($pattern,$cc_num)) {
    $verified = true;
    } else {
    $verified = false;
    }


    } elseif($type == "Master") {
    $pattern = "/^([51|52|53|54|55]{2})([0-9]{14})$/";//Mastercard
    if (preg_match($pattern,$cc_num)) {
    $verified = true;
    } else {
    $verified = false;
    }


    } elseif($type == "Visa") {
    $pattern = "/^([4]{1})([0-9]{12,15})$/";//Visa
    if (preg_match($pattern,$cc_num)) {
    $verified = true;
    } else {
    $verified = false;
    }

    }

    if($verified == false) {
    //Do something here in case the validation fails
    echo "Credit card invalid. Please make sure that you entered a valid <em>" . $denum . "</em> credit card ";

    } else { //if it will pass...do something
    echo "Your <em>" . $denum . "</em> credit card is valid";
    }


}

使用方法

echo validateCC("1738292928284637", "Dinners");

更多理论信息可以在这里找到:

信用卡验证 - 校验位

校验和


1
该算法可以大致识别卡发行者,但它不会验证卡号是否合理。例如,它不使用 Luhn 算法检查校验和! - Ray Hayes

3

2

0

仅提供一些其他人可能会发现有用的代码片段(非PHP代码)。

PYTHON(单行代码;可能不太高效)

验证:

>>> not(sum(map(int, ''.join(str(n*(i%2+1)) for i, n in enumerate(map(int, reversed('1234567890123452'))))))%10)
True
>>> not(sum(map(int, ''.join(str(n*(i%2+1)) for i, n in enumerate(map(int, reversed('1234567890123451'))))))%10)
False

为了返回所需的校验位:

>>> (10-sum(map(int, ''.join(str(n*(i%2+1)) for i, n in enumerate(map(int, reversed('123456789012345')), start=1)))))%10
2
>>> (10-sum(map(int, ''.join(str(n*(i%2+1)) for i, n in enumerate(map(int, reversed('234567890123451')), start=1)))))%10
1

MySQL 函数

函数 "ccc" 和 "ccd"(信用卡检查和信用卡数字)

请注意,“ccc”函数有一个额外的检查,如果计算出的总和为0,则返回的结果将始终为FALSE,因此所有零CC号码将永远不会验证为正确(在正常情况下,它将正确验证)。根据具体要求,可以添加/删除此功能;可能很有用。

DROP FUNCTION IF EXISTS ccc;
DROP FUNCTION IF EXISTS ccd;

DELIMITER //

CREATE FUNCTION ccc (n TINYTEXT) RETURNS BOOL
BEGIN
  DECLARE x TINYINT UNSIGNED;
  DECLARE l TINYINT UNSIGNED DEFAULT length(n);
  DECLARE i TINYINT UNSIGNED DEFAULT l;
  DECLARE s SMALLINT UNSIGNED DEFAULT 0;
  WHILE i > 0 DO
    SET x = mid(n,i,1);
    IF (l-i) mod 2 = 1 THEN
      SET x = x * 2;
    END IF;
    SET s = s + x div 10 + x mod 10;
    SET i = i - 1;
  END WHILE;
  RETURN s != 0 && s mod 10 = 0;
END;

CREATE FUNCTION ccd (n TINYTEXT) RETURNS TINYINT
BEGIN
  DECLARE x TINYINT UNSIGNED;
  DECLARE l TINYINT UNSIGNED DEFAULT length(n);
  DECLARE i TINYINT UNSIGNED DEFAULT l;
  DECLARE s SMALLINT UNSIGNED DEFAULT 0;
  WHILE i > 0 DO
    SET x = mid(n,i,1);
    IF (l-i) mod 2 = 0 THEN
      SET x = x * 2;
    END IF;
    SET s = s + x div 10 + x mod 10;
    SET i = i - 1;
  END WHILE;
  RETURN ceil(s/10)*10-s;
END;

函数可以直接在SQL查询中使用:

mysql> SELECT ccc(1234567890123452);
+-----------------------+
| ccc(1234567890123452) |
+-----------------------+
|                     1 |
+-----------------------+
1 row in set (0.00 sec)

mysql> SELECT ccc(1234567890123451);
+-----------------------+
| ccc(1234567890123451) |
+-----------------------+
|                     0 |
+-----------------------+
1 row in set (0.00 sec)

mysql> SELECT ccd(123456789012345);
+----------------------+
| ccd(123456789012345) |
+----------------------+
|                    2 |
+----------------------+
1 row in set (0.00 sec)

mysql> SELECT ccd(234567890123451);
+----------------------+
| ccd(234567890123451) |
+----------------------+
|                    1 |
+----------------------+
1 row in set (0.00 sec)

0

https://github.com/sfyanbel/CreditCardValidation

使用这个软件包很简单。
    $testCreditCard = "43943418067844";
    $ob = new CreditCard();
    return $ob->getTypeCreditCard($testCreditCard);

你将获得以下预期数据:
['美国运通', 1],['Visa', 2],['万事达卡', 3],['Discover', 4]

-1

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接