From 2d0790b984ed495546a702eb38a34839838657e0 Mon Sep 17 00:00:00 2001 From: Andrew Willis Date: Thu, 9 Jan 2014 22:05:23 +0000 Subject: [PATCH 1/8] Added credit card validation for Visa, Mastercard, American Express, Dinersclub and Discover --- lang/en.php | 3 +- src/Valitron/Validator.php | 101 ++++++++++++++++++++++++++++++++ tests/Valitron/ValidateTest.php | 52 ++++++++++++++++ 3 files changed, 155 insertions(+), 1 deletion(-) 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) { From 322df8b0210255951a28c9a798bf0f927f4efaef Mon Sep 17 00:00:00 2001 From: Andrew Willis Date: Thu, 9 Jan 2014 22:18:29 +0000 Subject: [PATCH 2/8] added docblock and readme docs for using the new filter --- README.md | 34 ++++++++++++++++++++++++++++++++++ src/Valitron/Validator.php | 9 +++++++++ 2 files changed, 43 insertions(+) diff --git a/README.md b/README.md index 62a0641..54cde9f 100644 --- a/README.md +++ b/README.md @@ -109,6 +109,40 @@ V::lang('ar'); * `dateBefore` - Field is a valid date and is before the given date * `dateAfter` - Field is a valid date and is after the given date * `contains` - Field is a string and contains the given string + * `creditCard` - Field is a valid credit card number + + ## Credit Card Validation usage + + Credit card validation currently allows you to validate a Visa `visa`, + Mastercard `mastercard`, Dinersclub `dinersclub`, American Express `amex` + or Discovery `discovery` + + This will check the credit card against each card type +```php +$v->rule('creditCard', 'credit_card'); +``` + +To optionally filter card types, add the slug to an array as the next parameter: + +```php +$v->rule('creditCard', 'credit_card', ['visa', 'mastercard']); +``` + +If you only want to validate one type of card, put it as a string: + +```php +$v->rule('creditCard', 'credit_card', 'visa'); +``` + +If the card type information is coming from the client, you might also want to +still specify an array of valid card types: + +```php +$cardType = 'amex'; +$v->rule('creditCard', 'credit_card', $cardType, ['visa', 'mastercard']); +$v->validate(); // false +``` + ## Adding Custom Validation Rules diff --git a/src/Valitron/Validator.php b/src/Valitron/Validator.php index d95cbc6..cea357b 100644 --- a/src/Valitron/Validator.php +++ b/src/Valitron/Validator.php @@ -472,6 +472,15 @@ class Validator return (is_bool($value)) ? true : false; } + /** + * Validate that a field contains a valid credit card + * optionally filtered by an array + * + * @param string $field + * @param string $value + * @param array $params + * @return bool + */ protected function validateCreditCard($field, $value, $params) { /** From da76fde359216018f62635818446490b76dfcb96 Mon Sep 17 00:00:00 2001 From: Andrew Willis Date: Thu, 9 Jan 2014 22:20:04 +0000 Subject: [PATCH 3/8] =?UTF-8?q?oops=E2=80=A6=20(newline)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 54cde9f..bab6d21 100644 --- a/README.md +++ b/README.md @@ -111,6 +111,7 @@ V::lang('ar'); * `contains` - Field is a string and contains the given string * `creditCard` - Field is a valid credit card number + ## Credit Card Validation usage Credit card validation currently allows you to validate a Visa `visa`, From efba2d8cf1f2f70ad211a1db237df4717409246e Mon Sep 17 00:00:00 2001 From: Andrew Willis Date: Thu, 9 Jan 2014 22:22:00 +0000 Subject: [PATCH 4/8] =?UTF-8?q?oops=E2=80=A6=20(newline=20/=20whitespace)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index bab6d21..3ea7552 100644 --- a/README.md +++ b/README.md @@ -112,13 +112,14 @@ V::lang('ar'); * `creditCard` - Field is a valid credit card number - ## Credit Card Validation usage +## Credit Card Validation usage - Credit card validation currently allows you to validate a Visa `visa`, - Mastercard `mastercard`, Dinersclub `dinersclub`, American Express `amex` - or Discovery `discovery` +Credit card validation currently allows you to validate a Visa `visa`, +Mastercard `mastercard`, Dinersclub `dinersclub`, American Express `amex` +or Discovery `discovery` + +This will check the credit card against each card type - This will check the credit card against each card type ```php $v->rule('creditCard', 'credit_card'); ``` From 7b37331c58933614b660e3c7ab9a0c204b950aef Mon Sep 17 00:00:00 2001 From: Andrew Willis Date: Thu, 9 Jan 2014 23:22:44 +0000 Subject: [PATCH 5/8] fixed a typo --- tests/Valitron/ValidateTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Valitron/ValidateTest.php b/tests/Valitron/ValidateTest.php index f66e61a..883fac2 100644 --- a/tests/Valitron/ValidateTest.php +++ b/tests/Valitron/ValidateTest.php @@ -702,7 +702,7 @@ class ValidateTest extends BaseTestCase $this->assertFalse($v->validate()); $v->rule('creditCard', 'test', 'invalidCardName', ['invalidCardName', 'mastercard', 'visa']); $this->assertFalse($v->validate()); - unset($d); + unset($v); } } } From f26a1f55b4274d739c741e13cd42eef7b7b7d0fb Mon Sep 17 00:00:00 2001 From: Andrew Willis Date: Fri, 10 Jan 2014 06:54:10 +0000 Subject: [PATCH 6/8] fixed indentation and php5.3 array syntax --- src/Valitron/Validator.php | 42 ++++++++++++++++----------------- tests/Valitron/ValidateTest.php | 24 +++++++++---------- 2 files changed, 33 insertions(+), 33 deletions(-) diff --git a/src/Valitron/Validator.php b/src/Valitron/Validator.php index cea357b..d92e2e6 100644 --- a/src/Valitron/Validator.php +++ b/src/Valitron/Validator.php @@ -509,29 +509,29 @@ class Validator * @return bool */ $numberIsValid = function () use ($value) { - $number = preg_replace('/[^0-9]+/', '', $value); - $sum = 0; + $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; + $strlen = strlen($number); + if ($strlen < 13) { + return false; } - $sum += $sub_total; - } - if ($sum > 0 && $sum % 10 == 0) { - return true; - } - 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()) { diff --git a/tests/Valitron/ValidateTest.php b/tests/Valitron/ValidateTest.php index f66e61a..b13aa9c 100644 --- a/tests/Valitron/ValidateTest.php +++ b/tests/Valitron/ValidateTest.php @@ -657,11 +657,11 @@ class ValidateTest extends BaseTestCase 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]; + $visa = array(4539511619543489, 4532949059629052, 4024007171194938, 4929646403373269, 4539135861690622); + $mastercard = array(5162057048081965, 5382687859049349, 5484388880142230, 5464941521226434, 5473481232685965); + $amex = array(371442067262027, 340743030537918, 345509167493596, 343665795576848, 346087552944316); + $dinersclub = array(30363194756249, 30160097740704, 38186521192206, 38977384214552, 38563220301454); + $discover = array(6011712400392605, 6011536340491809, 6011785775263015, 6011984124619056, 6011320958064251); foreach (compact('visa', 'mastercard', 'amex', 'dinersclub', 'discover') as $type => $numbers) { foreach($numbers as $number) { @@ -674,18 +674,18 @@ class ValidateTest extends BaseTestCase $this->assertTrue($v->validate()); $v->rule('creditCard', 'test', $type, [$type, 'mastercard', 'visa']); $this->assertTrue($v->validate()); - unset($d); + unset($v); } } } 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]; + $visa = array(3539511619543489, 3532949059629052, 3024007171194938, 3929646403373269, 3539135861690622); + $mastercard = array(4162057048081965, 4382687859049349, 4484388880142230, 4464941521226434, 4473481232685965); + $amex = array(271442067262027, 240743030537918, 245509167493596, 243665795576848, 246087552944316); + $dinersclub = array(20363194756249, 20160097740704, 28186521192206, 28977384214552, 28563220301454); + $discover = array(5011712400392605, 5011536340491809, 5011785775263015, 5011984124619056, 5011320958064251); foreach (compact('visa', 'mastercard', 'amex', 'dinersclub', 'discover') as $type => $numbers) { foreach($numbers as $number) { @@ -702,7 +702,7 @@ class ValidateTest extends BaseTestCase $this->assertFalse($v->validate()); $v->rule('creditCard', 'test', 'invalidCardName', ['invalidCardName', 'mastercard', 'visa']); $this->assertFalse($v->validate()); - unset($d); + unset($v); } } } From d7ec85ef48ed94a658a124db8f9ff775737624fd Mon Sep 17 00:00:00 2001 From: Andrew Willis Date: Fri, 10 Jan 2014 06:58:18 +0000 Subject: [PATCH 7/8] and again with the array thing making valid for php5.3 --- tests/Valitron/ValidateTest.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/Valitron/ValidateTest.php b/tests/Valitron/ValidateTest.php index b13aa9c..d1ed886 100644 --- a/tests/Valitron/ValidateTest.php +++ b/tests/Valitron/ValidateTest.php @@ -668,11 +668,11 @@ class ValidateTest extends BaseTestCase $v = new Validator(array('test' => $number)); $v->rule('creditCard', 'test'); $this->assertTrue($v->validate()); - $v->rule('creditCard', 'test', [$type, 'mastercard', 'visa']); + $v->rule('creditCard', 'test', array($type, 'mastercard', 'visa')); $this->assertTrue($v->validate()); $v->rule('creditCard', 'test', $type); $this->assertTrue($v->validate()); - $v->rule('creditCard', 'test', $type, [$type, 'mastercard', 'visa']); + $v->rule('creditCard', 'test', $type, array($type, 'mastercard', 'visa')); $this->assertTrue($v->validate()); unset($v); } @@ -692,15 +692,15 @@ class ValidateTest extends BaseTestCase $v = new Validator(array('test' => $number)); $v->rule('creditCard', 'test'); $this->assertFalse($v->validate()); - $v->rule('creditCard', 'test', [$type, 'mastercard', 'visa']); + $v->rule('creditCard', 'test', array($type, 'mastercard', 'visa')); $this->assertFalse($v->validate()); $v->rule('creditCard', 'test', $type); $this->assertFalse($v->validate()); - $v->rule('creditCard', 'test', $type, [$type, 'mastercard', 'visa']); + $v->rule('creditCard', 'test', $type, array($type, 'mastercard', 'visa')); $this->assertFalse($v->validate()); $v->rule('creditCard', 'test', 'invalidCardName'); $this->assertFalse($v->validate()); - $v->rule('creditCard', 'test', 'invalidCardName', ['invalidCardName', 'mastercard', 'visa']); + $v->rule('creditCard', 'test', 'invalidCardName', array('invalidCardName', 'mastercard', 'visa')); $this->assertFalse($v->validate()); unset($v); } From 8d29868ee7fc004b606ca139277d6f1c75c6f2a3 Mon Sep 17 00:00:00 2001 From: Andrew Willis Date: Fri, 10 Jan 2014 19:38:22 +0000 Subject: [PATCH 8/8] discover -y --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3ea7552..550bfce 100644 --- a/README.md +++ b/README.md @@ -116,7 +116,7 @@ V::lang('ar'); Credit card validation currently allows you to validate a Visa `visa`, Mastercard `mastercard`, Dinersclub `dinersclub`, American Express `amex` -or Discovery `discovery` +or Discover `discover` This will check the credit card against each card type