1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * https://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17 package org.apache.commons.validator;
18
19 import java.util.ArrayList;
20 import java.util.Collection;
21
22 import org.apache.commons.validator.util.Flags;
23
24 /**
25 * Perform credit card validations.
26 *
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,
31 * </p>
32 *
33 * <pre>
34 * {@code CreditCardValidator ccv = new CreditCardValidator(CreditCardValidator.AMEX + CreditCardValidator.VISA);}
35 * </pre>
36 *
37 * <p>
38 * configures the validator to only pass American Express and Visa cards.
39 * If a card type is not directly supported by this class, you can implement
40 * the CreditCardType interface and pass an instance into the
41 * {@code addAllowedCardType} method.
42 * </p>
43 *
44 * <p>
45 * For a similar implementation in Perl, reference Sean M. Burke's
46 * <a href="http://www.speech.cs.cmu.edu/~sburke/pub/luhn_lib.html">script</a>.
47 * More information is also available
48 * <a href="http://www.merriampark.com/anatomycc.htm">here</a>.
49 * </p>
50 *
51 * @since 1.1
52 * @deprecated Use the new CreditCardValidator in the routines package. This class
53 * will be removed in a future release.
54 */
55 // CHECKSTYLE:OFF (deprecated code)
56 @Deprecated
57 public class CreditCardValidator {
58
59 private static class Amex implements CreditCardType {
60 static final Amex INSTANCE = new Amex();
61 private static final String PREFIX = "34,37,";
62 @Override
63 public boolean matches(final String card) {
64 final String prefix2 = card.substring(0, 2) + ",";
65 return PREFIX.contains(prefix2) && card.length() == 15;
66 }
67 }
68
69 /**
70 * CreditCardType implementations define how validation is performed
71 * for one type/brand of credit card.
72 * @since 1.1.2
73 */
74 public interface CreditCardType {
75
76 /**
77 * Returns true if the card number matches this type of credit
78 * card. Note that this method is <strong>not</strong> responsible
79 * for analyzing the general form of the card number because
80 * {@code CreditCardValidator} performs those checks before
81 * calling this method. It is generally only required to valid the
82 * length and prefix of the number to determine if it's the correct
83 * type.
84 * @param card The card number, never null.
85 * @return true if the number matches.
86 */
87 boolean matches(String card);
88
89 }
90
91 private static class Discover implements CreditCardType {
92 static final Discover INSTANCE = new Discover();
93 private static final String PREFIX = "6011";
94 @Override
95 public boolean matches(final String card) {
96 return card.substring(0, 4).equals(PREFIX) && card.length() == 16;
97 }
98 }
99
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 * @since 1.1.2
136 */
137 public static final int NONE = 0;
138
139 /**
140 * Option specifying that American Express cards are allowed.
141 */
142 public static final int AMEX = 1 << 0;
143
144 /**
145 * Option specifying that Visa cards are allowed.
146 */
147 public static final int VISA = 1 << 1;
148
149 /**
150 * Option specifying that Mastercard cards are allowed.
151 */
152 public static final int MASTERCARD = 1 << 2;
153
154 /**
155 * Option specifying that Discover cards are allowed.
156 */
157 public static final int DISCOVER = 1 << 3;
158
159 /**
160 * The CreditCardTypes that are allowed to pass validation.
161 */
162 private final Collection<CreditCardType> cardTypes = new ArrayList<>();
163
164 /**
165 * Create a new CreditCardValidator with default options.
166 */
167 public CreditCardValidator() {
168 this(AMEX + VISA + MASTERCARD + DISCOVER);
169 }
170
171 /**
172 * Creates a new CreditCardValidator with the specified options.
173 * @param options Pass in
174 * CreditCardValidator.VISA + CreditCardValidator.AMEX to specify that
175 * those are the only valid card types.
176 */
177 public CreditCardValidator(final int options) {
178 final Flags f = new Flags(options);
179 if (f.isOn(VISA)) {
180 cardTypes.add(Visa.INSTANCE);
181 }
182
183 if (f.isOn(AMEX)) {
184 cardTypes.add(Amex.INSTANCE);
185 }
186
187 if (f.isOn(MASTERCARD)) {
188 cardTypes.add(Mastercard.INSTANCE);
189 }
190
191 if (f.isOn(DISCOVER)) {
192 cardTypes.add(Discover.INSTANCE);
193 }
194 }
195
196 /**
197 * Adds an allowed CreditCardType that participates in the card
198 * validation algorithm.
199 * @param type The type that is now allowed to pass validation.
200 * @since 1.1.2
201 */
202 public void addAllowedCardType(final CreditCardType type){
203 cardTypes.add(type);
204 }
205
206 /**
207 * Checks if the field is a valid credit card number.
208 * @param card The card number to validate.
209 * @return Whether the card number is valid.
210 */
211 public boolean isValid(final String card) {
212 if (card == null || card.length() < 13 || card.length() > 19 || !luhnCheck(card)) {
213 return false;
214 }
215 for (final Object cardType : cardTypes) {
216 final CreditCardType type = (CreditCardType) cardType;
217 if (type.matches(card)) {
218 return true;
219 }
220 }
221 return false;
222 }
223
224 /**
225 * Checks for a valid credit card number.
226 * @param cardNumber Credit Card Number.
227 * @return Whether the card number passes the luhnCheck.
228 */
229 protected boolean luhnCheck(final String cardNumber) {
230 // number must be validated as 0..9 numeric first!!
231 final int digits = cardNumber.length();
232 final int oddOrEven = digits & 1;
233 long sum = 0;
234 for (int count = 0; count < digits; count++) {
235 int digit = 0;
236 try {
237 digit = Integer.parseInt(cardNumber.charAt(count) + "");
238 } catch (final NumberFormatException e) {
239 return false;
240 }
241 if ((count & 1 ^ oddOrEven) == 0) { // not
242 digit *= 2;
243 if (digit > 9) {
244 digit -= 9;
245 }
246 }
247 sum += digit;
248 }
249 return sum != 0 && sum % 10 == 0;
250 }
251
252 }