File indexing completed on 2024-12-22 05:37:12

0001 <?php
0002 /**
0003  * Zend Framework
0004  *
0005  * LICENSE
0006  *
0007  * This source file is subject to the new BSD license that is bundled
0008  * with this package in the file LICENSE.txt.
0009  * It is also available through the world-wide-web at this URL:
0010  * http://framework.zend.com/license/new-bsd
0011  * If you did not receive a copy of the license and are unable to
0012  * obtain it through the world-wide-web, please send an email
0013  * to license@zend.com so we can send you a copy immediately.
0014  *
0015  * @category   Zend
0016  * @package    Zend_Validate
0017  * @copyright  Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
0018  * @license    http://framework.zend.com/license/new-bsd     New BSD License
0019  * @version    $Id$
0020  */
0021 
0022 /**
0023  * @see Zend_Validate_Abstract
0024  */
0025 // require_once 'Zend/Validate/Abstract.php';
0026 
0027 /**
0028  * @category   Zend
0029  * @package    Zend_Validate
0030  * @copyright  Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
0031  * @license    http://framework.zend.com/license/new-bsd     New BSD License
0032  */
0033 class Zend_Validate_CreditCard extends Zend_Validate_Abstract
0034 {
0035     /**
0036      * Detected CCI list
0037      *
0038      * @var string
0039      */
0040     const ALL              = 'All';
0041     const AMERICAN_EXPRESS = 'American_Express';
0042     const UNIONPAY         = 'Unionpay';
0043     const DINERS_CLUB      = 'Diners_Club';
0044     const DINERS_CLUB_US   = 'Diners_Club_US';
0045     const DISCOVER         = 'Discover';
0046     const JCB              = 'JCB';
0047     const LASER            = 'Laser';
0048     const MAESTRO          = 'Maestro';
0049     const MASTERCARD       = 'Mastercard';
0050     const SOLO             = 'Solo';
0051     const VISA             = 'Visa';
0052 
0053     const CHECKSUM       = 'creditcardChecksum';
0054     const CONTENT        = 'creditcardContent';
0055     const INVALID        = 'creditcardInvalid';
0056     const LENGTH         = 'creditcardLength';
0057     const PREFIX         = 'creditcardPrefix';
0058     const SERVICE        = 'creditcardService';
0059     const SERVICEFAILURE = 'creditcardServiceFailure';
0060 
0061     /**
0062      * Validation failure message template definitions
0063      *
0064      * @var array
0065      */
0066     protected $_messageTemplates = array(
0067         self::CHECKSUM       => "'%value%' seems to contain an invalid checksum",
0068         self::CONTENT        => "'%value%' must contain only digits",
0069         self::INVALID        => "Invalid type given. String expected",
0070         self::LENGTH         => "'%value%' contains an invalid amount of digits",
0071         self::PREFIX         => "'%value%' is not from an allowed institute",
0072         self::SERVICE        => "'%value%' seems to be an invalid creditcard number",
0073         self::SERVICEFAILURE => "An exception has been raised while validating '%value%'",
0074     );
0075 
0076     /**
0077      * List of allowed CCV lengths
0078      *
0079      * @var array
0080      */
0081     protected $_cardLength = array(
0082         self::AMERICAN_EXPRESS => array(15),
0083         self::DINERS_CLUB      => array(14),
0084         self::DINERS_CLUB_US   => array(16),
0085         self::DISCOVER         => array(16),
0086         self::JCB              => array(16),
0087         self::LASER            => array(16, 17, 18, 19),
0088         self::MAESTRO          => array(12, 13, 14, 15, 16, 17, 18, 19),
0089         self::MASTERCARD       => array(16),
0090         self::SOLO             => array(16, 18, 19),
0091         self::UNIONPAY         => array(16, 17, 18, 19),
0092         self::VISA             => array(16),
0093     );
0094 
0095     /**
0096      * List of accepted CCV provider tags
0097      *
0098      * @var array
0099      */
0100     protected $_cardType = array(
0101         self::AMERICAN_EXPRESS => array('34', '37'),
0102         self::DINERS_CLUB      => array('300', '301', '302', '303', '304', '305', '36'),
0103         self::DINERS_CLUB_US   => array('54', '55'),
0104         self::DISCOVER         => array('6011', '622126', '622127', '622128', '622129', '62213',
0105                                         '62214', '62215', '62216', '62217', '62218', '62219',
0106                                         '6222', '6223', '6224', '6225', '6226', '6227', '6228',
0107                                         '62290', '62291', '622920', '622921', '622922', '622923',
0108                                         '622924', '622925', '644', '645', '646', '647', '648',
0109                                         '649', '65'),
0110         self::JCB              => array('3528', '3529', '353', '354', '355', '356', '357', '358'),
0111         self::LASER            => array('6304', '6706', '6771', '6709'),
0112         self::MAESTRO          => array('5018', '5020', '5038', '6304', '6759', '6761', '6763'),
0113         self::MASTERCARD       => array('51', '52', '53', '54', '55'),
0114         self::SOLO             => array('6334', '6767'),
0115         self::UNIONPAY         => array('622126', '622127', '622128', '622129', '62213', '62214',
0116                                         '62215', '62216', '62217', '62218', '62219', '6222', '6223',
0117                                         '6224', '6225', '6226', '6227', '6228', '62290', '62291',
0118                                         '622920', '622921', '622922', '622923', '622924', '622925'),
0119         self::VISA             => array('4'),
0120     );
0121 
0122     /**
0123      * CCIs which are accepted by validation
0124      *
0125      * @var array
0126      */
0127     protected $_type = array();
0128 
0129     /**
0130      * Service callback for additional validation
0131      *
0132      * @var callback
0133      */
0134     protected $_service;
0135 
0136     /**
0137      * Constructor
0138      *
0139      * @param string|array|Zend_Config $options OPTIONAL Type of CCI to allow
0140      */
0141     public function __construct($options = array())
0142     {
0143         if ($options instanceof Zend_Config) {
0144             $options = $options->toArray();
0145         } else if (!is_array($options)) {
0146             $options = func_get_args();
0147             $temp['type'] = array_shift($options);
0148             if (!empty($options)) {
0149                 $temp['service'] = array_shift($options);
0150             }
0151 
0152             $options = $temp;
0153         }
0154 
0155         if (!array_key_exists('type', $options)) {
0156             $options['type'] = self::ALL;
0157         }
0158 
0159         $this->setType($options['type']);
0160         if (array_key_exists('service', $options)) {
0161             $this->setService($options['service']);
0162         }
0163     }
0164 
0165     /**
0166      * Returns a list of accepted CCIs
0167      *
0168      * @return array
0169      */
0170     public function getType()
0171     {
0172         return $this->_type;
0173     }
0174 
0175     /**
0176      * Sets CCIs which are accepted by validation
0177      *
0178      * @param string|array $type Type to allow for validation
0179      * @return Zend_Validate_CreditCard Provides a fluent interface
0180      */
0181     public function setType($type)
0182     {
0183         $this->_type = array();
0184         return $this->addType($type);
0185     }
0186 
0187     /**
0188      * Adds a CCI to be accepted by validation
0189      *
0190      * @param string|array $type Type to allow for validation
0191      * @return Zend_Validate_CreditCard Provides a fluent interface
0192      */
0193     public function addType($type)
0194     {
0195         if (is_string($type)) {
0196             $type = array($type);
0197         }
0198 
0199         foreach($type as $typ) {
0200             if (defined('self::' . strtoupper($typ)) && !in_array($typ, $this->_type)) {
0201                 $this->_type[] = $typ;
0202             }
0203 
0204             if (($typ == self::ALL)) {
0205                 $this->_type = array_keys($this->_cardLength);
0206             }
0207         }
0208 
0209         return $this;
0210     }
0211 
0212     /**
0213      * Returns the actual set service
0214      *
0215      * @return callback
0216      */
0217     public function getService()
0218     {
0219         return $this->_service;
0220     }
0221 
0222     /**
0223      * Sets a new callback for service validation
0224      *
0225      * @param mixed $service
0226      * @throws Zend_Validate_Exception
0227      * @return $this
0228      */
0229     public function setService($service)
0230     {
0231         if (!is_callable($service)) {
0232             // require_once 'Zend/Validate/Exception.php';
0233             throw new Zend_Validate_Exception('Invalid callback given');
0234         }
0235 
0236         $this->_service = $service;
0237         return $this;
0238     }
0239 
0240     /**
0241      * Defined by Zend_Validate_Interface
0242      *
0243      * Returns true if and only if $value follows the Luhn algorithm (mod-10 checksum)
0244      *
0245      * @param  string $value
0246      * @return boolean
0247      */
0248     public function isValid($value)
0249     {
0250         $this->_setValue($value);
0251 
0252         if (!is_string($value)) {
0253             $this->_error(self::INVALID, $value);
0254             return false;
0255         }
0256 
0257         if (!ctype_digit($value)) {
0258             $this->_error(self::CONTENT, $value);
0259             return false;
0260         }
0261 
0262         $length = strlen($value);
0263         $types  = $this->getType();
0264         $foundp = false;
0265         $foundl = false;
0266         foreach ($types as $type) {
0267             foreach ($this->_cardType[$type] as $prefix) {
0268                 if (substr($value, 0, strlen($prefix)) == $prefix) {
0269                     $foundp = true;
0270                     if (in_array($length, $this->_cardLength[$type])) {
0271                         $foundl = true;
0272                         break 2;
0273                     }
0274                 }
0275             }
0276         }
0277 
0278         if ($foundp == false){
0279             $this->_error(self::PREFIX, $value);
0280             return false;
0281         }
0282 
0283         if ($foundl == false) {
0284             $this->_error(self::LENGTH, $value);
0285             return false;
0286         }
0287 
0288         $sum    = 0;
0289         $weight = 2;
0290 
0291         for ($i = $length - 2; $i >= 0; $i--) {
0292             $digit = $weight * $value[$i];
0293             $sum += floor($digit / 10) + $digit % 10;
0294             $weight = $weight % 2 + 1;
0295         }
0296 
0297         if ((10 - $sum % 10) % 10 != $value[$length - 1]) {
0298             $this->_error(self::CHECKSUM, $value);
0299             return false;
0300         }
0301 
0302         if (!empty($this->_service)) {
0303             try {
0304                 // require_once 'Zend/Validate/Callback.php';
0305                 $callback = new Zend_Validate_Callback($this->_service);
0306                 $callback->setOptions($this->_type);
0307                 if (!$callback->isValid($value)) {
0308                     $this->_error(self::SERVICE, $value);
0309                     return false;
0310                 }
0311             } catch (Zend_Exception $e) {
0312                 $this->_error(self::SERVICEFAILURE, $value);
0313                 return false;
0314             }
0315         }
0316 
0317         return true;
0318     }
0319 }