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