3 * Zend Framework (http://framework.zend.com/)
5 * @link http://github.com/zendframework/zf2 for the canonical source repository
6 * @copyright Copyright (c) 2005-2013 Zend Technologies USA Inc. (http://www.zend.com)
7 * @license http://framework.zend.com/license/new-bsd New BSD License
10 namespace Zend\Validator
;
13 use Zend\Stdlib\ArrayUtils
;
15 class CreditCard
extends AbstractValidator
23 const AMERICAN_EXPRESS
= 'American_Express';
24 const UNIONPAY
= 'Unionpay';
25 const DINERS_CLUB
= 'Diners_Club';
26 const DINERS_CLUB_US
= 'Diners_Club_US';
27 const DISCOVER
= 'Discover';
29 const LASER
= 'Laser';
30 const MAESTRO
= 'Maestro';
31 const MASTERCARD
= 'Mastercard';
35 const CHECKSUM
= 'creditcardChecksum';
36 const CONTENT
= 'creditcardContent';
37 const INVALID
= 'creditcardInvalid';
38 const LENGTH
= 'creditcardLength';
39 const PREFIX
= 'creditcardPrefix';
40 const SERVICE
= 'creditcardService';
41 const SERVICEFAILURE
= 'creditcardServiceFailure';
44 * Validation failure message template definitions
48 protected $messageTemplates = array(
49 self
::CHECKSUM
=> "The input seems to contain an invalid checksum",
50 self
::CONTENT
=> "The input must contain only digits",
51 self
::INVALID
=> "Invalid type given. String expected",
52 self
::LENGTH
=> "The input contains an invalid amount of digits",
53 self
::PREFIX
=> "The input is not from an allowed institute",
54 self
::SERVICE
=> "The input seems to be an invalid credit card number",
55 self
::SERVICEFAILURE
=> "An exception has been raised while validating the input",
63 protected $cardName = array(
64 0 => self
::AMERICAN_EXPRESS
,
65 1 => self
::DINERS_CLUB
,
66 2 => self
::DINERS_CLUB_US
,
71 7 => self
::MASTERCARD
,
78 * List of allowed CCV lengths
82 protected $cardLength = array(
83 self
::AMERICAN_EXPRESS
=> array(15),
84 self
::DINERS_CLUB
=> array(14),
85 self
::DINERS_CLUB_US
=> array(16),
86 self
::DISCOVER
=> array(16),
87 self
::JCB
=> array(16),
88 self
::LASER
=> array(16, 17, 18, 19),
89 self
::MAESTRO
=> array(12, 13, 14, 15, 16, 17, 18, 19),
90 self
::MASTERCARD
=> array(16),
91 self
::SOLO
=> array(16, 18, 19),
92 self
::UNIONPAY
=> array(16, 17, 18, 19),
93 self
::VISA
=> array(16),
97 * List of accepted CCV provider tags
101 protected $cardType = array(
102 self
::AMERICAN_EXPRESS
=> array('34', '37'),
103 self
::DINERS_CLUB
=> array('300', '301', '302', '303', '304', '305', '36'),
104 self
::DINERS_CLUB_US
=> array('54', '55'),
105 self
::DISCOVER
=> array('6011', '622126', '622127', '622128', '622129', '62213',
106 '62214', '62215', '62216', '62217', '62218', '62219',
107 '6222', '6223', '6224', '6225', '6226', '6227', '6228',
108 '62290', '62291', '622920', '622921', '622922', '622923',
109 '622924', '622925', '644', '645', '646', '647', '648',
111 self
::JCB
=> array('3528', '3529', '353', '354', '355', '356', '357', '358'),
112 self
::LASER
=> array('6304', '6706', '6771', '6709'),
113 self
::MAESTRO
=> array('5018', '5020', '5038', '6304', '6759', '6761', '6762', '6763',
114 '6764', '6765', '6766'),
115 self
::MASTERCARD
=> array('51', '52', '53', '54', '55'),
116 self
::SOLO
=> array('6334', '6767'),
117 self
::UNIONPAY
=> array('622126', '622127', '622128', '622129', '62213', '62214',
118 '62215', '62216', '62217', '62218', '62219', '6222', '6223',
119 '6224', '6225', '6226', '6227', '6228', '62290', '62291',
120 '622920', '622921', '622922', '622923', '622924', '622925'),
121 self
::VISA
=> array('4'),
125 * Options for this validator
129 protected $options = array(
130 'service' => null, // Service callback for additional validation
131 'type' => array(), // CCIs which are accepted by validation
137 * @param string|array|Traversable $options OPTIONAL Type of CCI to allow
139 public function __construct($options = array())
141 if ($options instanceof Traversable
) {
142 $options = ArrayUtils
::iteratorToArray($options);
143 } elseif (!is_array($options)) {
144 $options = func_get_args();
145 $temp['type'] = array_shift($options);
146 if (!empty($options)) {
147 $temp['service'] = array_shift($options);
153 if (!array_key_exists('type', $options)) {
154 $options['type'] = self
::ALL
;
157 $this->setType($options['type']);
158 unset($options['type']);
160 if (array_key_exists('service', $options)) {
161 $this->setService($options['service']);
162 unset($options['service']);
165 parent
::__construct($options);
169 * Returns a list of accepted CCIs
173 public function getType()
175 return $this->options
['type'];
179 * Sets CCIs which are accepted by validation
181 * @param string|array $type Type to allow for validation
182 * @return CreditCard Provides a fluid interface
184 public function setType($type)
186 $this->options
['type'] = array();
187 return $this->addType($type);
191 * Adds a CCI to be accepted by validation
193 * @param string|array $type Type to allow for validation
194 * @return CreditCard Provides a fluid interface
196 public function addType($type)
198 if (is_string($type)) {
199 $type = array($type);
202 foreach ($type as $typ) {
203 if (defined('self::' . strtoupper($typ)) && !in_array($typ, $this->options
['type'])) {
204 $this->options
['type'][] = $typ;
207 if (($typ == self
::ALL
)) {
208 $this->options
['type'] = array_keys($this->cardLength
);
216 * Returns the actual set service
220 public function getService()
222 return $this->options
['service'];
226 * Sets a new callback for service validation
228 * @param callable $service
230 * @throws Exception\InvalidArgumentException on invalid service callback
232 public function setService($service)
234 if (!is_callable($service)) {
235 throw new Exception\
InvalidArgumentException('Invalid callback given');
238 $this->options
['service'] = $service;
243 * Returns true if and only if $value follows the Luhn algorithm (mod-10 checksum)
245 * @param string $value
248 public function isValid($value)
250 $this->setValue($value);
252 if (!is_string($value)) {
253 $this->error(self
::INVALID
, $value);
257 if (!ctype_digit($value)) {
258 $this->error(self
::CONTENT
, $value);
262 $length = strlen($value);
263 $types = $this->getType();
266 foreach ($types as $type) {
267 foreach ($this->cardType
[$type] as $prefix) {
268 if (substr($value, 0, strlen($prefix)) == $prefix) {
270 if (in_array($length, $this->cardLength
[$type])) {
278 if ($foundp == false) {
279 $this->error(self
::PREFIX
, $value);
283 if ($foundl == false) {
284 $this->error(self
::LENGTH
, $value);
291 for ($i = $length - 2; $i >= 0; $i--) {
292 $digit = $weight * $value[$i];
293 $sum +
= floor($digit / 10) +
$digit %
10;
294 $weight = $weight %
2 +
1;
297 if ((10 - $sum %
10) %
10 != $value[$length - 1]) {
298 $this->error(self
::CHECKSUM
, $value);
302 $service = $this->getService();
303 if (!empty($service)) {
305 $callback = new Callback($service);
306 $callback->setOptions($this->getType());
307 if (!$callback->isValid($value)) {
308 $this->error(self
::SERVICE
, $value);
311 } catch (\Exception
$e) {
312 $this->error(self
::SERVICEFAILURE
, $value);