001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * https://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.commons.validator; 018 019import java.util.ArrayList; 020import java.util.Collection; 021 022import org.apache.commons.validator.util.Flags; 023 024/** 025 * Perform credit card validations. 026 * 027 * <p> 028 * By default, all supported card types are allowed. You can specify which 029 * cards should pass validation by configuring the validation options. For 030 * example, 031 * </p> 032 * 033 * <pre> 034 * {@code CreditCardValidator ccv = new CreditCardValidator(CreditCardValidator.AMEX + CreditCardValidator.VISA);} 035 * </pre> 036 * 037 * <p> 038 * configures the validator to only pass American Express and Visa cards. 039 * If a card type is not directly supported by this class, you can implement 040 * the CreditCardType interface and pass an instance into the 041 * {@code addAllowedCardType} method. 042 * </p> 043 * 044 * <p> 045 * For a similar implementation in Perl, reference Sean M. Burke's 046 * <a href="https://www.speech.cs.cmu.edu/~sburke/pub/luhn_lib.html">script</a>. 047 * </p> 048 * 049 * @since 1.1 050 * @deprecated Use the new CreditCardValidator in the routines package. This class 051 * will be removed in a future release. 052 */ 053// CHECKSTYLE:OFF (deprecated code) 054@Deprecated 055public class CreditCardValidator { 056 057 private static class Amex implements CreditCardType { 058 static final Amex INSTANCE = new Amex(); 059 private static final String PREFIX = "34,37,"; 060 @Override 061 public boolean matches(final String card) { 062 final String prefix2 = card.substring(0, 2) + ","; 063 return PREFIX.contains(prefix2) && card.length() == 15; 064 } 065 } 066 067 /** 068 * CreditCardType implementations define how validation is performed 069 * for one type/brand of credit card. 070 * 071 * @since 1.1.2 072 */ 073 public interface CreditCardType { 074 075 /** 076 * Returns true if the card number matches this type of credit 077 * card. Note that this method is <strong>not</strong> responsible 078 * for analyzing the general form of the card number because 079 * {@code CreditCardValidator} performs those checks before 080 * calling this method. It is generally only required to valid the 081 * length and prefix of the number to determine if it's the correct 082 * type. 083 * 084 * @param card The card number, never null. 085 * @return true if the number matches. 086 */ 087 boolean matches(String card); 088 089 } 090 091 private static class Discover implements CreditCardType { 092 static final Discover INSTANCE = new Discover(); 093 private static final String PREFIX = "6011"; 094 @Override 095 public boolean matches(final String card) { 096 return card.substring(0, 4).equals(PREFIX) && card.length() == 16; 097 } 098 } 099 100 private static class Mastercard implements CreditCardType { 101 static final Mastercard INSTANCE = new Mastercard(); 102 private static final String PREFIX = "51,52,53,54,55,"; 103 @Override 104 public boolean matches(final String card) { 105 final String prefix2 = card.substring(0, 2) + ","; 106 return PREFIX.contains(prefix2) && card.length() == 16; 107 } 108 } 109 110 /** 111 * Change to support Visa Carte Blue used in France 112 * has been removed - see Bug 35926 113 */ 114 private static class Visa implements CreditCardType { 115 static final Visa INSTANCE = new Visa(); 116 private static final String PREFIX = "4"; 117 118 @Override 119 public boolean matches(final String card) { 120 return card.substring(0, 1).equals(PREFIX) && (card.length() == 13 || card.length() == 16); 121 } 122 } 123 124 /** 125 * Option specifying that no cards are allowed. This is useful if 126 * you want only custom card types to validate so you turn off the 127 * default cards with this option. 128 * <pre> 129 * {@code 130 * CreditCardValidator v = new CreditCardValidator(CreditCardValidator.NONE); 131 * v.addAllowedCardType(customType); 132 * v.isValid(aCardNumber); 133 * } 134 * </pre> 135 * 136 * @since 1.1.2 137 */ 138 public static final int NONE = 0; 139 140 /** 141 * Option specifying that American Express cards are allowed. 142 */ 143 public static final int AMEX = 1 << 0; 144 145 /** 146 * Option specifying that Visa cards are allowed. 147 */ 148 public static final int VISA = 1 << 1; 149 150 /** 151 * Option specifying that Mastercard cards are allowed. 152 */ 153 public static final int MASTERCARD = 1 << 2; 154 155 /** 156 * Option specifying that Discover cards are allowed. 157 */ 158 public static final int DISCOVER = 1 << 3; 159 160 /** 161 * The CreditCardTypes that are allowed to pass validation. 162 */ 163 private final Collection<CreditCardType> cardTypes = new ArrayList<>(); 164 165 /** 166 * Create a new CreditCardValidator with default options. 167 */ 168 public CreditCardValidator() { 169 this(AMEX + VISA + MASTERCARD + DISCOVER); 170 } 171 172 /** 173 * Creates a new CreditCardValidator with the specified options. 174 * 175 * @param options Pass in 176 * CreditCardValidator.VISA + CreditCardValidator.AMEX to specify that 177 * those are the only valid card types. 178 */ 179 public CreditCardValidator(final int options) { 180 final Flags f = new Flags(options); 181 if (f.isOn(VISA)) { 182 cardTypes.add(Visa.INSTANCE); 183 } 184 185 if (f.isOn(AMEX)) { 186 cardTypes.add(Amex.INSTANCE); 187 } 188 189 if (f.isOn(MASTERCARD)) { 190 cardTypes.add(Mastercard.INSTANCE); 191 } 192 193 if (f.isOn(DISCOVER)) { 194 cardTypes.add(Discover.INSTANCE); 195 } 196 } 197 198 /** 199 * Adds an allowed CreditCardType that participates in the card 200 * validation algorithm. 201 * 202 * @param type The type that is now allowed to pass validation. 203 * @since 1.1.2 204 */ 205 public void addAllowedCardType(final CreditCardType type){ 206 cardTypes.add(type); 207 } 208 209 /** 210 * Checks if the field is a valid credit card number. 211 * 212 * @param card The card number to validate. 213 * @return Whether the card number is valid. 214 */ 215 public boolean isValid(final String card) { 216 if (card == null || card.length() < 13 || card.length() > 19 || !luhnCheck(card)) { 217 return false; 218 } 219 for (final Object cardType : cardTypes) { 220 final CreditCardType type = (CreditCardType) cardType; 221 if (type.matches(card)) { 222 return true; 223 } 224 } 225 return false; 226 } 227 228 /** 229 * Checks for a valid credit card number. 230 * 231 * @param cardNumber Credit Card Number. 232 * @return Whether the card number passes the luhnCheck. 233 */ 234 protected boolean luhnCheck(final String cardNumber) { 235 // number must be validated as 0..9 numeric first!! 236 final int digits = cardNumber.length(); 237 final int oddOrEven = digits & 1; 238 long sum = 0; 239 for (int count = 0; count < digits; count++) { 240 int digit = 0; 241 try { 242 digit = Integer.parseInt(cardNumber.charAt(count) + ""); 243 } catch (final NumberFormatException e) { 244 return false; 245 } 246 if ((count & 1 ^ oddOrEven) == 0) { // not 247 digit *= 2; 248 if (digit > 9) { 249 digit -= 9; 250 } 251 } 252 sum += digit; 253 } 254 return sum != 0 && sum % 10 == 0; 255 } 256 257}