1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.commons.validator;
18
19 import java.util.ArrayList;
20 import java.util.Collection;
21 import java.util.Iterator;
22
23 import org.apache.commons.validator.util.Flags;
24
25 /***
26 * <p>Perform credit card validations.</p>
27 * <p>
28 * By default, all supported card types are allowed. You can specify which
29 * cards should pass validation by configuring the validation options. For
30 * example,<br/><code>CreditCardValidator ccv = new CreditCardValidator(CreditCardValidator.AMEX + CreditCardValidator.VISA);</code>
31 * configures the validator to only pass American Express and Visa cards.
32 * If a card type is not directly supported by this class, you can implement
33 * the CreditCardType interface and pass an instance into the
34 * <code>addAllowedCardType</code> method.
35 * </p>
36 * For a similar implementation in Perl, reference Sean M. Burke's
37 * <a href="http://www.speech.cs.cmu.edu/~sburke/pub/luhn_lib.html">script</a>.
38 * More information is also available
39 * <a href="http://www.merriampark.com/anatomycc.htm">here</a>.
40 *
41 * @version $Revision: 478334 $ $Date: 2006-11-22 21:31:54 +0000 (Wed, 22 Nov 2006) $
42 * @since Validator 1.1
43 */
44 public class CreditCardValidator {
45
46 /***
47 * Option specifying that no cards are allowed. This is useful if
48 * you want only custom card types to validate so you turn off the
49 * default cards with this option.
50 * <br/>
51 * <pre>
52 * CreditCardValidator v = new CreditCardValidator(CreditCardValidator.NONE);
53 * v.addAllowedCardType(customType);
54 * v.isValid(aCardNumber);
55 * </pre>
56 * @since Validator 1.1.2
57 */
58 public static final int NONE = 0;
59
60 /***
61 * Option specifying that American Express cards are allowed.
62 */
63 public static final int AMEX = 1 << 0;
64
65 /***
66 * Option specifying that Visa cards are allowed.
67 */
68 public static final int VISA = 1 << 1;
69
70 /***
71 * Option specifying that Mastercard cards are allowed.
72 */
73 public static final int MASTERCARD = 1 << 2;
74
75 /***
76 * Option specifying that Discover cards are allowed.
77 */
78 public static final int DISCOVER = 1 << 3;
79
80 /***
81 * The CreditCardTypes that are allowed to pass validation.
82 */
83 private Collection cardTypes = new ArrayList();
84
85 /***
86 * Create a new CreditCardValidator with default options.
87 */
88 public CreditCardValidator() {
89 this(AMEX + VISA + MASTERCARD + DISCOVER);
90 }
91
92 /***
93 * Create a new CreditCardValidator with the specified options.
94 * @param options Pass in
95 * CreditCardValidator.VISA + CreditCardValidator.AMEX to specify that
96 * those are the only valid card types.
97 */
98 public CreditCardValidator(int options) {
99 super();
100
101 Flags f = new Flags(options);
102 if (f.isOn(VISA)) {
103 this.cardTypes.add(new Visa());
104 }
105
106 if (f.isOn(AMEX)) {
107 this.cardTypes.add(new Amex());
108 }
109
110 if (f.isOn(MASTERCARD)) {
111 this.cardTypes.add(new Mastercard());
112 }
113
114 if (f.isOn(DISCOVER)) {
115 this.cardTypes.add(new Discover());
116 }
117 }
118
119 /***
120 * Checks if the field is a valid credit card number.
121 * @param card The card number to validate.
122 * @return Whether the card number is valid.
123 */
124 public boolean isValid(String card) {
125 if ((card == null) || (card.length() < 13) || (card.length() > 19)) {
126 return false;
127 }
128
129 if (!this.luhnCheck(card)) {
130 return false;
131 }
132
133 Iterator types = this.cardTypes.iterator();
134 while (types.hasNext()) {
135 CreditCardType type = (CreditCardType) types.next();
136 if (type.matches(card)) {
137 return true;
138 }
139 }
140
141 return false;
142 }
143
144 /***
145 * Add an allowed CreditCardType that participates in the card
146 * validation algorithm.
147 * @param type The type that is now allowed to pass validation.
148 * @since Validator 1.1.2
149 */
150 public void addAllowedCardType(CreditCardType type){
151 this.cardTypes.add(type);
152 }
153
154 /***
155 * Checks for a valid credit card number.
156 * @param cardNumber Credit Card Number.
157 * @return Whether the card number passes the luhnCheck.
158 */
159 protected boolean luhnCheck(String cardNumber) {
160
161 int digits = cardNumber.length();
162 int oddOrEven = digits & 1;
163 long sum = 0;
164 for (int count = 0; count < digits; count++) {
165 int digit = 0;
166 try {
167 digit = Integer.parseInt(cardNumber.charAt(count) + "");
168 } catch(NumberFormatException e) {
169 return false;
170 }
171
172 if (((count & 1) ^ oddOrEven) == 0) {
173 digit *= 2;
174 if (digit > 9) {
175 digit -= 9;
176 }
177 }
178 sum += digit;
179 }
180
181 return (sum == 0) ? false : (sum % 10 == 0);
182 }
183
184 /***
185 * CreditCardType implementations define how validation is performed
186 * for one type/brand of credit card.
187 * @since Validator 1.1.2
188 */
189 public interface CreditCardType {
190
191 /***
192 * Returns true if the card number matches this type of credit
193 * card. Note that this method is <strong>not</strong> responsible
194 * for analyzing the general form of the card number because
195 * <code>CreditCardValidator</code> performs those checks before
196 * calling this method. It is generally only required to valid the
197 * length and prefix of the number to determine if it's the correct
198 * type.
199 * @param card The card number, never null.
200 * @return true if the number matches.
201 */
202 boolean matches(String card);
203
204 }
205
206 /***
207 * Change to support Visa Carte Blue used in France
208 * has been removed - see Bug 35926
209 */
210 private class Visa implements CreditCardType {
211 private static final String PREFIX = "4";
212 public boolean matches(String card) {
213 return (
214 card.substring(0, 1).equals(PREFIX)
215 && (card.length() == 13 || card.length() == 16));
216 }
217 }
218
219 private class Amex implements CreditCardType {
220 private static final String PREFIX = "34,37,";
221 public boolean matches(String card) {
222 String prefix2 = card.substring(0, 2) + ",";
223 return ((PREFIX.indexOf(prefix2) != -1) && (card.length() == 15));
224 }
225 }
226
227 private class Discover implements CreditCardType {
228 private static final String PREFIX = "6011";
229 public boolean matches(String card) {
230 return (card.substring(0, 4).equals(PREFIX) && (card.length() == 16));
231 }
232 }
233
234 private class Mastercard implements CreditCardType {
235 private static final String PREFIX = "51,52,53,54,55,";
236 public boolean matches(String card) {
237 String prefix2 = card.substring(0, 2) + ",";
238 return ((PREFIX.indexOf(prefix2) != -1) && (card.length() == 16));
239 }
240 }
241
242 }