You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@wicket.apache.org by jd...@apache.org on 2009/11/08 08:50:07 UTC

svn commit: r833843 - in /wicket/trunk/wicket/src: main/java/org/apache/wicket/validation/validator/CreditCardValidator.java test/java/org/apache/wicket/validation/validator/CreditCardValidatorTest.java

Author: jdonnerstag
Date: Sun Nov  8 07:50:07 2009
New Revision: 833843

URL: http://svn.apache.org/viewvc?rev=833843&view=rev
Log:
fixed CreditCardValidator accepts invalid inputs
Issue: WICKET-2552

Added:
    wicket/trunk/wicket/src/test/java/org/apache/wicket/validation/validator/CreditCardValidatorTest.java
Modified:
    wicket/trunk/wicket/src/main/java/org/apache/wicket/validation/validator/CreditCardValidator.java

Modified: wicket/trunk/wicket/src/main/java/org/apache/wicket/validation/validator/CreditCardValidator.java
URL: http://svn.apache.org/viewvc/wicket/trunk/wicket/src/main/java/org/apache/wicket/validation/validator/CreditCardValidator.java?rev=833843&r1=833842&r2=833843&view=diff
==============================================================================
--- wicket/trunk/wicket/src/main/java/org/apache/wicket/validation/validator/CreditCardValidator.java (original)
+++ wicket/trunk/wicket/src/main/java/org/apache/wicket/validation/validator/CreditCardValidator.java Sun Nov  8 07:50:07 2009
@@ -19,27 +19,586 @@
 import org.apache.wicket.validation.IValidatable;
 
 /**
- * Performs the so-called "mod 10" algorithm to check the validity of credit card numbers such as
- * VISA.
+ * Checks if a credit card number is valid. The number will be checked for "American Express",
+ * "China UnionPay", "Diners Club Carte Blanche", "Diners Club International",
+ * "Diners Club US & Canada", "Discover Card", "JCB", "Laser", "Maestro", "MasterCard", "Solo",
+ * "Switch", "Visa" and "Visa Electron". If none of those apply to the credit card number, the
+ * credit card number is considered invalid.
+ * 
  * <p>
- * In addition to this, the credit card number can be further validated by its length and prefix,
- * but those properties depend upon the credit card type, and such validation is not performed by
- * this validator.
+ * Card prefixes and lengths have been taken from <a
+ * href="http://en.wikipedia.org/w/index.php?title=Bank_card_number&oldid=322132931">Wikipedia</a>.
  * 
  * @author Johan Compagner
+ * @author Joachim F. Rohde
  * @since 1.2.6
  */
 public class CreditCardValidator extends AbstractValidator<String>
 {
 	private static final long serialVersionUID = 1L;
 
+	/** The credit card number, which should be validated. */
+	private String creditCardNumber = null;
+
+	/** The ID which represents the credit card institute. */
+	private int cardId = -1;
+
+	/** */
+	public static final int INVALID = -1;
+	public static final int AMERICAN_EXPRESS = 0;
+	public static final int CHINA_UNIONPAY = 1;
+	public static final int DINERS_CLUB_CARTE_BLANCHE = 2;
+	public static final int DINERS_CLUB_INTERNATIONAL = 3;
+	public static final int DINERS_CLUB_US_AND_CANADA = 4;
+	public static final int DISCOVER_CARD = 5;
+	public static final int JCB = 6;
+	public static final int LASER = 7;
+	public static final int MAESTRO = 8;
+	public static final int MASTERCARD = 9;
+	public static final int SOLO = 10;
+	public static final int SWITCH = 11;
+	public static final int VISA = 12;
+	public static final int VISA_ELECTRON = 13;
+
+	private static final String[] creditCardNames = { "American Express", "China UnionPay",
+			"Diners Club Carte Blanche", "Diners Club International", "Diners Club US & Canada",
+			"Discover Card", "JCB", "Laser", "Maestro", "MasterCard", "Solo", "Switch", "Visa",
+			"Visa Electron" };
+
 	/**
 	 * @see AbstractValidator#onValidate(IValidatable)
 	 */
 	@Override
 	protected void onValidate(IValidatable<String> validatable)
 	{
-		String input = (validatable.getValue());
+		creditCardNumber = validatable.getValue();
+		if (!isLengthAndPrefixCorrect(creditCardNumber))
+		{
+			error(validatable);
+		}
+	}
+
+	/**
+	 * Checks if the credit card number can be determined as a valid number.
+	 * 
+	 * @param creditCardNumber
+	 *            the credit card number as a string
+	 * @return <code>TRUE</code> if the credit card number could be determined as a valid number,
+	 *         else <code>FALSE</code> is returned
+	 */
+	private boolean isLengthAndPrefixCorrect(String creditCardNumber)
+	{
+		if (creditCardNumber != null)
+		{
+			// strip spaces and dashes
+			creditCardNumber = creditCardNumber.replaceAll("[ -]", "");
+		}
+
+		// the length of the credit card number has to be between 12 and 19.
+		// else the number is invalid.
+		if (creditCardNumber != null && creditCardNumber.length() >= 12 &&
+			creditCardNumber.length() <= 19)
+		{
+			if (isAmericanExpress(creditCardNumber))
+			{
+				return true;
+			}
+			else if (isChinaUnionPay(creditCardNumber))
+			{
+				return true;
+			}
+			else if (isDinersClubCarteBlanche(creditCardNumber))
+			{
+				return true;
+			}
+			else if (isDinersClubInternational(creditCardNumber))
+			{
+				return true;
+			}
+			else if (isDinersClubUsAndCanada(creditCardNumber))
+			{
+				return true;
+			}
+			else if (isDiscoverCard(creditCardNumber))
+			{
+				return true;
+			}
+			else if (isJCB(creditCardNumber))
+			{
+				return true;
+			}
+			else if (isLaser(creditCardNumber))
+			{
+				return true;
+			}
+			else if (isMaestro(creditCardNumber))
+			{
+				return true;
+			}
+			else if (isMastercard(creditCardNumber))
+			{
+				return true;
+			}
+			else if (isSolo(creditCardNumber))
+			{
+				return true;
+			}
+			else if (isSwitch(creditCardNumber))
+			{
+				return true;
+			}
+			else if (isVisa(creditCardNumber))
+			{
+				return true;
+			}
+			else if (isVisaElectron(creditCardNumber))
+			{
+				return true;
+			}
+			else if (isUnknown(creditCardNumber))
+			{
+				return true;
+			}
+		}
+
+		return false;
+	}
+
+	/**
+	 * Can be used (subclassed) to extend the test with a credit card not yet known by the
+	 * validator.
+	 * 
+	 * @param creditCardNumber
+	 *            the credit card number as a string
+	 * @return <code>TRUE</code> if the credit card number seems to be a valid American Express
+	 *         number. Else <code>FALSE</code> will be returned
+	 */
+	protected boolean isUnknown(String creditCardNumber)
+	{
+		return false;
+	}
+
+	/**
+	 * Check if the credit card is an American Express. An American Express number has to start with
+	 * 34 or 37 and has to have a length of 15. The number has to be validated with the Luhn
+	 * alorithm.
+	 * 
+	 * @param creditCardNumber
+	 *            the credit card number as a string
+	 * @return <code>TRUE</code> if the credit card number seems to be a valid American Express
+	 *         number. Else <code>FALSE</code> will be returned
+	 */
+	private boolean isAmericanExpress(String creditCardNumber)
+	{
+		cardId = CreditCardValidator.INVALID;
+		boolean returnValue = false;
+
+		if (creditCardNumber.length() == 15 &&
+			(creditCardNumber.startsWith("34") || creditCardNumber.startsWith("37")))
+		{
+			if (isChecksumCorrect(creditCardNumber))
+			{
+				cardId = CreditCardValidator.AMERICAN_EXPRESS;
+				returnValue = true;
+			}
+		}
+
+		return returnValue;
+	}
+
+	/**
+	 * Check if the credit card is a China UnionPay. A China UnionPay number has to start with 622
+	 * (622126-622925) and has to have a length between 16 and 19. No further validation takes
+	 * place.<br/>
+	 * <br/>
+	 * 
+	 * @param creditCardNumber
+	 *            the credit card number as a string
+	 * @return <code>TRUE</code> if the credit card number seems to be a valid China UnionPay
+	 *         number. Else <code>FALSE</code> will be returned.
+	 */
+	private boolean isChinaUnionPay(String creditCardNumber)
+	{
+		cardId = CreditCardValidator.INVALID;
+		boolean returnValue = false;
+
+		if ((creditCardNumber.length() >= 16 && creditCardNumber.length() <= 19) &&
+			(creditCardNumber.startsWith("622")))
+		{
+			int firstDigits = Integer.parseInt(creditCardNumber.substring(0, 5));
+			if (firstDigits >= 622126 && firstDigits <= 622925)
+			{
+				cardId = CreditCardValidator.CHINA_UNIONPAY;
+				returnValue = true;
+			}
+		}
+
+		return returnValue;
+	}
+
+	/**
+	 * Check if the credit card is a Diners Club Carte Blanche. A Diners Club Carte Blanche number
+	 * has to start with a number between 300 and 305 and has to have a length of 14. The number has
+	 * to be validated with the Luhn alorithm.
+	 * 
+	 * @param creditCardNumber
+	 *            the credit card number as a string
+	 * @return <code>TRUE</code> if the credit card number seems to be a valid Diners Club Carte
+	 *         Blanche number. Else <code>FALSE</code> will be returned
+	 */
+	private boolean isDinersClubCarteBlanche(String creditCardNumber)
+	{
+		cardId = CreditCardValidator.INVALID;
+		boolean returnValue = false;
+
+		if (creditCardNumber.length() == 14 && creditCardNumber.startsWith("30"))
+		{
+			int firstDigits = Integer.parseInt(creditCardNumber.substring(0, 3));
+			if (firstDigits >= 300 && firstDigits <= 305 && isChecksumCorrect(creditCardNumber))
+			{
+				cardId = CreditCardValidator.DINERS_CLUB_CARTE_BLANCHE;
+				returnValue = true;
+			}
+		}
+
+		return returnValue;
+	}
+
+	/**
+	 * Check if the credit card is a Diners Club International. A Diners Club International number
+	 * has to start with the number 36 and has to have a length of 14. The number has to be
+	 * validated with the Luhn alorithm.
+	 * 
+	 * @param creditCardNumber
+	 *            the credit card number as a string
+	 * @return <code>TRUE</code> if the credit card number seems to be a valid Diners Club
+	 *         International number. Else <code>FALSE</code> will be returned
+	 */
+	private boolean isDinersClubInternational(String creditCardNumber)
+	{
+		cardId = CreditCardValidator.INVALID;
+		boolean returnValue = false;
+
+		if (creditCardNumber.length() == 14 && creditCardNumber.startsWith("36") &&
+			isChecksumCorrect(creditCardNumber))
+		{
+			cardId = CreditCardValidator.DINERS_CLUB_INTERNATIONAL;
+			returnValue = true;
+		}
+
+		return returnValue;
+	}
+
+	/**
+	 * Check if the credit card is a Diners Club US & Canada. A Diners Club US & Canada number has
+	 * to start with the number 54 or 55 and has to have a length of 16. The number has to be
+	 * validated with the Luhn alorithm.
+	 * 
+	 * @param creditCardNumber
+	 *            the credit card number as a string
+	 * @return <code>TRUE</code> if the credit card number seems to be a valid Diners Club US &
+	 *         Canada number. Else <code>FALSE</code> will be returned
+	 */
+	private boolean isDinersClubUsAndCanada(String creditCardNumber)
+	{
+		cardId = CreditCardValidator.INVALID;
+		boolean returnValue = false;
+
+		if (creditCardNumber.length() == 16 &&
+			(creditCardNumber.startsWith("54") || creditCardNumber.startsWith("55")) &&
+			isChecksumCorrect(creditCardNumber))
+		{
+			cardId = CreditCardValidator.DINERS_CLUB_US_AND_CANADA;
+			returnValue = true;
+		}
+
+		return returnValue;
+	}
+
+	/**
+	 * Check if the credit card is a Discover Card. A Discover Card number has to start with 6011,
+	 * 622126-622925, 644-649 or 65 and has to have a length of 16. The number has to be validated
+	 * with the Luhn alorithm.
+	 * 
+	 * @param creditCardNumber
+	 *            the credit card number as a string
+	 * @return <code>TRUE</code> if the credit card number seems to be a valid Discover Card number.
+	 *         Else <code>FALSE</code> will be returned
+	 */
+	private boolean isDiscoverCard(String creditCardNumber)
+	{
+		cardId = CreditCardValidator.INVALID;
+		boolean returnValue = false;
+
+		if (creditCardNumber.length() == 16 && creditCardNumber.startsWith("6") &&
+			isChecksumCorrect(creditCardNumber))
+		{
+			int firstThreeDigits = Integer.parseInt(creditCardNumber.substring(0, 3));
+			int firstSixDigits = Integer.parseInt(creditCardNumber.substring(0, 6));
+			if (creditCardNumber.startsWith("6011") || creditCardNumber.startsWith("65") ||
+				(firstThreeDigits >= 644 && firstThreeDigits <= 649) ||
+				(firstSixDigits >= 622126 && firstSixDigits <= 622925))
+			{
+				cardId = CreditCardValidator.DISCOVER_CARD;
+				returnValue = true;
+			}
+		}
+
+		return returnValue;
+	}
+
+	/**
+	 * Check if the credit card is a JCB. A JCB number has to start with a number between 3528 and
+	 * 3589 and has to have a length of 16. The number has to be validated with the Luhn alorithm.
+	 * 
+	 * @param creditCardNumber
+	 *            the credit card number as a string
+	 * @return <code>TRUE</code> if the credit card number seems to be a valid JCB number. Else
+	 *         <code>FALSE</code> will be returned
+	 */
+	private boolean isJCB(String creditCardNumber)
+	{
+		cardId = CreditCardValidator.INVALID;
+		boolean returnValue = false;
+
+		if (creditCardNumber.length() == 16 && isChecksumCorrect(creditCardNumber))
+		{
+			int firstFourDigits = Integer.parseInt(creditCardNumber.substring(0, 4));
+			if (firstFourDigits >= 3528 && firstFourDigits <= 3589)
+			{
+				cardId = CreditCardValidator.JCB;
+				returnValue = true;
+			}
+		}
+
+		return returnValue;
+	}
+
+	/**
+	 * Check if the credit card is a Laser. A Laser number has to start with 6304, 6706, 6771 or
+	 * 6709 and has to have a length between 16 and 19 digits. The number has to be validated with
+	 * the Luhn alorithm.
+	 * 
+	 * @param creditCardNumber
+	 *            the credit card number as a string
+	 * @return <code>TRUE</code> if the credit card number seems to be a valid Laser number. Else
+	 *         <code>FALSE</code> will be returned
+	 */
+	private boolean isLaser(String creditCardNumber)
+	{
+		cardId = CreditCardValidator.INVALID;
+		boolean returnValue = false;
+
+		if (creditCardNumber.length() >= 16 && creditCardNumber.length() <= 19 &&
+			isChecksumCorrect(creditCardNumber))
+		{
+			if (creditCardNumber.startsWith("6304") || creditCardNumber.startsWith("6706") ||
+				creditCardNumber.startsWith("6771") || creditCardNumber.startsWith("6709"))
+			{
+				cardId = CreditCardValidator.LASER;
+				returnValue = true;
+			}
+		}
+
+		return returnValue;
+	}
+
+	/**
+	 * Check if the credit card is a Maestro. A Maestro number has to start with
+	 * 5018,5020,5038,6304,6759,6761 or 6763 and has to have a length between 12 and 19 digits. The
+	 * number has to be validated with the Luhn alorithm.
+	 * 
+	 * @param creditCardNumber
+	 *            the credit card number as a string
+	 * @return <code>TRUE</code> if the credit card number seems to be a valid Maestro number. Else
+	 *         <code>FALSE</code> will be returned
+	 */
+	private boolean isMaestro(String creditCardNumber)
+	{
+		cardId = CreditCardValidator.INVALID;
+		boolean returnValue = false;
+
+		if (creditCardNumber.length() >= 12 && creditCardNumber.length() <= 19 &&
+			isChecksumCorrect(creditCardNumber))
+		{
+			if (creditCardNumber.startsWith("5018") || creditCardNumber.startsWith("5020") ||
+				creditCardNumber.startsWith("5038") || creditCardNumber.startsWith("6304") ||
+				creditCardNumber.startsWith("6759") || creditCardNumber.startsWith("6761") ||
+				creditCardNumber.startsWith("6763"))
+			{
+				cardId = CreditCardValidator.MAESTRO;
+				returnValue = true;
+			}
+		}
+
+		return returnValue;
+	}
+
+	/**
+	 * Check if the credit card is a Solo. A Solo number has to start with 6334 or 6767 and has to
+	 * have a length of 16, 18 or 19 digits. The number has to be validated with the Luhn alorithm.
+	 * 
+	 * @param creditCardNumber
+	 *            the credit card number as a string
+	 * @return <code>TRUE</code> if the credit card number seems to be a valid Solo number. Else
+	 *         <code>FALSE</code> will be returned
+	 */
+	private boolean isSolo(String creditCardNumber)
+	{
+		cardId = CreditCardValidator.INVALID;
+		boolean returnValue = false;
+
+		if ((creditCardNumber.length() == 16 || creditCardNumber.length() == 18 || creditCardNumber.length() == 19) &&
+			isChecksumCorrect(creditCardNumber))
+		{
+			if (creditCardNumber.startsWith("6334") || creditCardNumber.startsWith("6767"))
+			{
+				cardId = CreditCardValidator.SOLO;
+				returnValue = true;
+			}
+		}
+
+		return returnValue;
+	}
+
+	/**
+	 * Check if the credit card is a Switch. A Switch number has to start with
+	 * 4903,4905,4911,4936,564182,633110,6333 or 6759 and has to have a length of 16, 18 or 19
+	 * digits. The number has to be validated with the Luhn alorithm.
+	 * 
+	 * @param creditCardNumber
+	 *            the credit card number as a string
+	 * @return <code>TRUE</code> if the credit card number seems to be a valid Switch number. Else
+	 *         <code>FALSE</code> will be returned
+	 */
+	private boolean isSwitch(String creditCardNumber)
+	{
+		cardId = CreditCardValidator.INVALID;
+		boolean returnValue = false;
+
+		if ((creditCardNumber.length() == 16 || creditCardNumber.length() == 18 || creditCardNumber.length() == 19) &&
+			isChecksumCorrect(creditCardNumber))
+		{
+			if (creditCardNumber.startsWith("4903") || creditCardNumber.startsWith("4905") ||
+				creditCardNumber.startsWith("4911") || creditCardNumber.startsWith("4936") ||
+				creditCardNumber.startsWith("564182") || creditCardNumber.startsWith("633110") ||
+				creditCardNumber.startsWith("6333") || creditCardNumber.startsWith("6759"))
+			{
+				cardId = CreditCardValidator.SWITCH;
+				returnValue = true;
+			}
+		}
+
+		return returnValue;
+	}
+
+	/**
+	 * Check if the credit card is a Visa. A Visa number has to start with a 4 and has to have a
+	 * length of 13 or 16 digits. The number has to be validated with the Luhn alorithm.
+	 * 
+	 * @param creditCardNumber
+	 *            the credit card number as a string
+	 * @return <code>TRUE</code> if the credit card number seems to be a valid Visa number. Else
+	 *         <code>FALSE</code> will be returned
+	 */
+	private boolean isVisa(String creditCardNumber)
+	{
+		cardId = CreditCardValidator.INVALID;
+		boolean returnValue = false;
+
+		if (creditCardNumber.length() == 13 || creditCardNumber.length() == 16)
+		{
+			if (creditCardNumber.startsWith("4"))
+			{
+				cardId = CreditCardValidator.SWITCH;
+				returnValue = true;
+			}
+		}
+
+		return returnValue;
+	}
+
+	/**
+	 * Check if the credit card is a Visa Electron. A Visa Electron number has to start with
+	 * 417500,4917,4913,4508 or 4844 and has to have a length of 16 digits. The number has to be
+	 * validated with the Luhn alorithm.
+	 * 
+	 * @param creditCardNumber
+	 *            the credit card number as a string
+	 * @return <code>TRUE</code> if the credit card number seems to be a valid Visa Electron number.
+	 *         Else <code>FALSE</code> will be returned
+	 */
+	private boolean isVisaElectron(String creditCardNumber)
+	{
+		cardId = CreditCardValidator.INVALID;
+		boolean returnValue = false;
+
+		if (creditCardNumber.length() == 16 &&
+			(creditCardNumber.startsWith("417500") || creditCardNumber.startsWith("4917") ||
+				creditCardNumber.startsWith("4913") || creditCardNumber.startsWith("4508") || creditCardNumber.startsWith("4844")))
+		{
+			cardId = CreditCardValidator.VISA_ELECTRON;
+			returnValue = true;
+		}
+
+		return returnValue;
+	}
+
+	/**
+	 * Check if the credit card is a Mastercard. A Mastercard number has to start with a number
+	 * between 51 and 55 and has to have a length of 16. The number has to be validated with the
+	 * Luhn alorithm.
+	 * 
+	 * @param creditCardNumber
+	 *            the credit card number as a string
+	 * @return <code>TRUE</code> if the credit card number seems to be a valid Mastercard number.
+	 *         Else <code>FALSE</code> will be returned
+	 */
+	private boolean isMastercard(String creditCardNumber)
+	{
+		cardId = CreditCardValidator.INVALID;
+		boolean returnValue = false;
+
+		if (creditCardNumber.length() == 16 && isChecksumCorrect(creditCardNumber))
+		{
+			int firstTwoDigits = Integer.parseInt(creditCardNumber.substring(0, 2));
+			if (firstTwoDigits >= 51 && firstTwoDigits <= 55)
+			{
+				cardId = CreditCardValidator.MASTERCARD;
+				returnValue = true;
+			}
+		}
+
+		return returnValue;
+	}
+
+	/**
+	 * Just used for debugging purposes.<br>
+	 * Due to re-branding (e.g. Switch was re-branded as Maestro in mid 2007) some rules might
+	 * overlap, but those overlappings are not considered. So it might happen, that a Switch-card is
+	 * identified as a Maestro. <br>
+	 * So you shouldn't rely on the name which is returned here.
+	 * 
+	 * @return the name of the credit card if it could be determined, else an empty string
+	 */
+	private String getCardName()
+	{
+		return (cardId > -1 && cardId < creditCardNames.length ? creditCardNames[cardId] : "");
+	}
+
+	/**
+	 * Calculates the checksum of a credit card number using the Luhn algorithm (the so-called
+	 * "mod 10" algorithm).
+	 * 
+	 * @param creditCardNumber
+	 *            the credit card number for which the checksum should be calculated
+	 * @return <code>TRUE</code> if the checksum for the given credit card number is valid, else
+	 *         return <code>FALSE</code>
+	 * @see <a href="http://en.wikipedia.org/wiki/Luhn_algorithm">Wikipedie - Luhn algorithm</a>
+	 */
+	private boolean isChecksumCorrect(String creditCardNumber)
+	{
+		String input = creditCardNumber;
 		String numberToCheck = input.replaceAll("[ -]", "");
 		int nulOffset = '0';
 		int sum = 0;
@@ -57,9 +616,7 @@
 				sum += currentDigit;
 			}
 		}
-		if (!((sum % 10) == 0))
-		{
-			error(validatable);
-		}
+
+		return (sum % 10) == 0;
 	}
 }

Added: wicket/trunk/wicket/src/test/java/org/apache/wicket/validation/validator/CreditCardValidatorTest.java
URL: http://svn.apache.org/viewvc/wicket/trunk/wicket/src/test/java/org/apache/wicket/validation/validator/CreditCardValidatorTest.java?rev=833843&view=auto
==============================================================================
--- wicket/trunk/wicket/src/test/java/org/apache/wicket/validation/validator/CreditCardValidatorTest.java (added)
+++ wicket/trunk/wicket/src/test/java/org/apache/wicket/validation/validator/CreditCardValidatorTest.java Sun Nov  8 07:50:07 2009
@@ -0,0 +1,164 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.wicket.validation.validator;
+
+import junit.framework.TestCase;
+
+import org.apache.wicket.validation.IValidatable;
+import org.apache.wicket.validation.Validatable;
+
+/**
+ * Tests a few valid and invalid credit card numbers.
+ * 
+ * @author Joachim F. Rohde
+ */
+public class CreditCardValidatorTest extends TestCase
+{
+	/**
+	 * Constructor.
+	 */
+	public CreditCardValidatorTest()
+	{
+		super();
+	}
+
+	/**
+	 * Constructor.
+	 * 
+	 * @param name
+	 */
+	public CreditCardValidatorTest(String name)
+	{
+		super(name);
+	}
+
+	/**
+	 * Tests a couple of credit card numbers that shouldn't be valid.
+	 */
+	public void testInvalidCreditCardNumbers()
+	{
+		// null value
+		CreditCardValidator test = new CreditCardValidator();
+		IValidatable<String> validatable = new Validatable(null);
+		test.onValidate(validatable);
+		assertEquals(false, validatable.isValid());
+
+		// too short
+		validatable = new Validatable("9845");
+		test.onValidate(validatable);
+		assertEquals(false, validatable.isValid());
+
+		// too long
+		validatable = new Validatable("1234678910111213141516");
+		test.onValidate(validatable);
+		assertEquals(false, validatable.isValid());
+
+		// contains a char
+		validatable = new Validatable("3782822X6310005");
+		test.onValidate(validatable);
+		assertEquals(false, validatable.isValid());
+
+		// invalid number
+		validatable = new Validatable("840898920205250");
+		test.onValidate(validatable);
+		assertEquals(false, validatable.isValid());
+	}
+
+	/**
+	 * Tests a couple of credit card numbers that should be valid. Those numbers has been taken from
+	 * https://www.paypal.com/en_US/vhelp/paypalmanager_help/credit_card_numbers.htm
+	 */
+	public void testValidCreditCardNumbers()
+	{
+		// American Express
+		CreditCardValidator test = new CreditCardValidator();
+		IValidatable<String> validatable = new Validatable("378282246310005");
+		test.onValidate(validatable);
+		assertEquals(true, validatable.isValid());
+
+		// American Express
+		validatable = new Validatable("371449635398431");
+		test.onValidate(validatable);
+		assertEquals(true, validatable.isValid());
+
+		// American Express Corporate
+		validatable = new Validatable("378734493671000");
+		test.onValidate(validatable);
+		assertEquals(true, validatable.isValid());
+
+		// American Express Corporate with dashes and spaces (should be filtered
+		// and are therefor legal)
+		validatable = new Validatable("378 - 7344-9367 1000");
+		test.onValidate(validatable);
+		assertEquals(true, validatable.isValid());
+
+		// Diners Club
+		validatable = new Validatable("30569309025904");
+		test.onValidate(validatable);
+		assertEquals(true, validatable.isValid());
+
+		// Discover
+		validatable = new Validatable("6011111111111117");
+		test.onValidate(validatable);
+		assertEquals(true, validatable.isValid());
+
+		// Discover
+		validatable = new Validatable("6011000990139424");
+		test.onValidate(validatable);
+		assertEquals(true, validatable.isValid());
+
+		// JCB
+		validatable = new Validatable("3530111333300000");
+		test.onValidate(validatable);
+		assertEquals(true, validatable.isValid());
+
+		// JCB
+		validatable = new Validatable("3566002020360505");
+		test.onValidate(validatable);
+		assertEquals(true, validatable.isValid());
+
+		// Mastercard
+		validatable = new Validatable("5555555555554444");
+		test.onValidate(validatable);
+		assertEquals(true, validatable.isValid());
+
+		// Mastercard
+		validatable = new Validatable("5105105105105100");
+		test.onValidate(validatable);
+		assertEquals(true, validatable.isValid());
+
+		// Visa
+		validatable = new Validatable("4111111111111111");
+		test.onValidate(validatable);
+		assertEquals(true, validatable.isValid());
+
+		// Visa
+		validatable = new Validatable("4012888888881881");
+		test.onValidate(validatable);
+		assertEquals(true, validatable.isValid());
+
+		// Visa
+		validatable = new Validatable("4222222222222");
+		test.onValidate(validatable);
+		assertEquals(true, validatable.isValid());
+
+		// Switch / Solo
+		validatable = new Validatable("6331101999990016");
+		test.onValidate(validatable);
+		assertEquals(true, validatable.isValid());
+	}
+}