respect-validation/library/Rules/CreditCard.php
Henrique Moody 7f66bcea10
Bump PHP support from 8.1 to 8.5
We want to release version 3.0 as fresh as possible, without having to
maintain backward compatibility with the previous versions. Because that
version will be on for some time, we decided it will be best to support
only PHP version 8.5 or higher.

Acked-by: Alexandre Gomes Gaigalas <alganet@gmail.com>
2025-12-18 19:03:38 +01:00

90 lines
2.8 KiB
PHP

<?php
/*
* Copyright (c) Alexandre Gomes Gaigalas <alganet@gmail.com>
* SPDX-License-Identifier: MIT
*/
declare(strict_types=1);
namespace Respect\Validation\Rules;
use Attribute;
use Respect\Validation\Exceptions\InvalidRuleConstructorException;
use Respect\Validation\Message\Template;
use Respect\Validation\Result;
use Respect\Validation\Rule;
use function array_keys;
use function is_scalar;
use function preg_match;
use function preg_replace;
#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'{{name}} must be a valid credit card number',
'{{name}} must not be a valid credit card number',
self::TEMPLATE_STANDARD,
)]
#[Template(
'{{name}} must be a valid {{brand|raw}} credit card number',
'{{name}} must not be a valid {{brand|raw}} credit card number',
self::TEMPLATE_BRANDED,
)]
final readonly class CreditCard implements Rule
{
public const TEMPLATE_BRANDED = '__branded__';
public const ANY = 'Any';
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';
public const RUPAY = 'RuPay';
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}$/',
self::JCB => '/^(?:2131|1800|35\d{3})\d{11}$/',
self::MASTERCARD => '/(5[1-5]|2[2-7])\d{14}$/',
self::VISA => '/^4\d{12}(?:\d{3})?$/',
self::RUPAY => '/^6(?!011)(?:0[0-9]{14}|52[12][0-9]{12})$/',
];
public function __construct(
private string $brand = self::ANY
) {
if (!isset(self::BRAND_REGEX_LIST[$brand])) {
throw new InvalidRuleConstructorException(
'"%s" is not a valid credit card brand (Available: %s)',
$brand,
array_keys(self::BRAND_REGEX_LIST)
);
}
}
public function evaluate(mixed $input): Result
{
$parameters = ['brand' => $this->brand];
$template = $this->brand === self::ANY ? self::TEMPLATE_STANDARD : self::TEMPLATE_BRANDED;
if (!is_scalar($input)) {
return Result::failed($input, $this, $parameters, $template);
}
$filteredInput = (string) preg_replace('/[ .-]/', '', (string) $input);
if (!(new Luhn())->evaluate($filteredInput)->hasPassed) {
return Result::failed($input, $this, $parameters, $template);
}
return new Result(
preg_match(self::BRAND_REGEX_LIST[$this->brand], $filteredInput) > 0,
$input,
$this,
$parameters,
$template
);
}
}