mirror of
https://github.com/Respect/Validation.git
synced 2024-06-04 23:02:16 +02:00
Apply contribution guidelines to "CreditCard" rule
Co-Authored-By: Henrique Moody <henriquemoody@gmail.com>
This commit is contained in:
parent
a79e702173
commit
8bd7428fe1
|
@ -7,25 +7,28 @@ Validates a credit card number.
|
|||
|
||||
```php
|
||||
v::creditCard()->validate('5376 7473 9720 8720'); // true
|
||||
v::creditCard()->validate('5376-7473-9720-8720'); // true
|
||||
v::creditCard()->validate('5376.7473.9720.8720'); // true
|
||||
|
||||
v::creditCard('American Express')->validate('340316193809364'); // true
|
||||
v::creditCard('Diners Club')->validate('30351042633884'); // true
|
||||
v::creditCard('American_Express')->validate('340316193809364'); // true
|
||||
v::creditCard('Diners_Club')->validate('30351042633884'); // true
|
||||
v::creditCard('Discover')->validate('6011000990139424'); // true
|
||||
v::creditCard('JCB')->validate('3566002020360505'); // true
|
||||
v::creditCard('MasterCard')->validate('5376747397208720'); // true
|
||||
v::creditCard('Mastercard')->validate('5376747397208720'); // true
|
||||
v::creditCard('Visa')->validate('4024007153361885'); // true
|
||||
```
|
||||
|
||||
The current supported brands are:
|
||||
|
||||
- American Express
|
||||
- Diners Club
|
||||
- Discover
|
||||
- JCB
|
||||
- MasterCard
|
||||
- Visa
|
||||
- American Express (`'American_Express'` or `CreditCard::AMERICAN_EXPRESS`)
|
||||
- Diners Club (`'Diners_Club'` or `CreditCard::DINERS_CLUB`)
|
||||
- Discover (`'Discover'` or `CreditCard::DISCOVER`)
|
||||
- JCB (`'JCB'` or `CreditCard::JCB`)
|
||||
- Mastercard (`'American_Express'` or `CreditCard::MASTERCARD`)
|
||||
- Visa (`'Visa'` or `CreditCard::VISA`)
|
||||
|
||||
It ignores any non-digit chars, so use `->digit()` when appropriate.
|
||||
It ignores any non-numeric characters, use [Digit](Digit.md),
|
||||
[NoWhitespace](NoWhitespace.md), or [Regex](Regex.md) when appropriate.
|
||||
|
||||
```php
|
||||
v::digit()->creditCard()->validate('5376747397208720'); // true
|
||||
|
@ -41,4 +44,6 @@ Version | Description
|
|||
***
|
||||
See also:
|
||||
|
||||
- [Digit](Digit.md)
|
||||
- [Luhn](Luhn.md)
|
||||
- [NoWhitespace](NoWhitespace.md)
|
||||
|
|
|
@ -13,10 +13,20 @@ declare(strict_types=1);
|
|||
|
||||
namespace Respect\Validation\Exceptions;
|
||||
|
||||
class CreditCardException extends ValidationException
|
||||
use Respect\Validation\Rules\CreditCard;
|
||||
|
||||
/**
|
||||
* @author Henrique Moody <henriquemoody@gmail.com>
|
||||
* @author Jean Pimentel <jeanfap@gmail.com>
|
||||
* @author William Espindola <oi@williamespindola.com.br>
|
||||
*/
|
||||
final class CreditCardException extends ValidationException
|
||||
{
|
||||
public const BRANDED = 'branded';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $defaultTemplates = [
|
||||
self::MODE_DEFAULT => [
|
||||
self::STANDARD => '{{name}} must be a valid Credit Card number',
|
||||
|
@ -28,9 +38,12 @@ class CreditCardException extends ValidationException
|
|||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function chooseTemplate(): string
|
||||
{
|
||||
if (!$this->getParam('brand')) {
|
||||
if (CreditCard::ANY === $this->getParam('brand')) {
|
||||
return static::STANDARD;
|
||||
}
|
||||
|
||||
|
|
|
@ -14,25 +14,64 @@ declare(strict_types=1);
|
|||
namespace Respect\Validation\Rules;
|
||||
|
||||
use Respect\Validation\Exceptions\ComponentException;
|
||||
use function array_keys;
|
||||
use function implode;
|
||||
use function is_scalar;
|
||||
use function preg_match;
|
||||
use function preg_replace;
|
||||
|
||||
class CreditCard extends AbstractRule
|
||||
/**
|
||||
* Validates whether the input is a credit card number.
|
||||
*
|
||||
* @author Andy Snell <andysnell@gmail.com>
|
||||
* @author Henrique Moody <henriquemoody@gmail.com>
|
||||
* @author Jean Pimentel <jeanfap@gmail.com>
|
||||
* @author Alexander <mazanax@yandex.ru>
|
||||
* @author Nick Lombard <github@jigsoft.co.za>
|
||||
* @author William Espindola <oi@williamespindola.com.br>
|
||||
*/
|
||||
final class CreditCard extends AbstractRule
|
||||
{
|
||||
public const AMERICAN_EXPRESS = 'American Express';
|
||||
public const DINERS_CLUB = 'Diners Club';
|
||||
public const DISCOVER = 'Discover';
|
||||
public const JCB = 'JCB';
|
||||
public const MASTERCARD = 'MasterCard';
|
||||
public const VISA = 'Visa';
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public const ANY = 'Any';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $brand;
|
||||
public const AMERICAN_EXPRESS = 'American Express';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public const DINERS_CLUB = 'Diners Club';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public const DISCOVER = 'Discover';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public const JCB = 'JCB';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public const MASTERCARD = 'MasterCard';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public const VISA = 'Visa';
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $brands = [
|
||||
private const BRAND_REGEX_LIST = [
|
||||
self::ANY => '/^[0-9]+$/',
|
||||
self::AMERICAN_EXPRESS => '/^3[47]\d{13}$/',
|
||||
self::DINERS_CLUB => '/^3(?:0[0-5]|[68]\d)\d{11}$/',
|
||||
self::DISCOVER => '/^6(?:011|5\d{2})\d{12}$/',
|
||||
|
@ -42,14 +81,27 @@ class CreditCard extends AbstractRule
|
|||
];
|
||||
|
||||
/**
|
||||
* @param string $brand Optional credit card brand
|
||||
* @var string
|
||||
*/
|
||||
public function __construct($brand = null)
|
||||
private $brand;
|
||||
|
||||
/**
|
||||
* Initializes the rule.
|
||||
*
|
||||
* @param string $brand
|
||||
*
|
||||
* @throws ComponentException
|
||||
*/
|
||||
public function __construct(string $brand = self::ANY)
|
||||
{
|
||||
if (null !== $brand && !isset($this->brands[$brand])) {
|
||||
$brands = implode(', ', array_keys($this->brands));
|
||||
$message = sprintf('"%s" is not a valid credit card brand (Available: %s).', $brand, $brands);
|
||||
throw new ComponentException($message);
|
||||
if (!isset(self::BRAND_REGEX_LIST[$brand])) {
|
||||
throw new ComponentException(
|
||||
sprintf(
|
||||
'"%s" is not a valid credit card brand (Available: %s)',
|
||||
$brand,
|
||||
implode(', ', array_keys(self::BRAND_REGEX_LIST))
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
$this->brand = $brand;
|
||||
|
@ -60,34 +112,15 @@ class CreditCard extends AbstractRule
|
|||
*/
|
||||
public function validate($input): bool
|
||||
{
|
||||
$input = preg_replace('([^0-9])', '', $input);
|
||||
|
||||
if (empty($input)) {
|
||||
if (!is_scalar($input)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$input = preg_replace('/[ .-]/', '', (string) $input);
|
||||
if (!(new Luhn())->validate($input)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->verifyBrand($input);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the input matches the defined credit card brand or not.
|
||||
*
|
||||
* @param string $input
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function verifyBrand($input)
|
||||
{
|
||||
if (null === $this->brand) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$pattern = $this->brands[$this->brand];
|
||||
|
||||
return preg_match($pattern, $input) > 0;
|
||||
return preg_match(self::BRAND_REGEX_LIST[$this->brand], $input) > 0;
|
||||
}
|
||||
}
|
||||
|
|
38
tests/integration/rules/creditCard.phpt
Normal file
38
tests/integration/rules/creditCard.phpt
Normal file
|
@ -0,0 +1,38 @@
|
|||
--FILE--
|
||||
<?php
|
||||
require 'vendor/autoload.php';
|
||||
|
||||
use Respect\Validation\Exceptions\CreditCardException;
|
||||
use Respect\Validation\Exceptions\NestedValidationException;
|
||||
use Respect\Validation\Validator as v;
|
||||
|
||||
try {
|
||||
v::creditCard('Discover')->check(3566002020360505);
|
||||
} catch (CreditCardException $e) {
|
||||
echo $e->getMessage().PHP_EOL;
|
||||
}
|
||||
|
||||
try {
|
||||
v::not(v::creditCard('Visa'))->check(4024007153361885);
|
||||
} catch (CreditCardException $e) {
|
||||
echo $e->getMessage().PHP_EOL;
|
||||
}
|
||||
|
||||
try {
|
||||
v::creditCard('MasterCard')->assert(3566002020360505);
|
||||
} catch (NestedValidationException $exception) {
|
||||
echo $exception->getFullMessage().PHP_EOL;
|
||||
}
|
||||
|
||||
try {
|
||||
v::not(v::creditCard())->assert(5555444433331111);
|
||||
} catch (NestedValidationException $exception) {
|
||||
echo $exception->getFullMessage().PHP_EOL;
|
||||
}
|
||||
|
||||
?>
|
||||
--EXPECTF--
|
||||
3566002020360505 must be a valid "Discover" Credit Card number
|
||||
4024007153361885 must not be a valid "Visa" Credit Card number
|
||||
- 3566002020360505 must be a valid "MasterCard" Credit Card number
|
||||
- 5555444433331111 must not be a valid Credit Card number
|
|
@ -1,11 +0,0 @@
|
|||
--FILE--
|
||||
<?php
|
||||
require 'vendor/autoload.php';
|
||||
|
||||
use Respect\Validation\Validator as v;
|
||||
|
||||
v::creditCard()->assert('5555 4444 3333 1111');
|
||||
v::creditCard()->check('4111 1111 1111 1111');
|
||||
|
||||
?>
|
||||
--EXPECTF--
|
|
@ -1,16 +0,0 @@
|
|||
--FILE--
|
||||
<?php
|
||||
require 'vendor/autoload.php';
|
||||
|
||||
use Respect\Validation\Exceptions\CreditCardException;
|
||||
use Respect\Validation\Validator as v;
|
||||
|
||||
try {
|
||||
v::creditCard()->check(0);
|
||||
} catch (CreditCardException $e) {
|
||||
echo $e->getMessage().PHP_EOL;
|
||||
}
|
||||
|
||||
?>
|
||||
--EXPECTF--
|
||||
0 must be a valid Credit Card number
|
|
@ -1,16 +0,0 @@
|
|||
--FILE--
|
||||
<?php
|
||||
require 'vendor/autoload.php';
|
||||
|
||||
use Respect\Validation\Exceptions\CreditCardException;
|
||||
use Respect\Validation\Validator as v;
|
||||
|
||||
try {
|
||||
v::not(v::creditCard())->check(5555444433331111);
|
||||
} catch (CreditCardException $e) {
|
||||
echo $e->getMessage().PHP_EOL;
|
||||
}
|
||||
|
||||
?>
|
||||
--EXPECTF--
|
||||
5555444433331111 must not be a valid Credit Card number
|
|
@ -1,16 +0,0 @@
|
|||
--FILE--
|
||||
<?php
|
||||
require 'vendor/autoload.php';
|
||||
|
||||
use Respect\Validation\Exceptions\CreditCardException;
|
||||
use Respect\Validation\Validator as v;
|
||||
|
||||
try {
|
||||
v::creditCard('Visa')->check(3566002020360505);
|
||||
} catch (CreditCardException $e) {
|
||||
echo $e->getMessage().PHP_EOL;
|
||||
}
|
||||
|
||||
?>
|
||||
--EXPECTF--
|
||||
3566002020360505 must be a valid "Visa" Credit Card number
|
|
@ -17,35 +17,33 @@ use Respect\Validation\Exceptions\ComponentException;
|
|||
use Respect\Validation\Test\RuleTestCase;
|
||||
|
||||
/**
|
||||
* @group rule
|
||||
* @group rule
|
||||
*
|
||||
* @covers \Respect\Validation\Rules\CreditCard
|
||||
*
|
||||
* @author Andy Snell <andysnell@gmail.com>
|
||||
* @author Henrique Moody <henriquemoody@gmail.com>
|
||||
* @author Jean Pimentel <jeanfap@gmail.com>
|
||||
* @author William Espindola <oi@williamespindola.com.br>
|
||||
*/
|
||||
class CreditCardTest extends RuleTestCase
|
||||
final class CreditCardTest extends RuleTestCase
|
||||
{
|
||||
public function testShouldHaveNoCreditCardBrandByDefault(): void
|
||||
{
|
||||
$rule = new CreditCard();
|
||||
|
||||
self::assertNull($rule->brand);
|
||||
}
|
||||
|
||||
public function testShouldAcceptCreditCardBrandOnConstructor(): void
|
||||
{
|
||||
$rule = new CreditCard(CreditCard::VISA);
|
||||
|
||||
self::assertSame(CreditCard::VISA, $rule->brand);
|
||||
}
|
||||
|
||||
public function testShouldThrowExceptionWhenCreditCardBrandIsNotValid(): void
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function itShouldThrowExceptionWhenCreditCardBrandIsNotValid(): void
|
||||
{
|
||||
$message = '"RespectCard" is not a valid credit card brand';
|
||||
$message .= ' (Available: American Express, Diners Club, Discover, JCB, MasterCard, Visa).';
|
||||
$message .= ' (Available: Any, American Express, Diners Club, Discover, JCB, MasterCard, Visa)';
|
||||
|
||||
$this->expectException(ComponentException::class, $message);
|
||||
|
||||
new CreditCard('RespectCard');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function providerForValidInput(): array
|
||||
{
|
||||
$general = new CreditCard();
|
||||
|
@ -57,6 +55,7 @@ class CreditCardTest extends RuleTestCase
|
|||
$visa = new CreditCard(CreditCard::VISA);
|
||||
|
||||
return [
|
||||
[$general, 5555444433331111], // MasterCard 5 BIN Range
|
||||
[$general, '5376 7473 9720 8720'], // MasterCard 5 BIN Range
|
||||
[$master, '5376 7473 9720 8720'],
|
||||
[$general, '2223000048400011'], // MasterCard 2 BIN Range
|
||||
|
@ -76,6 +75,9 @@ class CreditCardTest extends RuleTestCase
|
|||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function providerForInvalidInput(): array
|
||||
{
|
||||
$general = new CreditCard();
|
||||
|
|
Loading…
Reference in a new issue