diff --git a/lang/en.php b/lang/en.php index d2f3a0b..0a83aeb 100644 --- a/lang/en.php +++ b/lang/en.php @@ -25,7 +25,8 @@ return array( 'dateAfter' => "must be date after '%s'", 'contains' => "must contain %s", 'boolean' => "must be a boolean", - 'lengthBetween' => "must be between %d and %d characters" + 'lengthBetween' => "must be between %d and %d characters", + 'creditCard' => "must be a valid credit card number" ); diff --git a/src/Valitron/Validator.php b/src/Valitron/Validator.php index 6632a7c..d95cbc6 100644 --- a/src/Valitron/Validator.php +++ b/src/Valitron/Validator.php @@ -472,6 +472,107 @@ class Validator return (is_bool($value)) ? true : false; } + protected function validateCreditCard($field, $value, $params) + { + /** + * I there has been an array of valid cards supplied, or the name of the users card + * or the name and an array of valid cards + */ + if (!empty($params)) { + /** + * array of valid cards + */ + if (is_array($params[0])) { + $cards = $params[0]; + } else if (is_string($params[0])){ + $cardType = $params[0]; + if (isset($params[1]) && is_array($params[1])) { + $cards = $params[1]; + if (!in_array($cardType, $cards)) { + return false; + } + } + } + } + /** + * Luhn algorithm + * + * @return bool + */ + $numberIsValid = function () use ($value) { + $number = preg_replace('/[^0-9]+/', '', $value); + $sum = 0; + + $strlen = strlen($number); + if ($strlen < 13) { + return false; + } + for ($i = 0; $i < $strlen; $i++) { + $digit = (int) substr($number, $strlen - $i - 1, 1); + if ($i % 2 == 1) { + $sub_total = $digit * 2; + if ($sub_total > 9) { + $sub_total = ($sub_total - 10) + 1; + } + } else { + $sub_total = $digit; + } + $sum += $sub_total; + } + if ($sum > 0 && $sum % 10 == 0) { + return true; + } + return false; + }; + + if ($numberIsValid()) { + if (!isset($cards)) { + return true; + } else { + $cardRegex = array( + 'visa' => '#^4[0-9]{12}(?:[0-9]{3})?$#', + 'mastercard' => '#^5[1-5][0-9]{14}$#', + 'amex' => '#^3[47][0-9]{13}$#', + 'dinersclub' => '#^3(?:0[0-5]|[68][0-9])[0-9]{11}$#', + 'discover' => '#^6(?:011|5[0-9]{2})[0-9]{12}$#', + ); + + if (isset($cardType)) { + // if we don't have any valid cards specified and the card we've been given isn't in our regex array + if (!isset($cards) && !in_array($cardType, array_keys($cardRegex))) { + return false; + } + + // we only need to test against one card type + return (preg_match($cardRegex[$cardType], $value) === 1); + + } else if (isset($cards)) { + // if we have cards, check our users card against only the ones we have + foreach($cards as $card) { + if (in_array($card, array_keys($cardRegex))) { + // if the card is valid, we want to stop looping + if (preg_match($cardRegex[$card], $value) === 1) { + return true; + } + } + } + } else { + // loop through every card + foreach($cardRegex as $regex) { + // until we find a valid one + if (preg_match($regex, $value) === 1) { + return true; + } + } + } + } + } + + // if we've got this far, the card has passed no validation so it's invalid! + return false; + } + + /** * Get array of fields and data */ diff --git a/tests/Valitron/ValidateTest.php b/tests/Valitron/ValidateTest.php index dce9829..f66e61a 100644 --- a/tests/Valitron/ValidateTest.php +++ b/tests/Valitron/ValidateTest.php @@ -654,6 +654,58 @@ class ValidateTest extends BaseTestCase $v->rule('boolean', 'test'); $this->assertFalse($v->validate()); } + + public function testCreditCardValid() + { + $visa = [4539511619543489, 4532949059629052, 4024007171194938, 4929646403373269, 4539135861690622]; + $mastercard = [5162057048081965, 5382687859049349, 5484388880142230, 5464941521226434, 5473481232685965]; + $amex = [371442067262027, 340743030537918, 345509167493596, 343665795576848, 346087552944316]; + $dinersclub = [30363194756249, 30160097740704, 38186521192206, 38977384214552, 38563220301454]; + $discover = [6011712400392605, 6011536340491809, 6011785775263015, 6011984124619056, 6011320958064251]; + + foreach (compact('visa', 'mastercard', 'amex', 'dinersclub', 'discover') as $type => $numbers) { + foreach($numbers as $number) { + $v = new Validator(array('test' => $number)); + $v->rule('creditCard', 'test'); + $this->assertTrue($v->validate()); + $v->rule('creditCard', 'test', [$type, 'mastercard', 'visa']); + $this->assertTrue($v->validate()); + $v->rule('creditCard', 'test', $type); + $this->assertTrue($v->validate()); + $v->rule('creditCard', 'test', $type, [$type, 'mastercard', 'visa']); + $this->assertTrue($v->validate()); + unset($d); + } + } + } + + public function testcreditCardInvalid() + { + $visa = [3539511619543489, 3532949059629052, 3024007171194938, 3929646403373269, 3539135861690622]; + $mastercard = [4162057048081965, 4382687859049349, 4484388880142230, 4464941521226434, 4473481232685965]; + $amex = [271442067262027, 240743030537918, 245509167493596, 243665795576848, 246087552944316]; + $dinersclub = [20363194756249, 20160097740704, 28186521192206, 28977384214552, 28563220301454]; + $discover = [5011712400392605, 5011536340491809, 5011785775263015, 5011984124619056, 5011320958064251]; + + foreach (compact('visa', 'mastercard', 'amex', 'dinersclub', 'discover') as $type => $numbers) { + foreach($numbers as $number) { + $v = new Validator(array('test' => $number)); + $v->rule('creditCard', 'test'); + $this->assertFalse($v->validate()); + $v->rule('creditCard', 'test', [$type, 'mastercard', 'visa']); + $this->assertFalse($v->validate()); + $v->rule('creditCard', 'test', $type); + $this->assertFalse($v->validate()); + $v->rule('creditCard', 'test', $type, [$type, 'mastercard', 'visa']); + $this->assertFalse($v->validate()); + $v->rule('creditCard', 'test', 'invalidCardName'); + $this->assertFalse($v->validate()); + $v->rule('creditCard', 'test', 'invalidCardName', ['invalidCardName', 'mastercard', 'visa']); + $this->assertFalse($v->validate()); + unset($d); + } + } + } } function sampleFunctionCallback($field, $value, array $params) {