Credit Card Validation

A PHP Class

Credit card companies (Visa, Master Card, AMEX, etc) each format their card numbers in specific ways. A quick check on the card number's format can tell you if that card number has the possibility of working with that merchant.
Some merchant accounts will charge a fee when you try to approve a transaction and it's declined due to an invalid card format ... even before they check to see if that account has available funds. This class helps prevent that by validating the card format before you try to approve it through the merchant.

What this does: Checks the format of the card number vs the card type.
What this does NOT do: Approve/deny the card based on the customer's account standing.

Usage:
require('CCValidate.php');

$cc = new CCValidate();

//validateNumber($card_type, $card_number)
$valid = $cc->validateNumber('Visa', 4561875422554412);

if ($valid == false) {
	// That's an invalid card number for Visa
}

Source:
/*************************************************************************
A PHP class to validate credit card number formats.

Created by Keith Duvall (http://www.duvalltech.com/ Copyright (C) Oct 2012

You may freely use, modify, and share this however you like -- personal or
commercial -- as long as you don't charge to distribute it.

Please give credit where credit is due.
*************************************************************************/
class CCValidate
{

	/**
	* An array to hold the valid prefixes and
	* valid lengths for various credit cards.
	*/
	private $card_specs = array(
		'Master Card' => array(
			'prefixes' => array(
				51,
				52,
				53,
				54,
				55
			),
			'lengths' => array(
				16
			)
		),

		'Visa' => array(
			'prefixes' => array(
				4
			),
			'lengths' => array(
				13,
				16
			)
		),

		'American Express' => array(
			'prefixes' => array(
				34,
				37
			),
			'lengths' => array(
				15
			)
		),

		// Adding a duplicate entry under the abbreviated name just to make it easier for some people
		'AMEX' => array(
			'prefixes' => array(
				34,
				37
			),
			'lengths' => array(
				15
			)
		),

		'Diners Club' => array(
			'prefixes' => array(
				300,
				301,
				302,
				303,
				304,
				305,
				36,
				38
			),
			'lengths' => array(
				14
			)
		),

		// Another name for Diners Club
		'Carte Blanche' => array(
			'prefixes' => array(
				300,
				301,
				302,
				303,
				304,
				305,
				36,
				38
			),
			'lengths' => array(
				14
			)
		),

		// Who the hell still uses Discover?
		'Discover' => array(
			'prefixes' => array(
				6011
			),
			'lengths' => array(
				16
			)
		),

		'enRoute' => array(
			'prefixes' => array(
				2014,
				2149
			),
			'lengths' => array(
				15
			)
		)
	);

	private $card_type;
	private $card_number;

	public function __construct() { }

	/**
	* Main function to validate a CC#
	*/
	public function validateNumber($card_type, $card_number)
	{
		$this->card_type = $card_type;
		$this->card_number = $card_number;

		if (!isset($this->card_specs[$this->card_type])) {
			return "'{$this->card_type}' is an unknown card type.";
		}

		// Remove any non-digit characters
		$this->card_number = preg_replace('/\D/', '', $this->card_number);

		if ($this->card_number == '') { // Must have had no digits in the "number"
			return false;
		}

		if (!$this->checkPrefix()) { return false; }
		if (!$this->checkLength()) { return false; }
		if (!$this->checkAlgorithm()) { return false; }

		return true;
	}

	/**
	* Checks to make sure the CC# has one of the valid prefixes for that card type.
	*/
	private function checkPrefix()
	{
		$valid_prefixes = $this->card_specs[$this->card_type]['prefixes'];

		foreach ($valid_prefixes as $prefix)
		{
			if (substr($this->card_number, 0, strlen($prefix)) == $prefix) { return true; }
		}

		return false;
	}

	/**
	* Makes sure the CC# length is one of the valid lengths for that card type.
	*/
	private function checkLength()
	{
		$valid_lengths = $this->card_specs[$this->card_type]['lengths'];
		foreach ($valid_lengths as $length)
		{
			if (strlen($this->card_number) == $length) { return true; }
		}

		return false;
	}

	/**
	* Makes sure the entire number is a valid account number format.
	*
	* First start from the right side of the number and multiply each
	* number by 2 -- skipping the right most digit and starting from the second to last.
	* If the product has more than one digit, that product will become the sum of it's digits.
	* IE: if the product is 18, that product will become 9 (1+8).
	* Then, starting from the left of the CC#, add the corresponding products to the CC digits that
	* you did not multiply earlier. Then add all the resulting sums. The result should be divisible by 10
	* with no remainder for valid CC#s. Otherwise, it's an invalid number and is guaranteed to be denied.
	*/
	private function checkAlgorithm()
	{
		$sums = array();
		$num = $this->card_number;

		// Create an array with each key containing one of the CC# digits
		// Reverse it to make it easier to work "right to left" on each digits.
		$each_num = array_reverse(str_split($num));
		foreach ($each_num as $index => $number)
		{
			// Skip the first digit and start with the next and every other one from there
			if ($index % 2) {
				$result = 0;
				$product = $number * 2; // Multiply by two
				$product = str_split($product); // Separate each digit
				foreach ($product as $digit)
				{
					$result += $digit; // Add individual digits to each other if there is more than one
				}
				$sums[] = $result;
				unset($each_num[$index]); // Remove this CC# digit from the array making it easier to tell which digits to use in the next step.
			}
		}

		$result = 0;
		// Reverse the resulting sums and the array containing the CC digits so it's easier to work "left to right" this time.
		$sums = array_reverse($sums);
		$each_num = array_reverse($each_num);
		foreach ($each_num as $key => $val)
		{
			// Add the digit to the sum from the prev step to the total so far
			$result += $val + $sums[$key];
		}

		// Check to see if the result is divisible by 10 with no remainder
		if (($result != 0) && ($result % 10 == 0)) {
			return true;
		}

		return false;

	}
}