CreditCard.php 2.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102
  1. <?php
  2. /*
  3. * Copyright (c) Alexandre Gomes Gaigalas <alganet@gmail.com>
  4. * SPDX-License-Identifier: MIT
  5. */
  6. declare(strict_types=1);
  7. namespace Respect\Validation\Rules;
  8. use Respect\Validation\Exceptions\ComponentException;
  9. use function array_keys;
  10. use function implode;
  11. use function is_scalar;
  12. use function preg_match;
  13. use function preg_replace;
  14. use function sprintf;
  15. /**
  16. * Validates whether the input is a credit card number.
  17. *
  18. * @author Alexander Gorshkov <mazanax@yandex.ru>
  19. * @author Andy Snell <andysnell@gmail.com>
  20. * @author Henrique Moody <henriquemoody@gmail.com>
  21. * @author Jean Pimentel <jeanfap@gmail.com>
  22. * @author Nick Lombard <github@jigsoft.co.za>
  23. * @author William Espindola <oi@williamespindola.com.br>
  24. * @author Rakshit Arora <rakshit087@gmail.com>
  25. */
  26. final class CreditCard extends AbstractRule
  27. {
  28. public const ANY = 'Any';
  29. public const AMERICAN_EXPRESS = 'American Express';
  30. public const DINERS_CLUB = 'Diners Club';
  31. public const DISCOVER = 'Discover';
  32. public const JCB = 'JCB';
  33. public const MASTERCARD = 'MasterCard';
  34. public const VISA = 'Visa';
  35. public const RUPAY = 'RuPay';
  36. private const BRAND_REGEX_LIST = [
  37. self::ANY => '/^[0-9]+$/',
  38. self::AMERICAN_EXPRESS => '/^3[47]\d{13}$/',
  39. self::DINERS_CLUB => '/^3(?:0[0-5]|[68]\d)\d{11}$/',
  40. self::DISCOVER => '/^6(?:011|5\d{2})\d{12}$/',
  41. self::JCB => '/^(?:2131|1800|35\d{3})\d{11}$/',
  42. self::MASTERCARD => '/(5[1-5]|2[2-7])\d{14}$/',
  43. self::VISA => '/^4\d{12}(?:\d{3})?$/',
  44. self::RUPAY => '/^6(?!011)(?:0[0-9]{14}|52[12][0-9]{12})$/',
  45. ];
  46. /**
  47. * @var string
  48. */
  49. private $brand;
  50. /**
  51. * Initializes the rule.
  52. *
  53. * @throws ComponentException
  54. */
  55. public function __construct(string $brand = self::ANY)
  56. {
  57. if (!isset(self::BRAND_REGEX_LIST[$brand])) {
  58. throw new ComponentException(
  59. sprintf(
  60. '"%s" is not a valid credit card brand (Available: %s)',
  61. $brand,
  62. implode(', ', array_keys(self::BRAND_REGEX_LIST))
  63. )
  64. );
  65. }
  66. $this->brand = $brand;
  67. }
  68. /**
  69. * {@inheritDoc}
  70. */
  71. public function validate($input): bool
  72. {
  73. if (!is_scalar($input)) {
  74. return false;
  75. }
  76. $input = (string) preg_replace('/[ .-]/', '', (string) $input);
  77. if (!(new Luhn())->validate($input)) {
  78. return false;
  79. }
  80. return preg_match(self::BRAND_REGEX_LIST[$this->brand], $input) > 0;
  81. }
  82. }