/*
* 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 org.apache.wicket.validation.IValidatable;
import org.apache.wicket.validation.IValidationError;
import org.apache.wicket.validation.IValidator;
import org.apache.wicket.validation.ValidationError;
/**
* 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>
* 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 implements IValidator<String>
{
private static final long serialVersionUID = 1L;
/** */
public static enum CreditCard {
/** */
INVALID(null),
/** */
AMERICAN_EXPRESS("American Express"),
/** */
CHINA_UNIONPAY("China UnionPay"),
/** */
DINERS_CLUB_CARTE_BLANCHE("Diners Club Carte Blanche"),
/** */
DINERS_CLUB_INTERNATIONAL("Diners Club International"),
/** */
DINERS_CLUB_US_AND_CANADA("Diners Club US & Canada"),
/** */
DISCOVER_CARD("Discover Card"),
/** */
JCB("JCB"),
/** */
LASER("Laser"),
/** */
MAESTRO("Maestro"),
/** */
MASTERCARD("MasterCard"),
/** */
SOLO("Solo"),
/** */
SWITCH("Switch"),
/** */
VISA("Visa"),
/** */
VISA_ELECTRON("Visa Electron");
private final String name;
CreditCard(String name)
{
this.name = name;
}
}
/** The ID which represents the credit card institute. */
private CreditCard cardId = CreditCard.INVALID;
private boolean failOnUnknown = true;
/**
* Construct.
*/
public CreditCardValidator()
{
}
/**
* Construct.
*
* @param failOnUnkown
*/
public CreditCardValidator(final boolean failOnUnkown)
{
failOnUnknown = failOnUnkown;
}
/**
*
* @return Credit card issuer
*/
public final CreditCard getCardId()
{
return cardId;
}
/**
* Allow subclasses to set the card id
*
* @param cardId
*/
protected void setCardId(final CreditCard cardId)
{
this.cardId = cardId;
}
@Override
public void validate(final IValidatable<String> validatable)
{
final String value = validatable.getValue();
try
{
if (!isLengthAndPrefixCorrect(value))
{
validatable.error(decorate(new ValidationError(this), validatable));
}
}
catch (final NumberFormatException ex)
{
validatable.error(decorate(new ValidationError(this), validatable));
}
}
/**
* Allows subclasses to decorate reported errors
*
* @param error
* @param validatable
* @return decorated error
*/
protected IValidationError decorate(IValidationError error, IValidatable<String> validatable)
{
return error;
}
/**
* 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
*/
protected boolean isLengthAndPrefixCorrect(String creditCardNumber)
{
if (creditCardNumber == null)
{
return false;
}
// 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.length() >= 12) && (creditCardNumber.length() <= 19) &&
isChecksumCorrect(creditCardNumber))
{
if ((failOnUnknown == false) ||
(determineCardId(creditCardNumber) != CreditCard.INVALID))
{
return true;
}
}
return false;
}
/**
* 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
*/
public final CreditCard determineCardId(String creditCardNumber)
{
if (creditCardNumber == null)
{
return CreditCard.INVALID;
}
// 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.length() >= 12) && (creditCardNumber.length() <= 19) &&
isChecksumCorrect(creditCardNumber))
{
cardId = CreditCard.INVALID;
if (cardId == CreditCard.INVALID)
{
cardId = isAmericanExpress(creditCardNumber);
}
if (cardId == CreditCard.INVALID)
{
cardId = isChinaUnionPay(creditCardNumber);
}
if (cardId == CreditCard.INVALID)
{
cardId = isDinersClubCarteBlanche(creditCardNumber);
}
if (cardId == CreditCard.INVALID)
{
cardId = isDinersClubInternational(creditCardNumber);
}
if (cardId == CreditCard.INVALID)
{
cardId = isDinersClubUsAndCanada(creditCardNumber);
}
if (cardId == CreditCard.INVALID)
{
cardId = isDiscoverCard(creditCardNumber);
}
if (cardId == CreditCard.INVALID)
{
cardId = isJCB(creditCardNumber);
}
if (cardId == CreditCard.INVALID)
{
cardId = isLaser(creditCardNumber);
}
if (cardId == CreditCard.INVALID)
{
cardId = isMaestro(creditCardNumber);
}
if (cardId == CreditCard.INVALID)
{
cardId = isMastercard(creditCardNumber);
}
if (cardId == CreditCard.INVALID)
{
cardId = isSolo(creditCardNumber);
}
if (cardId == CreditCard.INVALID)
{
cardId = isSwitch(creditCardNumber);
}
if (cardId == CreditCard.INVALID)
{
cardId = isVisa(creditCardNumber);
}
if (cardId == CreditCard.INVALID)
{
cardId = isVisaElectron(creditCardNumber);
}
}
else
{
cardId = isUnknown(creditCardNumber);
}
return cardId;
}
/**
* 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 The credit card id of the issuer
*/
protected CreditCard isUnknown(String creditCardNumber)
{
return CreditCard.INVALID;
}
/**
* 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
* algorithm.
*
* @param creditCardNumber
* the credit card number as a string
* @return The credit card id of the issuer
*/
private CreditCard isAmericanExpress(String creditCardNumber)
{
if (creditCardNumber.length() == 15 &&
(creditCardNumber.startsWith("34") || creditCardNumber.startsWith("37")))
{
return CreditCard.AMERICAN_EXPRESS;
}
return CreditCard.INVALID;
}
/**
* 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 The credit card id of the issuer
*/
private CreditCard isChinaUnionPay(String creditCardNumber)
{
if ((creditCardNumber.length() >= 16 && creditCardNumber.length() <= 19) &&
(creditCardNumber.startsWith("622")))
{
int firstDigits = Integer.parseInt(creditCardNumber.substring(0, 6));
if (firstDigits >= 622126 && firstDigits <= 622925)
{
return CreditCard.CHINA_UNIONPAY;
}
}
return CreditCard.INVALID;
}
/**
* 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 algorithm.
*
* @param creditCardNumber
* the credit card number as a string
* @return The credit card id of the issuer
*/
private CreditCard isDinersClubCarteBlanche(String creditCardNumber)
{
if (creditCardNumber.length() == 14 && creditCardNumber.startsWith("30"))
{
int firstDigits = Integer.parseInt(creditCardNumber.substring(0, 3));
if (firstDigits >= 300 && firstDigits <= 305)
{
return CreditCard.DINERS_CLUB_CARTE_BLANCHE;
}
}
return CreditCard.INVALID;
}
/**
* 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 algorithm.
*
* @param creditCardNumber
* the credit card number as a string
* @return The credit card id of the issuer
*/
private CreditCard isDinersClubInternational(String creditCardNumber)
{
if (creditCardNumber.length() == 14 && creditCardNumber.startsWith("36"))
{
return CreditCard.DINERS_CLUB_INTERNATIONAL;
}
return CreditCard.INVALID;
}
/**
* 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 algorithm.
*
* @param creditCardNumber
* the credit card number as a string
* @return The credit card id of the issuer
*/
private CreditCard isDinersClubUsAndCanada(String creditCardNumber)
{
if (creditCardNumber.length() == 16 &&
(creditCardNumber.startsWith("54") || creditCardNumber.startsWith("55")))
{
return CreditCard.DINERS_CLUB_US_AND_CANADA;
}
return CreditCard.INVALID;
}
/**
* 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 algorithm.
*
* @param creditCardNumber
* the credit card number as a string
* @return The credit card id of the issuer
*/
private CreditCard isDiscoverCard(String creditCardNumber)
{
if (creditCardNumber.length() == 16 && creditCardNumber.startsWith("6"))
{
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))
{
return CreditCard.DISCOVER_CARD;
}
}
return CreditCard.INVALID;
}
/**
* 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 algorithm.
*
* @param creditCardNumber
* the credit card number as a string
* @return The credit card id of the issuer
*/
private CreditCard isJCB(String creditCardNumber)
{
if (creditCardNumber.length() == 16)
{
int firstFourDigits = Integer.parseInt(creditCardNumber.substring(0, 4));
if (firstFourDigits >= 3528 && firstFourDigits <= 3589)
{
return CreditCard.JCB;
}
}
return CreditCard.INVALID;
}
/**
* 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 algorithm.
*
* @param creditCardNumber
* the credit card number as a string
* @return The credit card id of the issuer
*/
private CreditCard isLaser(String creditCardNumber)
{
if (creditCardNumber.length() >= 16 && creditCardNumber.length() <= 19)
{
if (creditCardNumber.startsWith("6304") || creditCardNumber.startsWith("6706") ||
creditCardNumber.startsWith("6771") || creditCardNumber.startsWith("6709"))
{
return CreditCard.LASER;
}
}
return CreditCard.INVALID;
}
/**
* 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 algorithm.
*
* @param creditCardNumber
* the credit card number as a string
* @return The credit card id of the issuer
*/
private CreditCard isMaestro(String creditCardNumber)
{
if (creditCardNumber.length() >= 12 && creditCardNumber.length() <= 19)
{
if (creditCardNumber.startsWith("5018") || creditCardNumber.startsWith("5020") ||
creditCardNumber.startsWith("5038") || creditCardNumber.startsWith("6304") ||
creditCardNumber.startsWith("6759") || creditCardNumber.startsWith("6761") ||
creditCardNumber.startsWith("6763"))
{
return CreditCard.MAESTRO;
}
}
return CreditCard.INVALID;
}
/**
* 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 algorithm.
*
* @param creditCardNumber
* the credit card number as a string
* @return The credit card id of the issuer
*/
private CreditCard isSolo(String creditCardNumber)
{
if ((creditCardNumber.length() == 16) || (creditCardNumber.length() == 18) ||
(creditCardNumber.length() == 19))
{
if (creditCardNumber.startsWith("6334") || creditCardNumber.startsWith("6767"))
{
return CreditCard.SOLO;
}
}
return CreditCard.INVALID;
}
/**
* 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 algorithm.
*
* @param creditCardNumber
* the credit card number as a string
* @return The credit card id of the issuer
*/
private CreditCard isSwitch(String creditCardNumber)
{
if ((creditCardNumber.length() == 16 || creditCardNumber.length() == 18 || creditCardNumber.length() == 19))
{
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"))
{
return CreditCard.SWITCH;
}
}
return CreditCard.INVALID;
}
/**
* 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 algorithm.
*
* @param creditCardNumber
* the credit card number as a string
* @return The credit card id of the issuer
*/
private CreditCard isVisa(String creditCardNumber)
{
if (creditCardNumber.length() == 13 || creditCardNumber.length() == 16)
{
if (creditCardNumber.startsWith("4"))
{
return CreditCard.VISA;
}
}
return CreditCard.INVALID;
}
/**
* 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 algorithm.
*
* @param creditCardNumber
* the credit card number as a string
* @return The credit card id of the issuer
*/
private CreditCard isVisaElectron(String creditCardNumber)
{
if (creditCardNumber.length() == 16 &&
(creditCardNumber.startsWith("417500") || creditCardNumber.startsWith("4917") ||
creditCardNumber.startsWith("4913") || creditCardNumber.startsWith("4508") || creditCardNumber.startsWith("4844")))
{
return CreditCard.VISA_ELECTRON;
}
return CreditCard.INVALID;
}
/**
* 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 algorithm.
*
* @param creditCardNumber
* the credit card number as a string
* @return The credit card id of the issuer
*/
private CreditCard isMastercard(String creditCardNumber)
{
if (creditCardNumber.length() == 16)
{
int firstTwoDigits = Integer.parseInt(creditCardNumber.substring(0, 2));
if (firstTwoDigits >= 51 && firstTwoDigits <= 55)
{
return CreditCard.MASTERCARD;
}
}
return CreditCard.INVALID;
}
/**
* 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>
*/
protected final boolean isChecksumCorrect(final String creditCardNumber)
{
int nulOffset = '0';
int sum = 0;
for (int i = 1; i <= creditCardNumber.length(); i++)
{
int currentDigit = creditCardNumber.charAt(creditCardNumber.length() - i) - nulOffset;
if ((i % 2) == 0)
{
currentDigit *= 2;
currentDigit = currentDigit > 9 ? currentDigit - 9 : currentDigit;
sum += currentDigit;
}
else
{
sum += currentDigit;
}
}
return (sum % 10) == 0;
}
}