/src/CreditCard.php
https://github.com/inacho/php-credit-card-validator · PHP · 228 lines · 186 code · 28 blank · 14 comment · 27 complexity · acbd8f8aaa30d8ead9b560f2d2c10eed MD5 · raw file
- <?php
- /**
- * Validates popular debit and credit cards numbers against regular expressions and Luhn algorithm.
- * Also validates the CVC and the expiration date.
- *
- * @author Ignacio de Tomás <nacho@inacho.es>
- * @copyright 2014 Ignacio de Tomás (http://inacho.es)
- */
- namespace Inacho;
- class CreditCard
- {
- protected static $cards = array(
- // Debit cards must come first, since they have more specific patterns than their credit-card equivalents.
- 'visaelectron' => array(
- 'type' => 'visaelectron',
- 'pattern' => '/^4(026|17500|405|508|844|91[37])/',
- 'length' => array(16),
- 'cvcLength' => array(3),
- 'luhn' => true,
- ),
- 'maestro' => array(
- 'type' => 'maestro',
- 'pattern' => '/^(5(018|0[23]|[68])|6(39|7))/',
- 'length' => array(12, 13, 14, 15, 16, 17, 18, 19),
- 'cvcLength' => array(3),
- 'luhn' => true,
- ),
- 'forbrugsforeningen' => array(
- 'type' => 'forbrugsforeningen',
- 'pattern' => '/^600/',
- 'length' => array(16),
- 'cvcLength' => array(3),
- 'luhn' => true,
- ),
- 'dankort' => array(
- 'type' => 'dankort',
- 'pattern' => '/^5019/',
- 'length' => array(16),
- 'cvcLength' => array(3),
- 'luhn' => true,
- ),
- // Credit cards
- 'visa' => array(
- 'type' => 'visa',
- 'pattern' => '/^4/',
- 'length' => array(13, 16),
- 'cvcLength' => array(3),
- 'luhn' => true,
- ),
- 'mastercard' => array(
- 'type' => 'mastercard',
- 'pattern' => '/^(5[0-5]|2[2-7])/',
- 'length' => array(16),
- 'cvcLength' => array(3),
- 'luhn' => true,
- ),
- 'amex' => array(
- 'type' => 'amex',
- 'pattern' => '/^3[47]/',
- 'format' => '/(\d{1,4})(\d{1,6})?(\d{1,5})?/',
- 'length' => array(15),
- 'cvcLength' => array(3, 4),
- 'luhn' => true,
- ),
- 'dinersclub' => array(
- 'type' => 'dinersclub',
- 'pattern' => '/^3[0689]/',
- 'length' => array(14),
- 'cvcLength' => array(3),
- 'luhn' => true,
- ),
- 'discover' => array(
- 'type' => 'discover',
- 'pattern' => '/^6([045]|22)/',
- 'length' => array(16),
- 'cvcLength' => array(3),
- 'luhn' => true,
- ),
- 'unionpay' => array(
- 'type' => 'unionpay',
- 'pattern' => '/^(62|88)/',
- 'length' => array(16, 17, 18, 19),
- 'cvcLength' => array(3),
- 'luhn' => false,
- ),
- 'jcb' => array(
- 'type' => 'jcb',
- 'pattern' => '/^35/',
- 'length' => array(16),
- 'cvcLength' => array(3),
- 'luhn' => true,
- ),
- );
- public static function validCreditCard($number, $type = null)
- {
- $ret = array(
- 'valid' => false,
- 'number' => '',
- 'type' => '',
- );
- // Strip non-numeric characters
- $number = preg_replace('/[^0-9]/', '', $number);
- if (empty($type)) {
- $type = self::creditCardType($number);
- }
- if (array_key_exists($type, self::$cards) && self::validCard($number, $type)) {
- return array(
- 'valid' => true,
- 'number' => $number,
- 'type' => $type,
- );
- }
- return $ret;
- }
- public static function validCvc($cvc, $type)
- {
- return (ctype_digit($cvc) && array_key_exists($type, self::$cards) && self::validCvcLength($cvc, $type));
- }
- public static function validDate($year, $month)
- {
- $month = str_pad($month, 2, '0', STR_PAD_LEFT);
- if (! preg_match('/^20\d\d$/', $year)) {
- return false;
- }
- if (! preg_match('/^(0[1-9]|1[0-2])$/', $month)) {
- return false;
- }
- // past date
- if ($year < date('Y') || $year == date('Y') && $month < date('m')) {
- return false;
- }
- return true;
- }
- // PROTECTED
- // ---------------------------------------------------------
- protected static function creditCardType($number)
- {
- foreach (self::$cards as $type => $card) {
- if (preg_match($card['pattern'], $number)) {
- return $type;
- }
- }
- return '';
- }
- protected static function validCard($number, $type)
- {
- return (self::validPattern($number, $type) && self::validLength($number, $type) && self::validLuhn($number, $type));
- }
- protected static function validPattern($number, $type)
- {
- return preg_match(self::$cards[$type]['pattern'], $number);
- }
- protected static function validLength($number, $type)
- {
- foreach (self::$cards[$type]['length'] as $length) {
- if (strlen($number) == $length) {
- return true;
- }
- }
- return false;
- }
- protected static function validCvcLength($cvc, $type)
- {
- foreach (self::$cards[$type]['cvcLength'] as $length) {
- if (strlen($cvc) == $length) {
- return true;
- }
- }
- return false;
- }
- protected static function validLuhn($number, $type)
- {
- if (! self::$cards[$type]['luhn']) {
- return true;
- } else {
- return self::luhnCheck($number);
- }
- }
- protected static function luhnCheck($number)
- {
- $checksum = 0;
- for ($i=(2-(strlen($number) % 2)); $i<=strlen($number); $i+=2) {
- $checksum += (int) ($number{$i-1});
- }
- // Analyze odd digits in even length strings or even digits in odd length strings.
- for ($i=(strlen($number)% 2) + 1; $i<strlen($number); $i+=2) {
- $digit = (int) ($number{$i-1}) * 2;
- if ($digit < 10) {
- $checksum += $digit;
- } else {
- $checksum += ($digit-9);
- }
- }
- if (($checksum % 10) == 0) {
- return true;
- } else {
- return false;
- }
- }
- }