Chris@0: Chris@0: * Chris@0: * For the full copyright and license information, please view the LICENSE Chris@0: * file that was distributed with this source code. Chris@0: */ Chris@0: Chris@0: namespace Symfony\Component\Validator\Constraints; Chris@0: Chris@0: use Symfony\Component\Validator\Constraint; Chris@0: use Symfony\Component\Validator\ConstraintValidator; Chris@0: use Symfony\Component\Validator\Exception\UnexpectedTypeException; Chris@0: Chris@0: /** Chris@0: * Validates that a card number belongs to a specified scheme. Chris@0: * Chris@0: * @author Tim Nagel Chris@0: * @author Bernhard Schussek Chris@0: * Chris@0: * @see http://en.wikipedia.org/wiki/Bank_card_number Chris@0: * @see http://www.regular-expressions.info/creditcard.html Chris@0: * @see http://www.barclaycard.co.uk/business/files/Ranges_and_Rules_September_2014.pdf Chris@0: */ Chris@0: class CardSchemeValidator extends ConstraintValidator Chris@0: { Chris@17: protected $schemes = [ Chris@0: // American Express card numbers start with 34 or 37 and have 15 digits. Chris@17: 'AMEX' => [ Chris@0: '/^3[47][0-9]{13}$/', Chris@17: ], Chris@0: // China UnionPay cards start with 62 and have between 16 and 19 digits. Chris@0: // Please note that these cards do not follow Luhn Algorithm as a checksum. Chris@17: 'CHINA_UNIONPAY' => [ Chris@0: '/^62[0-9]{14,17}$/', Chris@17: ], Chris@0: // Diners Club card numbers begin with 300 through 305, 36 or 38. All have 14 digits. Chris@0: // There are Diners Club cards that begin with 5 and have 16 digits. Chris@0: // These are a joint venture between Diners Club and MasterCard, and should be processed like a MasterCard. Chris@17: 'DINERS' => [ Chris@0: '/^3(?:0[0-5]|[68][0-9])[0-9]{11}$/', Chris@17: ], Chris@0: // Discover card numbers begin with 6011, 622126 through 622925, 644 through 649 or 65. Chris@0: // All have 16 digits. Chris@17: 'DISCOVER' => [ Chris@0: '/^6011[0-9]{12}$/', Chris@0: '/^64[4-9][0-9]{13}$/', Chris@0: '/^65[0-9]{14}$/', Chris@0: '/^622(12[6-9]|1[3-9][0-9]|[2-8][0-9][0-9]|91[0-9]|92[0-5])[0-9]{10}$/', Chris@17: ], Chris@0: // InstaPayment cards begin with 637 through 639 and have 16 digits. Chris@17: 'INSTAPAYMENT' => [ Chris@0: '/^63[7-9][0-9]{13}$/', Chris@17: ], Chris@0: // JCB cards beginning with 2131 or 1800 have 15 digits. Chris@0: // JCB cards beginning with 35 have 16 digits. Chris@17: 'JCB' => [ Chris@0: '/^(?:2131|1800|35[0-9]{3})[0-9]{11}$/', Chris@17: ], Chris@0: // Laser cards begin with either 6304, 6706, 6709 or 6771 and have between 16 and 19 digits. Chris@17: 'LASER' => [ Chris@0: '/^(6304|670[69]|6771)[0-9]{12,15}$/', Chris@17: ], Chris@0: // Maestro international cards begin with 675900..675999 and have between 12 and 19 digits. Chris@0: // Maestro UK cards begin with either 500000..509999 or 560000..699999 and have between 12 and 19 digits. Chris@17: 'MAESTRO' => [ Chris@0: '/^(6759[0-9]{2})[0-9]{6,13}$/', Chris@0: '/^(50[0-9]{4})[0-9]{6,13}$/', Chris@0: '/^5[6-9][0-9]{10,17}$/', Chris@0: '/^6[0-9]{11,18}$/', Chris@17: ], Chris@0: // All MasterCard numbers start with the numbers 51 through 55. All have 16 digits. Chris@0: // October 2016 MasterCard numbers can also start with 222100 through 272099. Chris@17: 'MASTERCARD' => [ Chris@0: '/^5[1-5][0-9]{14}$/', Chris@0: '/^2(22[1-9][0-9]{12}|2[3-9][0-9]{13}|[3-6][0-9]{14}|7[0-1][0-9]{13}|720[0-9]{12})$/', Chris@17: ], Chris@14: // All Visa card numbers start with a 4 and have a length of 13, 16, or 19 digits. Chris@17: 'VISA' => [ Chris@14: '/^4([0-9]{12}|[0-9]{15}|[0-9]{18})$/', Chris@17: ], Chris@17: ]; Chris@0: Chris@0: /** Chris@0: * Validates a creditcard belongs to a specified scheme. Chris@0: * Chris@0: * @param mixed $value Chris@0: * @param Constraint $constraint Chris@0: */ Chris@0: public function validate($value, Constraint $constraint) Chris@0: { Chris@0: if (!$constraint instanceof CardScheme) { Chris@0: throw new UnexpectedTypeException($constraint, __NAMESPACE__.'\CardScheme'); Chris@0: } Chris@0: Chris@0: if (null === $value || '' === $value) { Chris@0: return; Chris@0: } Chris@0: Chris@0: if (!is_numeric($value)) { Chris@0: $this->context->buildViolation($constraint->message) Chris@0: ->setParameter('{{ value }}', $this->formatValue($value)) Chris@0: ->setCode(CardScheme::NOT_NUMERIC_ERROR) Chris@0: ->addViolation(); Chris@0: Chris@0: return; Chris@0: } Chris@0: Chris@0: $schemes = array_flip((array) $constraint->schemes); Chris@0: $schemeRegexes = array_intersect_key($this->schemes, $schemes); Chris@0: Chris@0: foreach ($schemeRegexes as $regexes) { Chris@0: foreach ($regexes as $regex) { Chris@0: if (preg_match($regex, $value)) { Chris@0: return; Chris@0: } Chris@0: } Chris@0: } Chris@0: Chris@0: $this->context->buildViolation($constraint->message) Chris@0: ->setParameter('{{ value }}', $this->formatValue($value)) Chris@0: ->setCode(CardScheme::INVALID_FORMAT_ERROR) Chris@0: ->addViolation(); Chris@0: } Chris@0: }