web-dev-qa-db-fra.com

Quel est le meilleur moyen de valider une carte de crédit en PHP?

Étant donné un numéro de carte de crédit et aucune information supplémentaire, quel est le meilleur moyen, dans PHP, de déterminer s'il s'agit d'un numéro valide?

Pour le moment, j'ai besoin de quelque chose qui fonctionne avec American Express, Discover, MasterCard et Visa, mais il serait peut-être utile que cela fonctionne également avec d'autres types.

66
Joe Lencioni

La validation du numéro de carte comporte trois parties:

  1. MODÈLE - correspond-il à un modèle d'émetteur (par exemple, VISA/Mastercard/etc.)
  2. CHECKSUM - vérifie-t-il réellement la somme (par exemple, pas seulement 13 nombres aléatoires après "34" pour en faire un numéro de carte AMEX)
  3. REALY EXISTS - dispose-t-il d'un compte associé (il est peu probable que vous l'obteniez sans compte marchand)

Modèle

  • MASTERCARD Préfixe = 51-55, Longueur = 16 (mod10 vérifiés)
  • Préfixe VISA = 4, Longueur = 13 ou 16 (Mod10)
  • Préfixe AMEX = 34 ou 37, Longueur = 15 (Mod10)
  • Préfixe Carte/Diners Club = 300-305, 36 ou 38, Longueur = 14 (Mod10)
  • Préfixe de découverte = 6011,622126-622925,644-649,65, longueur = 16, (Mod10)
  • etc. ( liste détaillée des préfixes )

Somme de contrôle

La plupart des cartes utilisent l'algorithme de Luhn pour les sommes de contrôle:

L'algorithme de Luhn décrit sur Wikipedia

Il existe des liens vers de nombreuses implémentations sur le lien Wikipedia, y compris 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;

}
?>
143
Ray Hayes

De 10 expressions régulières sans lesquelles vous ne pouvez pas vivre dans 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;
}

Exemple de saisie:

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

Cela nous donne

4111 1111 1111 1111 - Visa 
26
ConroyP

Il est probablement préférable de ne pas valider le code de votre côté. Envoyez les informations de la carte directement à votre passerelle de paiement, puis traitez leur réponse. Cela les aide à détecter les fraudes si vous ne faites rien comme Luhn en premier - laissez-les voir les tentatives qui ont échoué.

12
PartialOrder

PHP Code

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


}

Usage

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

Plus d'informations théoriques peuvent être trouvées ici:

Validation de la carte de crédit - Vérifier les chiffres

Checksum

6

Nous pouvons utiliser les éléments suivants pour valider la carte de crédit. Cela fonctionne parfaitement pour moi.

protected function luhn($number)
{
    // Force the value to be a string as this method uses string functions.
    // Converting to an integer may pass PHP_INT_MAX and result in an error!
    $number = (string)$number;

    if (!ctype_digit($number)) {
        // Luhn can only be used on numbers!
        return FALSE;
    }

    // Check number length
    $length = strlen($number);

    // Checksum of the card number
    $checksum = 0;

    for ($i = $length - 1; $i >= 0; $i -= 2) {
        // Add up every 2nd digit, starting from the right
        $checksum += substr($number, $i, 1);
    }

    for ($i = $length - 2; $i >= 0; $i -= 2) {
        // Add up every 2nd digit doubled, starting from the right
        $double = substr($number, $i, 1) * 2;

        // Subtract 9 from the double where value is greater than 10
        $checksum += ($double >= 10) ? ($double - 9) : $double;
    }

    // If the checksum is a multiple of 10, the number is valid
    return ($checksum % 10 === 0);
}

protected function ValidCreditcard($number)
{
    $card_array = array(
        'default' => array(
            'length' => '13,14,15,16,17,18,19',
            'prefix' => '',
            'luhn' => TRUE,
        ),
        'american express' => array(
            'length' => '15',
            'prefix' => '3[47]',
            'luhn' => TRUE,
        ),
        'diners club' => array(
            'length' => '14,16',
            'prefix' => '36|55|30[0-5]',
            'luhn' => TRUE,
        ),
        'discover' => array(
            'length' => '16',
            'prefix' => '6(?:5|011)',
            'luhn' => TRUE,
        ),
        'jcb' => array(
            'length' => '15,16',
            'prefix' => '3|1800|2131',
            'luhn' => TRUE,
        ),
        'maestro' => array(
            'length' => '16,18',
            'prefix' => '50(?:20|38)|6(?:304|759)',
            'luhn' => TRUE,
        ),
        'mastercard' => array(
            'length' => '16',
            'prefix' => '5[1-5]',
            'luhn' => TRUE,
        ),
        'visa' => array(
            'length' => '13,16',
            'prefix' => '4',
            'luhn' => TRUE,
        ),
    );

    // Remove all non-digit characters from the number
    if (($number = preg_replace('/\D+/', '', $number)) === '')
        return FALSE;

    // Use the default type
    $type = 'default';

    $cards = $card_array;

    // Check card type
    $type = strtolower($type);

    if (!isset($cards[$type]))
        return FALSE;

    // Check card number length
    $length = strlen($number);

    // Validate the card length by the card type
    if (!in_array($length, preg_split('/\D+/', $cards[$type]['length'])))
        return FALSE;

    // Check card number prefix
    if (!preg_match('/^' . $cards[$type]['prefix'] . '/', $number))
        return FALSE;

    // No Luhn check required
    if ($cards[$type]['luhn'] == FALSE)
        return TRUE;

    return $this->luhn($number);

}
5
jeeva

L'algorithme luhn est une somme de contrôle qui permet de valider le format d'un grand nombre de formats de cartes de crédit (ainsi que les numéros d'assurance sociale canadiens ...).

L'article de Wikipédia est également lié à de nombreuses implémentations différentes; en voici un PHP:

http://planzero.org/code/bits/viewcode.php?src=luhn_check.phps

3
Dana

Il existe un package PEAR qui gère la validation de nombreux numéros financiers, ainsi que la validation de carte de crédit: http://pear.php.net/package/Validate_Finance_CreditCard

À propos, voici quelques Test Numéros de compte de carte de crédit par Paypal.

2
powtac

Ajoutez simplement des extraits de code supplémentaires que d'autres pourraient trouver utiles (pas du code PHP).

PYTHON(code à une seule ligne; probablement pas aussi efficace)

Valider:

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

Pour renvoyer le chiffre de contrôle requis:

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

Fonctions MySQL

Fonctions "ccc" et "ccd" (chèque de carte de crédit et chiffre de carte de crédit)

Notez que la fonction "ccc" a une vérification supplémentaire où, si la somme calculée est 0, le résultat retourné sera toujours FAUX, donc un nombre zéro sur zéro ne sera jamais validé comme étant correct (dans des conditions normales, il serait validé correctement) Cette fonctionnalité peut être ajoutée/supprimée si nécessaire. peut-être utile, selon les besoins spécifiques.

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;

Les fonctions peuvent ensuite être utilisées directement dans les requêtes 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
parkamark