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 * http://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 */
017 package org.apache.commons.validator;
018
019 import java.util.ArrayList;
020 import java.util.Collection;
021 import java.util.Iterator;
022
023 import org.apache.commons.validator.util.Flags;
024
025 /**
026 * <p>Perform credit card validations.</p>
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,<br/><code>CreditCardValidator ccv = new CreditCardValidator(CreditCardValidator.AMEX + CreditCardValidator.VISA);</code>
031 * configures the validator to only pass American Express and Visa cards.
032 * If a card type is not directly supported by this class, you can implement
033 * the CreditCardType interface and pass an instance into the
034 * <code>addAllowedCardType</code> method.
035 * </p>
036 * For a similar implementation in Perl, reference Sean M. Burke's
037 * <a href="http://www.speech.cs.cmu.edu/~sburke/pub/luhn_lib.html">script</a>.
038 * More information is also available
039 * <a href="http://www.merriampark.com/anatomycc.htm">here</a>.
040 *
041 * @version $Revision: 594962 $ $Date: 2007-11-14 18:30:27 +0100 (Mi, 14. Nov 2007) $
042 * @since Validator 1.1
043 * @deprecated Use the new CreditCardValidator in the routines package. This class
044 * will be removed in a future release.
045 */
046 public class CreditCardValidator {
047
048 /**
049 * Option specifying that no cards are allowed. This is useful if
050 * you want only custom card types to validate so you turn off the
051 * default cards with this option.
052 * <br/>
053 * <pre>
054 * CreditCardValidator v = new CreditCardValidator(CreditCardValidator.NONE);
055 * v.addAllowedCardType(customType);
056 * v.isValid(aCardNumber);
057 * </pre>
058 * @since Validator 1.1.2
059 */
060 public static final int NONE = 0;
061
062 /**
063 * Option specifying that American Express cards are allowed.
064 */
065 public static final int AMEX = 1 << 0;
066
067 /**
068 * Option specifying that Visa cards are allowed.
069 */
070 public static final int VISA = 1 << 1;
071
072 /**
073 * Option specifying that Mastercard cards are allowed.
074 */
075 public static final int MASTERCARD = 1 << 2;
076
077 /**
078 * Option specifying that Discover cards are allowed.
079 */
080 public static final int DISCOVER = 1 << 3;
081
082 /**
083 * The CreditCardTypes that are allowed to pass validation.
084 */
085 private Collection cardTypes = new ArrayList();
086
087 /**
088 * Create a new CreditCardValidator with default options.
089 */
090 public CreditCardValidator() {
091 this(AMEX + VISA + MASTERCARD + DISCOVER);
092 }
093
094 /**
095 * Create a new CreditCardValidator with the specified options.
096 * @param options Pass in
097 * CreditCardValidator.VISA + CreditCardValidator.AMEX to specify that
098 * those are the only valid card types.
099 */
100 public CreditCardValidator(int options) {
101 super();
102
103 Flags f = new Flags(options);
104 if (f.isOn(VISA)) {
105 this.cardTypes.add(new Visa());
106 }
107
108 if (f.isOn(AMEX)) {
109 this.cardTypes.add(new Amex());
110 }
111
112 if (f.isOn(MASTERCARD)) {
113 this.cardTypes.add(new Mastercard());
114 }
115
116 if (f.isOn(DISCOVER)) {
117 this.cardTypes.add(new Discover());
118 }
119 }
120
121 /**
122 * Checks if the field is a valid credit card number.
123 * @param card The card number to validate.
124 * @return Whether the card number is valid.
125 */
126 public boolean isValid(String card) {
127 if ((card == null) || (card.length() < 13) || (card.length() > 19)) {
128 return false;
129 }
130
131 if (!this.luhnCheck(card)) {
132 return false;
133 }
134
135 Iterator types = this.cardTypes.iterator();
136 while (types.hasNext()) {
137 CreditCardType type = (CreditCardType) types.next();
138 if (type.matches(card)) {
139 return true;
140 }
141 }
142
143 return false;
144 }
145
146 /**
147 * Add an allowed CreditCardType that participates in the card
148 * validation algorithm.
149 * @param type The type that is now allowed to pass validation.
150 * @since Validator 1.1.2
151 */
152 public void addAllowedCardType(CreditCardType type){
153 this.cardTypes.add(type);
154 }
155
156 /**
157 * Checks for a valid credit card number.
158 * @param cardNumber Credit Card Number.
159 * @return Whether the card number passes the luhnCheck.
160 */
161 protected boolean luhnCheck(String cardNumber) {
162 // number must be validated as 0..9 numeric first!!
163 int digits = cardNumber.length();
164 int oddOrEven = digits & 1;
165 long sum = 0;
166 for (int count = 0; count < digits; count++) {
167 int digit = 0;
168 try {
169 digit = Integer.parseInt(cardNumber.charAt(count) + "");
170 } catch(NumberFormatException e) {
171 return false;
172 }
173
174 if (((count & 1) ^ oddOrEven) == 0) { // not
175 digit *= 2;
176 if (digit > 9) {
177 digit -= 9;
178 }
179 }
180 sum += digit;
181 }
182
183 return (sum == 0) ? false : (sum % 10 == 0);
184 }
185
186 /**
187 * CreditCardType implementations define how validation is performed
188 * for one type/brand of credit card.
189 * @since Validator 1.1.2
190 */
191 public interface CreditCardType {
192
193 /**
194 * Returns true if the card number matches this type of credit
195 * card. Note that this method is <strong>not</strong> responsible
196 * for analyzing the general form of the card number because
197 * <code>CreditCardValidator</code> performs those checks before
198 * calling this method. It is generally only required to valid the
199 * length and prefix of the number to determine if it's the correct
200 * type.
201 * @param card The card number, never null.
202 * @return true if the number matches.
203 */
204 boolean matches(String card);
205
206 }
207
208 /**
209 * Change to support Visa Carte Blue used in France
210 * has been removed - see Bug 35926
211 */
212 private class Visa implements CreditCardType {
213 private static final String PREFIX = "4";
214 public boolean matches(String card) {
215 return (
216 card.substring(0, 1).equals(PREFIX)
217 && (card.length() == 13 || card.length() == 16));
218 }
219 }
220
221 private class Amex implements CreditCardType {
222 private static final String PREFIX = "34,37,";
223 public boolean matches(String card) {
224 String prefix2 = card.substring(0, 2) + ",";
225 return ((PREFIX.indexOf(prefix2) != -1) && (card.length() == 15));
226 }
227 }
228
229 private class Discover implements CreditCardType {
230 private static final String PREFIX = "6011";
231 public boolean matches(String card) {
232 return (card.substring(0, 4).equals(PREFIX) && (card.length() == 16));
233 }
234 }
235
236 private class Mastercard implements CreditCardType {
237 private static final String PREFIX = "51,52,53,54,55,";
238 public boolean matches(String card) {
239 String prefix2 = card.substring(0, 2) + ",";
240 return ((PREFIX.indexOf(prefix2) != -1) && (card.length() == 16));
241 }
242 }
243
244 }