PageRenderTime 42ms CodeModel.GetById 13ms RepoModel.GetById 0ms app.codeStats 1ms

/vendor/zendframework/zendframework/library/Zend/Validator/CreditCard.php

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