What is the best way to validate a credit card in PHP?
Asked Answered
P

9

73

Given a credit card number and no additional information, what is the best way in PHP to determine whether or not it is a valid number?

Right now I need something that will work with American Express, Discover, MasterCard, and Visa, but it might be helpful if it will also work with other types.

Poacher answered 6/10, 2008 at 15:21 Comment(0)
L
155

There are three parts to the validation of the card number:

  1. PATTERN - does it match an issuers pattern (e.g. VISA/Mastercard/etc.)
  2. CHECKSUM - does it actually check-sum (e.g. not just 13 random numbers after "34" to make it an AMEX card number)
  3. REALLY EXISTS - does it actually have an associated account (you are unlikely to get this without a merchant account)

Pattern

  • MASTERCARD Prefix=51-55, Length=16 (Mod10 checksummed)
  • VISA Prefix=4, Length=13 or 16 (Mod10)
  • AMEX Prefix=34 or 37, Length=15 (Mod10)
  • Diners Club/Carte Prefix=300-305, 36 or 38, Length=14 (Mod10)
  • Discover Prefix=6011,622126-622925,644-649,65, Length=16, (Mod10)
  • etc. (detailed list of prefixes)

Checksum

Most cards use the Luhn algorithm for checksums:

Luhn Algorithm described on Wikipedia

There are links to many implementations on the Wikipedia link, including 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;

}
?>
Lemuel answered 6/10, 2008 at 15:25 Comment(10)
Under pattern, you could added "Discover", prefix = "6" (maybe "60"), Length=16Mainz
MasterCard has a prefix of 51-55 not 51 or 55 according to beachnet.com/~hstiles/cardtype.htmlDiffusive
This function removes all non-digits from the string, so the card number "FRED" is valid. Make sure that you validated the card number has only digits before calling this function!Snow
Good call Matt Connolly. You could just check if $number_length is the right size (or > 0 for that matter) inside the function too.Splashboard
Your MasterCard prefix does not include 3 prefixes which are valid. 52, 53, and 54.Vancevancleave
A credit card number composed as nothing but 0s comes up as valid as wellGredel
Thank you so much mate, this function still works! I tried one from ELO, but it wasn't validating the Amex cards, but this one does!Erhart
How to check part - 3? REALLY EXISTSHyaline
@BijuPDais as mentioned in the text - to check if it really exists, you're likely to need to be a merchant and actually try to bill the card. Many actions (such as hotels) charge then refund a small amount onto the credit card. Of all the methods of validating - that's the only TRUE test of whether a card is valid!Lemuel
@Ray Hayes, thanks for the feedback. This is what I did. I did a 'test mode' transaction. If the card number is incorrect, I will get an error message. The only problem is that I am unable to validate if CVV, expiry dates entered are correct or not. Meaning, I am only able to check the validity of the card number.Hyaline
B
30

From 10 regular expressions you can't live without in 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;
}

Sample input:

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

This gives us

4111 1111 1111 1111 - Visa
Bagwell answered 6/10, 2008 at 15:31 Comment(2)
Where is validatecard function?Guillerminaguillermo
This answer will fail for new mastercard 2-series identification mastercard.us/content/mccom/en-us/issuers/get-support/…Reticulation
S
14

It's probably better NOT to validate in code at your end. Send the card info right over to your payment gateway and then deal with their response. It helps them detect fraud if you don't do anything like Luhn checking first -- let them see the failed attempts.

Strengthen answered 2/3, 2009 at 18:6 Comment(1)
The only problem with this is there is a cost for each transaction. It may be small but it adds up and if someone is running a large number of fraudulent card numbers through your system the fees can become overwhelming.Basia
G
6

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

More theoric information can be found here:

Credit Card Validation - Check Digits

Checksum

Gripping answered 6/10, 2008 at 15:25 Comment(1)
That algorithm may identify the card issuer, roughly, it doesn't validate whether the number makes sense.. e.g. it doesn't check the checksum using the Luhn algorithm!Lemuel
L
3

The luhn algorithm is a checksum that can used to validate the format of a lot of credit card formats (and also Canadian social insurance numbers...)

The wikipedia article also links to many different implementations; here's a PHP one:

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

Lipoma answered 6/10, 2008 at 15:27 Comment(0)
S
2

There is a PEAR package which handles the validation of many financial numbers, also credit card validation: http://pear.php.net/package/Validate_Finance_CreditCard

By the way, here are some Test Credit Card Account Numbers by PayPal.

Seaweed answered 7/10, 2008 at 11:56 Comment(0)
C
0

Just throwing in some further code snippets that others may find useful (not PHP code).

PYTHON (single line code; probably not that efficient)

To validate:

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

To return the required check digit:

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

Functions "ccc" and "ccd" (credit-card-check and credit-card-digit)

Note that the "ccc" function has an additional check where if the calculated sum is 0, the returned result will always be FALSE, so an all zero CC number will never validate as being correct (under normal behaviour, it would validate correctly). This feature can be added/removed as required; maybe useful, depending on specific requirements.

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;

Functions can then be used directly in SQL queries:

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)
Cavie answered 12/12, 2014 at 14:28 Comment(0)
D
0

https://github.com/sfyanbel/CreditCardValidation

Use this package it is easy

    $testCreditCard = "43943418067844";
    $ob = new CreditCard();
    return $ob->getTypeCreditCard($testCreditCard);

You will get this expected data

['AmericanExpress',1], ['Visa',2], ['MasterCards',3], ['Discover',4]

Dori answered 20/10, 2023 at 22:32 Comment(0)
H
-1

This is only to make sure that the numbers are valid using some basic RegEX patterns.

Note, this does not check to see if the numbers are in-use by someone.

http://www.roscripts.com/How_to_validate_credit_card_numbers-106.html

Hewart answered 6/10, 2008 at 15:23 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.