PageRenderTime 31ms CodeModel.GetById 10ms RepoModel.GetById 0ms app.codeStats 0ms

/library/Zend/Validator/CreditCard.php

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