Browse Source

[GENERIC] Zend_Validate_CreditCard:

- new component merged to core
- deprecated Ccnum validator for security reasons

git-svn-id: http://framework.zend.com/svn/framework/standard/trunk@18748 44c647ce-9c0f-0410-b52a-842ac1e357ba
thomas 16 years ago
parent
commit
58b38b05f9

+ 336 - 0
documentation/manual/en/module_specs/Zend_Validate-CreditCard.xml

@@ -0,0 +1,336 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Reviewed: no -->
+<sect2 id="zend.validate.set.creditcard">
+    <title>CreditCard</title>
+
+    <para>
+        <classname>Zend_Validate_CreditCard</classname> allows you to validate if a given value
+        could be a credit card number.
+    </para>
+
+    <para>
+        A creditcard contains several items of metadata, including a hologram, account number, logo,
+        expiration date, security code and the card holder name.  The algorithms for verifying the
+        combination of metadata are only known to the issuing company, and should be verified with
+        them for purposes of payment. However, it's often useful to know whether or not a given
+        number actually falls within the ranges of possible numbers <emphasis>prior</emphasis> to
+        performing such verification, and, as such, <classname>Zend_Validate_CreditCard</classname>
+        simply verifies that the credit card number provided is well-formed. 
+    </para>
+
+    <para>
+        For those cases where you have a service that can perform comprehensive verification,
+        <classname>Zend_Validate_CreditCard</classname> also provides the ability to attach a
+        service callback to trigger once the credit card number has been deemed valid; this callback
+        will then be triggered, and its return value will determine overall validity.
+    </para>
+
+    <para>
+        The following issuing institutes are accepted:
+    </para>
+
+    <itemizedlist>
+        <listitem>
+            <para>
+                <emphasis>American Express</emphasis>
+            </para>
+
+            <para>
+                <emphasis>China UnionPay</emphasis>
+            </para>
+
+            <para>
+                <emphasis>Diners Club Card Blanche</emphasis>
+            </para>
+
+            <para>
+                <emphasis>Diners Club International</emphasis>
+            </para>
+
+            <para>
+                <emphasis>Diners Club US &amp; Canada</emphasis>
+            </para>
+
+            <para>
+                <emphasis>Discover Card</emphasis>
+            </para>
+
+            <para>
+                <emphasis>JCB</emphasis>
+            </para>
+
+            <para>
+                <emphasis>Laser</emphasis>
+            </para>
+
+            <para>
+                <emphasis>Maestro</emphasis>
+            </para>
+
+            <para>
+                <emphasis>MasterCard</emphasis>
+            </para>
+
+            <para>
+                <emphasis>Solo</emphasis>
+            </para>
+
+            <para>
+                <emphasis>Visa</emphasis>
+            </para>
+
+            <para>
+                <emphasis>Visa Electron</emphasis>
+            </para>
+        </listitem>
+    </itemizedlist>
+
+    <note>
+        <title>Invalid institutes</title>
+
+        <para>
+            The institutes <emphasis>Bankcard</emphasis> and <emphasis>Diners Club
+                enRoute</emphasis> do not exist anymore. Therefore they are treated as invalid.
+        </para>
+
+        <para>
+            <emphasis>Switch</emphasis> has been rebranded to <emphasis>Visa</emphasis> and is
+            therefore also treated as invalid.
+        </para>
+    </note>
+
+    <sect3 id="zend.validate.set.creditcard.basic">
+        <title>Basic usage</title>
+
+        <para>
+            There are several credit card institutes which can be validated by
+            <classname>Zend_Validate_CreditCard</classname>. Per default, all known institutes will
+            be accepted. See the folowing example:
+        </para>
+
+        <programlisting language="php"><![CDATA[
+$valid = new Zend_Validate_CreditCard();
+if ($valid->isValid($input)) {
+    // input appears to be valid
+} else {
+    // input is invalid
+}
+]]></programlisting>
+
+        <para>
+            The above example would validate against all known credit card institutes.
+        </para>
+    </sect3>
+
+    <sect3 id="zend.validate.set.creditcard.institute">
+        <title>Accepting defined credit cards</title>
+
+        <para>
+            Sometimes it is necessary to accept only defined credit card institutes instead of all; 
+            e.g., when you have a webshop which accepts only Visa and American Express cards.
+            <classname>Zend_Validate_CreditCard</classname> allows you to do exactly this by
+            limiting it to exactly these institutes.
+        </para>
+
+        <para>
+            To use a limitation you can either provide specific institutes at initiation, or
+            afterwards by using <methodname>setType()</methodname>. Each can take several arguments.
+        </para>
+
+        <para>
+            You can provide a single institute:
+        </para>
+
+        <programlisting language="php"><![CDATA[
+$valid = new Zend_Validate_CreditCard(
+    Zend_Validate_CreditCard::AMERICAN_EXPRESS
+);
+]]></programlisting>
+
+        <para>
+            When you want to allow multiple institutes, then you can provide them as array:
+        </para>
+
+        <programlisting language="php"><![CDATA[
+$valid = new Zend_Validate_CreditCard(array(
+    Zend_Validate_CreditCard::AMERICAN_EXPRESS,
+    Zend_Validate_CreditCard::VISA
+));
+]]></programlisting>
+
+        <para>
+            And as with all validators, you can also pass an associative array of options or an
+            instance of Zend_Config. In this case you have to provide the institutes with the
+            <property>type</property> array key as simulated here:
+        </para>
+
+        <programlisting language="php"><![CDATA[
+$valid = new Zend_Validate_CreditCard(array(
+    'type' => array(Zend_Validate_CreditCard::AMERICAN_EXPRESS)
+));
+]]></programlisting>
+
+        <table id="zend.validate.set.creditcard.institute.table">
+            <title>Constants for credit card institutes</title>
+            <tgroup cols="5">
+                <thead>
+                    <row>
+                        <entry>Institute</entry>
+                        <entry>Constant</entry>
+                    </row>
+                </thead>
+
+                <tbody>
+                    <row>
+                        <entry><emphasis>American Express</emphasis></entry>
+                        <entry><constant>AMERICAN_EXPRESS</constant></entry>
+                    </row>
+
+                    <row>
+                        <entry><emphasis>China UnionPay</emphasis></entry>
+                        <entry><constant>UNIONPAY</constant></entry>
+                    </row>
+
+                    <row>
+                        <entry><emphasis>Diners Club Card Blanche</emphasis></entry>
+                        <entry><constant>DINERS_CLUB</constant></entry>
+                    </row>
+
+                    <row>
+                        <entry><emphasis>Diners Club International</emphasis></entry>
+                        <entry><constant>DINERS_CLUB</constant></entry>
+                    </row>
+
+                    <row>
+                        <entry><emphasis>Diners Club US &amp; Canada</emphasis></entry>
+                        <entry><constant>DINERS_CLUB_US</constant></entry>
+                    </row>
+
+                    <row>
+                        <entry><emphasis>Discover Card</emphasis></entry>
+                        <entry><constant>DISCOVER</constant></entry>
+                    </row>
+
+                    <row>
+                        <entry><emphasis>JCB</emphasis></entry>
+                        <entry><constant>JCB</constant></entry>
+                    </row>
+
+                    <row>
+                        <entry><emphasis>Laser</emphasis></entry>
+                        <entry><constant>LASER</constant></entry>
+                    </row>
+
+                    <row>
+                        <entry><emphasis>Maestro</emphasis></entry>
+                        <entry><constant>MAESTRO</constant></entry>
+                    </row>
+
+                    <row>
+                        <entry><emphasis>MasterCard</emphasis></entry>
+                        <entry><constant>MASTERCARD</constant></entry>
+                    </row>
+
+                    <row>
+                        <entry><emphasis>Solo</emphasis></entry>
+                        <entry><constant>Solo</constant></entry>
+                    </row>
+
+                    <row>
+                        <entry><emphasis>Visa</emphasis></entry>
+                        <entry><constant>VISA</constant></entry>
+                    </row>
+
+                    <row>
+                        <entry><emphasis>Visa Electron</emphasis></entry>
+                        <entry><constant>VISA</constant></entry>
+                    </row>
+                </tbody>
+            </tgroup>
+        </table>
+
+        <para>
+            You can also set or add institutes afterward instantiation by using the methods
+            <methodname>setType()</methodname>, <methodname>addType()</methodname> and
+            <methodname>getType()</methodname>.
+        </para>
+
+        <programlisting language="php"><![CDATA[
+$valid = new Zend_Validate_CreditCard();
+$valid->setType(array(
+    Zend_Validate_CreditCard::AMERICAN_EXPRESS,
+    Zend_Validate_CreditCard::VISA
+));
+]]></programlisting>
+
+        <note>
+            <title>Default institute</title>
+
+            <para>
+                When no institute is given at initiation then <emphasis>ALL</emphasis> will be
+                used, which sets all institutes at once.
+            </para>
+
+            <para>
+                In this case the usage of <methodname>addType</methodname> is useless because all
+                institutes are already added.
+            </para>
+        </note>
+    </sect3>
+
+    <sect3 id="zend.validate.set.creditcard.servicecheck">
+        <title>Validation by using foreign APIs</title>
+
+        <para>
+            As said before <classname>Zend_Validate_CreditCard</classname> will only validate
+            the credit card number. Fortunately, some institutes provide online APIs which
+            can validate a credit card number by using algorithms which are not available to the
+            public. Most of these services are paid services. Therefore, this check is deactivated
+            per default.
+        </para>
+
+        <para>
+            When you have access to such an API, then you can use it as an addon for
+            <classname>Zend_Validate_CreditCard</classname> and increase the security of the
+            validation.
+        </para>
+
+        <para>
+            To do so, you simply need to give a callback which will be called when the generic
+            validation has passed. This prevents the API from being called for invalid numbers,
+            which increases the performance of the application.
+        </para>
+
+        <para>
+            <methodname>setService()</methodname> sets a new service, and
+            <methodname>getService()</methodname> returns the set service. As a configuration
+            option,
+            you can give the array key '<property>service</property>' at initiation. For details
+            about possible options take a look into <link linkend="zend.validate.set.callback" />.
+        </para>
+
+        <programlisting language="php"><![CDATA[
+// Your service class
+class CcService
+{
+    public function checkOnline($cardnumber, $types)
+    {
+        // some online validation
+    }
+}
+
+// The validation
+$service = new CcService();
+$valid   = new Zend_Validate_CreditCard(Zend_Validate_CreditCard::VISA);
+$valid->setService(array($service, 'checkOnline'));
+]]></programlisting>
+
+        <para>
+            As you can see the callback method will be called with the creditcard number as the
+            first parameter, and the accepted types as the second parameter.
+        </para>
+    </sect3>
+</sect2>
+<!--
+vim:se ts=4 sw=4 et:
+-->

+ 8 - 0
documentation/manual/en/module_specs/Zend_Validate-Set.xml

@@ -59,6 +59,7 @@
     </sect2>
 
     <xi:include href="Zend_Validate-Callback.xml" />
+    <xi:include href="Zend_Validate-CreditCard.xml" />
 
     <sect2 id="zend.validate.set.ccnum">
         <title>Ccnum</title>
@@ -66,6 +67,13 @@
             Returns <constant>TRUE</constant> if and only if <varname>$value</varname> follows the Luhn algorithm
             (mod-10 checksum) for credit card numbers.
         </para>
+        <note>
+            <para>
+                The <classname>Ccnum</classname> validator has been deprecated in favor of the
+                <classname>CreditCard</classname> validator. For security reasons you should use
+                CreditCard instead of Ccnum.
+            </para>
+        </note>
     </sect2>
 
     <sect2 id="zend.validate.set.date">

+ 5 - 0
library/Zend/Validate/Ccnum.php

@@ -59,6 +59,11 @@ class Zend_Validate_Ccnum extends Zend_Validate_Abstract
         self::CHECKSUM => "Luhn algorithm (mod-10 checksum) failed on '%value%'"
     );
 
+    public function __construct()
+    {
+        trigger_error('Using the Ccnum validator is deprecated in favor of the CreditCard validator');
+    }
+
     /**
      * Defined by Zend_Validate_Interface
      *

+ 315 - 0
library/Zend/Validate/CreditCard.php

@@ -0,0 +1,315 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Validate
+ * @copyright  Copyright (c) 2005-2009 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ * @version    $Id:$
+ */
+
+/**
+ * @see Zend_Validate_Abstract
+ */
+require_once 'Zend/Validate/Abstract.php';
+
+/**
+ * @category   Zend
+ * @package    Zend_Validate
+ * @copyright  Copyright (c) 2005-2009 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+class Zend_Validate_CreditCard extends Zend_Validate_Abstract
+{
+    /**
+     * Detected CCI list
+     *
+     * @var string
+     */
+    const ALL              = 'All';
+    const AMERICAN_EXPRESS = 'American_Express';
+    const UNIONPAY         = 'Unionpay';
+    const DINERS_CLUB      = 'Diners_Club';
+    const DINERS_CLUB_US   = 'Diners_Club_US';
+    const DISCOVER         = 'Discover';
+    const JCB              = 'JCB';
+    const LASER            = 'Laser';
+    const MAESTRO          = 'Maestro';
+    const MASTERCARD       = 'Mastercard';
+    const SOLO             = 'Solo';
+    const VISA             = 'Visa';
+
+    const CHECKSUM       = 'creditcardChecksum';
+    const CONTENT        = 'creditcardContent';
+    const INVALID        = 'creditcardInvalid';
+    const LENGTH         = 'creditcardLength';
+    const PREFIX         = 'creditcardPrefix';
+    const SERVICE        = 'creditcardService';
+    const SERVICEFAILURE = 'creditcardServiceFailure';
+
+    /**
+     * Validation failure message template definitions
+     *
+     * @var array
+     */
+    protected $_messageTemplates = array(
+        self::CHECKSUM       => "Luhn algorithm (mod-10 checksum) failed on '%value%'",
+        self::CONTENT        => "'%value%' must contain only digits",
+        self::INVALID        => "Invalid type given, value should be a string",
+        self::LENGTH         => "'%value%' contains a invalid amount of digits",
+        self::PREFIX         => "'%value%' is not from an allowed institute",
+        self::SERVICE        => "Validation of '%value%' has been failed by the service",
+        self::SERVICEFAILURE => "The service returned a failure while validating '%value%'"
+    );
+
+    /**
+     * List of allowed CCV lengths
+     *
+     * @var array
+     */
+    protected $_cardLength = array(
+        self::AMERICAN_EXPRESS => array(15),
+        self::DINERS_CLUB      => array(14),
+        self::DINERS_CLUB_US   => array(16),
+        self::DISCOVER         => array(16),
+        self::JCB              => array(16),
+        self::LASER            => array(16, 17, 18, 19),
+        self::MAESTRO          => array(12, 13, 14, 15, 16, 17, 18, 19),
+        self::MASTERCARD       => array(16),
+        self::SOLO             => array(16, 18, 19),
+        self::UNIONPAY         => array(16, 17, 18, 19),
+        self::VISA             => array(16),
+    );
+
+    /**
+     * List of accepted CCV provider tags
+     *
+     * @var array
+     */
+    protected $_cardType = array(
+        self::AMERICAN_EXPRESS => array('34', '37'),
+        self::DINERS_CLUB      => array('300', '301', '302', '303', '304', '305', '36'),
+        self::DINERS_CLUB_US   => array('54', '55'),
+        self::DISCOVER         => array('6011', '622126', '622127', '622128', '622129', '62213',
+                                        '62214', '62215', '62216', '62217', '62218', '62219',
+                                        '6222', '6223', '6224', '6225', '6226', '6227', '6228',
+                                        '62290', '62291', '622920', '622921', '622922', '622923',
+                                        '622924', '622925', '644', '645', '646', '647', '648',
+                                        '649', '65'),
+        self::JCB              => array('3528', '3529', '353', '354', '355', '356', '357', '358'),
+        self::LASER            => array('6304', '6706', '6771', '6709'),
+        self::MAESTRO          => array('5018', '5020', '5038', '6304', '6759', '6761', '6763'),
+        self::MASTERCARD       => array('51', '52', '53', '54', '55'),
+        self::SOLO             => array('6334', '6767'),
+        self::UNIONPAY         => array('622126', '622127', '622128', '622129', '62213', '62214',
+                                        '62215', '62216', '62217', '62218', '62219', '6222', '6223',
+                                        '6224', '6225', '6226', '6227', '6228', '62290', '62291',
+                                        '622920', '622921', '622922', '622923', '622924', '622925'),
+        self::VISA             => array('4'),
+    );
+
+    /**
+     * CCIs which are accepted by validation
+     *
+     * @var array
+     */
+    protected $_type = array();
+
+    /**
+     * Service callback for additional validation
+     *
+     * @var callback
+     */
+    protected $_service;
+
+    /**
+     * Constructor
+     *
+     * @param string|array $type OPTIONAL Type of CCI to allow
+     */
+    public function __construct($options = array())
+    {
+        if ($options instanceof Zend_Config) {
+            $options = $options->toArray();
+        } else if (!is_array($options)) {
+            $options = func_get_args();
+            $temp['type'] = array_shift($options);
+            if (!empty($options)) {
+                $temp['service'] = array_shift($options);
+            }
+
+            $options = $temp;
+        }
+
+        if (!array_key_exists('type', $options)) {
+            $options['type'] = self::ALL;
+        }
+
+        $this->setType($options['type']);
+        if (array_key_exists('service', $options)) {
+            $this->setService($options['service']);
+        }
+    }
+
+    /**
+     * Returns a list of accepted CCIs
+     *
+     * @return array
+     */
+    public function getType()
+    {
+        return $this->_type;
+    }
+
+    /**
+     * Sets CCIs which are accepted by validation
+     *
+     * @param string|array $type Type to allow for validation
+     * @return Zend_Validate_CreditCard Provides a fluid interface
+     */
+    public function setType($type)
+    {
+        $this->_type = array();
+        return $this->addType($type);
+    }
+
+    /**
+     * Adds a CCI to be accepted by validation
+     *
+     * @param string|array $type Type to allow for validation
+     * @return Zend_Validate_CreditCard Provides a fluid interface
+     */
+    public function addType($type)
+    {
+        if (is_string($type)) {
+            $type = array($type);
+        }
+
+        foreach($type as $typ) {
+            if (defined('self::' . strtoupper($typ)) && !in_array($typ, $this->_type)) {
+                $this->_type[] = $typ;
+            }
+
+            if (($typ == self::ALL)) {
+                $this->_type = array_keys($this->_cardLength);
+            }
+        }
+
+        return $this;
+    }
+
+    /**
+     * Returns the actual set service
+     *
+     * @return callback
+     */
+    public function getService()
+    {
+        return $this->_service;
+    }
+
+    /**
+     * Sets a new callback for service validation
+     *
+     * @param unknown_type $service
+     */
+    public function setService($service)
+    {
+        if (!is_callable($service)) {
+            require_once 'Zend/Validate/Exception.php';
+            throw new Zend_Validate_Exception('Invalid callback given');
+        }
+
+        $this->_service = $service;
+        return $this;
+    }
+
+    /**
+     * Defined by Zend_Validate_Interface
+     *
+     * Returns true if and only if $value follows the Luhn algorithm (mod-10 checksum)
+     *
+     * @param  string $value
+     * @return boolean
+     */
+    public function isValid($value)
+    {
+        $this->_setValue($value);
+
+        if (!is_string($value)) {
+            $this->_error(self::INVALID, $value);
+            return false;
+        }
+
+        if (!ctype_digit($value)) {
+            $this->_error(self::CONTENT, $value);
+            return false;
+        }
+
+        $length = strlen($value);
+        $types  = $this->getType();
+        $found  = false;
+        foreach ($types as $type) {
+            if (in_array($length, $this->_cardLength[$type])) {
+                foreach ($this->_cardType[$type] as $prefix) {
+                    if (substr($value, 0, strlen($prefix)) == $prefix) {
+                        $found = true;
+                        break;
+                    }
+                }
+            }
+        }
+
+        if ($found == false) {
+            if (!in_array($length, $this->_cardLength[$type])) {
+                $this->_error(self::LENGTH, $value);
+                return false;
+            } else {
+                $this->_error(self::PREFIX, $value);
+                return false;
+            }
+        }
+
+        $sum    = 0;
+        $weight = 2;
+
+        for ($i = $length - 2; $i >= 0; $i--) {
+            $digit = $weight * $value[$i];
+            $sum += floor($digit / 10) + $digit % 10;
+            $weight = $weight % 2 + 1;
+        }
+
+        if ((10 - $sum % 10) % 10 != $value[$length - 1]) {
+            $this->_error(self::CHECKSUM, $value);
+            return false;
+        }
+
+        if (!empty($this->_service)) {
+            try {
+                require_once 'Zend/Validate/Callback.php';
+                $callback = new Zend_Validate_Callback($this->_service);
+                $callback->setOptions($this->_type);
+                if (!$callback->isValid($value)) {
+                    $this->_error(self::SERVICE, $value);
+                    return false;
+                }
+            } catch (Zend_Exception $e) {
+                $this->_error(self::SERVICEFAILURE, $value);
+                return false;
+            }
+        }
+
+        return true;
+    }
+}

+ 18 - 0
tests/Zend/Validate/CcnumTest.php

@@ -56,6 +56,7 @@ class Zend_Validate_CcnumTest extends PHPUnit_Framework_TestCase
      */
     public function setUp()
     {
+        set_error_handler(array($this, 'errorHandlerIgnore'));
         $this->_validator = new Zend_Validate_Ccnum();
     }
 
@@ -76,6 +77,7 @@ class Zend_Validate_CcnumTest extends PHPUnit_Framework_TestCase
         foreach ($valuesExpected as $input => $result) {
             $this->assertEquals($result, $this->_validator->isValid($input));
         }
+        restore_error_handler();
     }
 
     /**
@@ -86,5 +88,21 @@ class Zend_Validate_CcnumTest extends PHPUnit_Framework_TestCase
     public function testGetMessages()
     {
         $this->assertEquals(array(), $this->_validator->getMessages());
+        restore_error_handler();
+    }
+
+    /**
+     * Ignores a raised PHP error when in effect, but throws a flag to indicate an error occurred
+     *
+     * @param  integer $errno
+     * @param  string  $errstr
+     * @param  string  $errfile
+     * @param  integer $errline
+     * @param  array   $errcontext
+     * @return void
+     */
+    public function errorHandlerIgnore($errno, $errstr, $errfile, $errline, array $errcontext)
+    {
+        $this->_errorOccured = true;
     }
 }

+ 256 - 0
tests/Zend/Validate/CreditCardTest.php

@@ -0,0 +1,256 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Validate
+ * @subpackage UnitTests
+ * @copyright  Copyright (c) 2005-2009 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ * @version    $Id:$
+ */
+
+/**
+ * Test helper
+ */
+require_once dirname(__FILE__) . '/../../TestHelper.php';
+
+/**
+ * @see Zend_Validate_CreditCard
+ */
+require_once 'Zend/Validate/CreditCard.php';
+
+/**
+ * @category   Zend
+ * @package    Zend_Validate
+ * @subpackage UnitTests
+ * @copyright  Copyright (c) 2005-2009 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ * @group      Zend_Validate
+ */
+class Zend_Validate_CreditCardTest extends PHPUnit_Framework_TestCase
+{
+    /**
+     * Ensures that the validator follows expected behavior
+     *
+     * @return void
+     */
+    public function testBasic()
+    {
+        $validator      = new Zend_Validate_CreditCard();
+        $valuesExpected = array(
+            '4111111111111111' => true,
+            '5404000000000001' => true,
+            '374200000000004'  => true,
+            '4444555566667777' => false,
+            'ABCDEF'           => false
+            );
+        foreach ($valuesExpected as $input => $result) {
+            $this->assertEquals($result, $validator->isValid($input), 'Test failed at ' . $input);
+        }
+    }
+
+    /**
+     * Ensures that getMessages() returns expected default value
+     *
+     * @return void
+     */
+    public function testGetMessages()
+    {
+        $validator = new Zend_Validate_CreditCard();
+        $this->assertEquals(array(), $validator->getMessages());
+    }
+
+    /**
+     * Ensures that get and setType works as expected
+     *
+     * @return void
+     */
+    public function testGetSetType()
+    {
+        $validator = new Zend_Validate_CreditCard();
+        $this->assertEquals(11, count($validator->getType()));
+
+        $validator->setType(Zend_Validate_CreditCard::MAESTRO);
+        $this->assertEquals(array(Zend_Validate_CreditCard::MAESTRO), $validator->getType());
+
+        $validator->setType(
+            array(
+                Zend_Validate_CreditCard::AMERICAN_EXPRESS,
+                Zend_Validate_CreditCard::MAESTRO
+            )
+        );
+        $this->assertEquals(
+            array(
+                Zend_Validate_CreditCard::AMERICAN_EXPRESS,
+                Zend_Validate_CreditCard::MAESTRO
+            ),
+            $validator->getType()
+        );
+
+        $validator->addType(
+            Zend_Validate_CreditCard::MASTERCARD
+        );
+        $this->assertEquals(
+            array(
+                Zend_Validate_CreditCard::AMERICAN_EXPRESS,
+                Zend_Validate_CreditCard::MAESTRO,
+                Zend_Validate_CreditCard::MASTERCARD
+            ),
+            $validator->getType()
+        );
+    }
+
+    /**
+     * Test specific provider
+     *
+     * @return void
+     */
+    public function testProvider()
+    {
+        $validator      = new Zend_Validate_CreditCard(Zend_Validate_CreditCard::VISA);
+        $valuesExpected = array(
+            '4111111111111111' => true,
+            '5404000000000001' => false,
+            '374200000000004'  => false,
+            '4444555566667777' => false,
+            'ABCDEF'           => false
+            );
+        foreach ($valuesExpected as $input => $result) {
+            $this->assertEquals($result, $validator->isValid($input));
+        }
+    }
+
+    /**
+     * Test non string input
+     *
+     * @return void
+     */
+    public function testIsValidWithNonString()
+    {
+        $validator = new Zend_Validate_CreditCard(Zend_Validate_CreditCard::VISA);
+        $this->assertFalse($validator->isValid(array('something')));
+    }
+
+    /**
+     * Test service class with invalid validation
+     *
+     * @return void
+     */
+    public function testServiceClass()
+    {
+        $validator = new Zend_Validate_CreditCard();
+        $this->assertEquals(null, $validator->getService());
+        $validator->setService(array('Zend_Validate_CreditCardTest', 'staticCallback'));
+        $valuesExpected = array(
+            '4111111111111111' => false,
+            '5404000000000001' => false,
+            '374200000000004'  => false,
+            '4444555566667777' => false,
+            'ABCDEF'           => false
+            );
+        foreach ($valuesExpected as $input => $result) {
+            $this->assertEquals($result, $validator->isValid($input));
+        }
+    }
+
+    /**
+     * Test non string input
+     *
+     * @return void
+     */
+    public function testConstructionWithOptions()
+    {
+        $validator = new Zend_Validate_CreditCard(
+            array(
+                'type' => Zend_Validate_CreditCard::VISA,
+                'service' => array('Zend_Validate_CreditCardTest', 'staticCallback')
+            )
+        );
+
+        $valuesExpected = array(
+            '4111111111111111' => false,
+            '5404000000000001' => false,
+            '374200000000004'  => false,
+            '4444555566667777' => false,
+            'ABCDEF'           => false
+            );
+        foreach ($valuesExpected as $input => $result) {
+            $this->assertEquals($result, $validator->isValid($input));
+        }
+    }
+
+    /**
+     * Test a invalid service class
+     *
+     * @return void
+     */
+    public function testInvalidServiceClass()
+    {
+        $validator = new Zend_Validate_CreditCard();
+        $this->assertEquals(null, $validator->getService());
+        try {
+            $validator->setService(array('Zend_Validate_CreditCardTest', 'nocallback'));
+            $this->fail('Exception expected');
+        } catch(Zend_Exception $e) {
+            $this->assertContains('Invalid callback given', $e->getMessage());
+        }
+    }
+
+    /**
+     * Test a config object
+     *
+     * @return void
+     */
+    public function testConfigObject()
+    {
+        require_once 'Zend/Config.php';
+        $options = array('type' => 'Visa');
+        $config = new Zend_Config($options, false);
+
+        $validator = new Zend_Validate_CreditCard($config);
+        $this->assertEquals(array('Visa'), $validator->getType());
+    }
+
+    /**
+     * Test optional parameters with config object
+     *
+     * @return void
+     */
+    public function testOptionalConstructorParameterByConfigObject()
+    {
+        require_once 'Zend/Config.php';
+        $config = new Zend_Config(array('type' => 'Visa', 'service' => array('Zend_Validate_CreditCardTest', 'staticCallback')));
+
+        $validator = new Zend_Validate_CreditCard($config);
+        $this->assertEquals(array('Visa'), $validator->getType());
+        $this->assertEquals(array('Zend_Validate_CreditCardTest', 'staticCallback'), $validator->getService());
+    }
+
+    /**
+     * Test optional constructor parameters
+     *
+     * @return void
+     */
+    public function testOptionalConstructorParameter()
+    {
+        $validator = new Zend_Validate_CreditCard('Visa', array('Zend_Validate_CreditCardTest', 'staticCallback'));
+        $this->assertEquals(array('Visa'), $validator->getType());
+        $this->assertEquals(array('Zend_Validate_CreditCardTest', 'staticCallback'), $validator->getService());
+    }
+
+    public static function staticCallback($value)
+    {
+        return false;
+    }
+}