package jp.webpay.android.token.validator; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.regex.Pattern; import jp.webpay.android.token.model.CardType; /** * Static class validates card numbers. * <p> * This class tests * <ul> * <li>the card number does not contain invalid characters,</li> * <li>the card number passes Luhn check, and</li> * <li>the card number is of supported card brands.</li> * </ul> */ public class CardNumberValidator { private static final char SEPARATOR = '-'; private static final Map<CardType, Pattern> CARD_TYPE_REGEXP = new HashMap<CardType, Pattern>(); static { CARD_TYPE_REGEXP.put(CardType.VISA, Pattern.compile("\\A4[0-9]{12}(?:[0-9]{3})?\\z")); CARD_TYPE_REGEXP.put(CardType.AMERICAN_EXPRESS, Pattern.compile("\\A3[47][0-9]{13}\\z")); CARD_TYPE_REGEXP.put(CardType.MASTERCARD, Pattern.compile("\\A5[1-5][0-9]{14}\\z")); CARD_TYPE_REGEXP.put(CardType.JCB, Pattern.compile("\\A(?:2131|1800|35\\d{3})\\d{11}\\z")); CARD_TYPE_REGEXP.put(CardType.DINERS_CLUB, Pattern.compile("\\A3(?:0[0-5]|[68][0-9])[0-9]{11}\\z")); } /** * Validate that the number is acceptable as an account number of card types supported by WebPay * @param number Card number composed of digits and hyphens. * @return true if card number is valid */ public static boolean isValid(String number) { return isValid(number, Arrays.asList(CardType.values())); } /** * Validate that the number is acceptable as an account number of listed card types * @param number Card number composed of digits, space and hyphens. * @param cardTypes List of available card types * @return true if card number is valid */ public static boolean isValid(String number, List<CardType> cardTypes) { if (cardTypes == null) return false; number = sanitize(number); return number != null && passLuhnTest(number) && matchNumberRegexp(number, cardTypes); } private static String sanitize(String number) { if (number == null) return null; StringBuilder builder = new StringBuilder(); for (int i = 0; i < number.length(); i++) { char ch = number.charAt(i); if (ch == SEPARATOR) continue; if (ch >= '0' && ch <= '9') { builder.append(ch); continue; } return null; } return builder.toString(); } private static boolean passLuhnTest(String number) { int s1 = 0, s2 = 0; String reverse = new StringBuffer(number).reverse().toString(); for (int i = 0 ;i < reverse.length();i++){ int digit = Character.digit(reverse.charAt(i), 10); if (i % 2 == 0) { s1 += digit; } else { s2 += 2 * digit; if (digit >= 5) { s2 -= 9; } } } return (s1 + s2) % 10 == 0; } private static boolean matchNumberRegexp(String number, List<CardType> cardTypes) { for (CardType cardType : cardTypes) { Pattern pattern = CARD_TYPE_REGEXP.get(cardType); if (pattern == null) continue; if (pattern.matcher(number).matches()) return true; } return false; } }