|
1 <?php |
|
2 /** |
|
3 * Zend Framework |
|
4 * |
|
5 * LICENSE |
|
6 * |
|
7 * This source file is subject to the new BSD license that is bundled |
|
8 * with this package in the file LICENSE.txt. |
|
9 * It is also available through the world-wide-web at this URL: |
|
10 * http://framework.zend.com/license/new-bsd |
|
11 * If you did not receive a copy of the license and are unable to |
|
12 * obtain it through the world-wide-web, please send an email |
|
13 * to license@zend.com so we can send you a copy immediately. |
|
14 * |
|
15 * @category Zend |
|
16 * @package Zend_Validate |
|
17 * @copyright Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com) |
|
18 * @license http://framework.zend.com/license/new-bsd New BSD License |
|
19 * @version $Id: CreditCard.php 22668 2010-07-25 14:50:46Z thomas $ |
|
20 */ |
|
21 |
|
22 /** |
|
23 * @see Zend_Validate_Abstract |
|
24 */ |
|
25 require_once 'Zend/Validate/Abstract.php'; |
|
26 |
|
27 /** |
|
28 * @category Zend |
|
29 * @package Zend_Validate |
|
30 * @copyright Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com) |
|
31 * @license http://framework.zend.com/license/new-bsd New BSD License |
|
32 */ |
|
33 class Zend_Validate_CreditCard extends Zend_Validate_Abstract |
|
34 { |
|
35 /** |
|
36 * Detected CCI list |
|
37 * |
|
38 * @var string |
|
39 */ |
|
40 const ALL = 'All'; |
|
41 const AMERICAN_EXPRESS = 'American_Express'; |
|
42 const UNIONPAY = 'Unionpay'; |
|
43 const DINERS_CLUB = 'Diners_Club'; |
|
44 const DINERS_CLUB_US = 'Diners_Club_US'; |
|
45 const DISCOVER = 'Discover'; |
|
46 const JCB = 'JCB'; |
|
47 const LASER = 'Laser'; |
|
48 const MAESTRO = 'Maestro'; |
|
49 const MASTERCARD = 'Mastercard'; |
|
50 const SOLO = 'Solo'; |
|
51 const VISA = 'Visa'; |
|
52 |
|
53 const CHECKSUM = 'creditcardChecksum'; |
|
54 const CONTENT = 'creditcardContent'; |
|
55 const INVALID = 'creditcardInvalid'; |
|
56 const LENGTH = 'creditcardLength'; |
|
57 const PREFIX = 'creditcardPrefix'; |
|
58 const SERVICE = 'creditcardService'; |
|
59 const SERVICEFAILURE = 'creditcardServiceFailure'; |
|
60 |
|
61 /** |
|
62 * Validation failure message template definitions |
|
63 * |
|
64 * @var array |
|
65 */ |
|
66 protected $_messageTemplates = array( |
|
67 self::CHECKSUM => "'%value%' seems to contain an invalid checksum", |
|
68 self::CONTENT => "'%value%' must contain only digits", |
|
69 self::INVALID => "Invalid type given. String expected", |
|
70 self::LENGTH => "'%value%' contains an invalid amount of digits", |
|
71 self::PREFIX => "'%value%' is not from an allowed institute", |
|
72 self::SERVICE => "'%value%' seems to be an invalid creditcard number", |
|
73 self::SERVICEFAILURE => "An exception has been raised while validating '%value%'", |
|
74 ); |
|
75 |
|
76 /** |
|
77 * List of allowed CCV lengths |
|
78 * |
|
79 * @var array |
|
80 */ |
|
81 protected $_cardLength = array( |
|
82 self::AMERICAN_EXPRESS => array(15), |
|
83 self::DINERS_CLUB => array(14), |
|
84 self::DINERS_CLUB_US => array(16), |
|
85 self::DISCOVER => array(16), |
|
86 self::JCB => array(16), |
|
87 self::LASER => array(16, 17, 18, 19), |
|
88 self::MAESTRO => array(12, 13, 14, 15, 16, 17, 18, 19), |
|
89 self::MASTERCARD => array(16), |
|
90 self::SOLO => array(16, 18, 19), |
|
91 self::UNIONPAY => array(16, 17, 18, 19), |
|
92 self::VISA => array(16), |
|
93 ); |
|
94 |
|
95 /** |
|
96 * List of accepted CCV provider tags |
|
97 * |
|
98 * @var array |
|
99 */ |
|
100 protected $_cardType = array( |
|
101 self::AMERICAN_EXPRESS => array('34', '37'), |
|
102 self::DINERS_CLUB => array('300', '301', '302', '303', '304', '305', '36'), |
|
103 self::DINERS_CLUB_US => array('54', '55'), |
|
104 self::DISCOVER => array('6011', '622126', '622127', '622128', '622129', '62213', |
|
105 '62214', '62215', '62216', '62217', '62218', '62219', |
|
106 '6222', '6223', '6224', '6225', '6226', '6227', '6228', |
|
107 '62290', '62291', '622920', '622921', '622922', '622923', |
|
108 '622924', '622925', '644', '645', '646', '647', '648', |
|
109 '649', '65'), |
|
110 self::JCB => array('3528', '3529', '353', '354', '355', '356', '357', '358'), |
|
111 self::LASER => array('6304', '6706', '6771', '6709'), |
|
112 self::MAESTRO => array('5018', '5020', '5038', '6304', '6759', '6761', '6763'), |
|
113 self::MASTERCARD => array('51', '52', '53', '54', '55'), |
|
114 self::SOLO => array('6334', '6767'), |
|
115 self::UNIONPAY => array('622126', '622127', '622128', '622129', '62213', '62214', |
|
116 '62215', '62216', '62217', '62218', '62219', '6222', '6223', |
|
117 '6224', '6225', '6226', '6227', '6228', '62290', '62291', |
|
118 '622920', '622921', '622922', '622923', '622924', '622925'), |
|
119 self::VISA => array('4'), |
|
120 ); |
|
121 |
|
122 /** |
|
123 * CCIs which are accepted by validation |
|
124 * |
|
125 * @var array |
|
126 */ |
|
127 protected $_type = array(); |
|
128 |
|
129 /** |
|
130 * Service callback for additional validation |
|
131 * |
|
132 * @var callback |
|
133 */ |
|
134 protected $_service; |
|
135 |
|
136 /** |
|
137 * Constructor |
|
138 * |
|
139 * @param string|array $type OPTIONAL Type of CCI to allow |
|
140 */ |
|
141 public function __construct($options = array()) |
|
142 { |
|
143 if ($options instanceof Zend_Config) { |
|
144 $options = $options->toArray(); |
|
145 } else if (!is_array($options)) { |
|
146 $options = func_get_args(); |
|
147 $temp['type'] = array_shift($options); |
|
148 if (!empty($options)) { |
|
149 $temp['service'] = array_shift($options); |
|
150 } |
|
151 |
|
152 $options = $temp; |
|
153 } |
|
154 |
|
155 if (!array_key_exists('type', $options)) { |
|
156 $options['type'] = self::ALL; |
|
157 } |
|
158 |
|
159 $this->setType($options['type']); |
|
160 if (array_key_exists('service', $options)) { |
|
161 $this->setService($options['service']); |
|
162 } |
|
163 } |
|
164 |
|
165 /** |
|
166 * Returns a list of accepted CCIs |
|
167 * |
|
168 * @return array |
|
169 */ |
|
170 public function getType() |
|
171 { |
|
172 return $this->_type; |
|
173 } |
|
174 |
|
175 /** |
|
176 * Sets CCIs which are accepted by validation |
|
177 * |
|
178 * @param string|array $type Type to allow for validation |
|
179 * @return Zend_Validate_CreditCard Provides a fluid interface |
|
180 */ |
|
181 public function setType($type) |
|
182 { |
|
183 $this->_type = array(); |
|
184 return $this->addType($type); |
|
185 } |
|
186 |
|
187 /** |
|
188 * Adds a CCI to be accepted by validation |
|
189 * |
|
190 * @param string|array $type Type to allow for validation |
|
191 * @return Zend_Validate_CreditCard Provides a fluid interface |
|
192 */ |
|
193 public function addType($type) |
|
194 { |
|
195 if (is_string($type)) { |
|
196 $type = array($type); |
|
197 } |
|
198 |
|
199 foreach($type as $typ) { |
|
200 if (defined('self::' . strtoupper($typ)) && !in_array($typ, $this->_type)) { |
|
201 $this->_type[] = $typ; |
|
202 } |
|
203 |
|
204 if (($typ == self::ALL)) { |
|
205 $this->_type = array_keys($this->_cardLength); |
|
206 } |
|
207 } |
|
208 |
|
209 return $this; |
|
210 } |
|
211 |
|
212 /** |
|
213 * Returns the actual set service |
|
214 * |
|
215 * @return callback |
|
216 */ |
|
217 public function getService() |
|
218 { |
|
219 return $this->_service; |
|
220 } |
|
221 |
|
222 /** |
|
223 * Sets a new callback for service validation |
|
224 * |
|
225 * @param unknown_type $service |
|
226 */ |
|
227 public function setService($service) |
|
228 { |
|
229 if (!is_callable($service)) { |
|
230 require_once 'Zend/Validate/Exception.php'; |
|
231 throw new Zend_Validate_Exception('Invalid callback given'); |
|
232 } |
|
233 |
|
234 $this->_service = $service; |
|
235 return $this; |
|
236 } |
|
237 |
|
238 /** |
|
239 * Defined by Zend_Validate_Interface |
|
240 * |
|
241 * Returns true if and only if $value follows the Luhn algorithm (mod-10 checksum) |
|
242 * |
|
243 * @param string $value |
|
244 * @return boolean |
|
245 */ |
|
246 public function isValid($value) |
|
247 { |
|
248 $this->_setValue($value); |
|
249 |
|
250 if (!is_string($value)) { |
|
251 $this->_error(self::INVALID, $value); |
|
252 return false; |
|
253 } |
|
254 |
|
255 if (!ctype_digit($value)) { |
|
256 $this->_error(self::CONTENT, $value); |
|
257 return false; |
|
258 } |
|
259 |
|
260 $length = strlen($value); |
|
261 $types = $this->getType(); |
|
262 $foundp = false; |
|
263 $foundl = false; |
|
264 foreach ($types as $type) { |
|
265 foreach ($this->_cardType[$type] as $prefix) { |
|
266 if (substr($value, 0, strlen($prefix)) == $prefix) { |
|
267 $foundp = true; |
|
268 if (in_array($length, $this->_cardLength[$type])) { |
|
269 $foundl = true; |
|
270 break 2; |
|
271 } |
|
272 } |
|
273 } |
|
274 } |
|
275 |
|
276 if ($foundp == false){ |
|
277 $this->_error(self::PREFIX, $value); |
|
278 return false; |
|
279 } |
|
280 |
|
281 if ($foundl == false) { |
|
282 $this->_error(self::LENGTH, $value); |
|
283 return false; |
|
284 } |
|
285 |
|
286 $sum = 0; |
|
287 $weight = 2; |
|
288 |
|
289 for ($i = $length - 2; $i >= 0; $i--) { |
|
290 $digit = $weight * $value[$i]; |
|
291 $sum += floor($digit / 10) + $digit % 10; |
|
292 $weight = $weight % 2 + 1; |
|
293 } |
|
294 |
|
295 if ((10 - $sum % 10) % 10 != $value[$length - 1]) { |
|
296 $this->_error(self::CHECKSUM, $value); |
|
297 return false; |
|
298 } |
|
299 |
|
300 if (!empty($this->_service)) { |
|
301 try { |
|
302 require_once 'Zend/Validate/Callback.php'; |
|
303 $callback = new Zend_Validate_Callback($this->_service); |
|
304 $callback->setOptions($this->_type); |
|
305 if (!$callback->isValid($value)) { |
|
306 $this->_error(self::SERVICE, $value); |
|
307 return false; |
|
308 } |
|
309 } catch (Zend_Exception $e) { |
|
310 $this->_error(self::SERVICEFAILURE, $value); |
|
311 return false; |
|
312 } |
|
313 } |
|
314 |
|
315 return true; |
|
316 } |
|
317 } |