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="https://www.speech.cs.cmu.edu/~sburke/pub/luhn_lib.html">script</a>.
47 * </p>
48 *
49 * @since 1.1
50 * @deprecated Use the new CreditCardValidator in the routines package. This class
51 * will be removed in a future release.
52 */
53 // CHECKSTYLE:OFF (deprecated code)
54 @Deprecated
55 public class CreditCardValidator {
56
57 private static class Amex implements CreditCardType {
58 static final Amex INSTANCE = new Amex();
59 private static final String PREFIX = "34,37,";
60 @Override
61 public boolean matches(final String card) {
62 final String prefix2 = card.substring(0, 2) + ",";
63 return PREFIX.contains(prefix2) && card.length() == 15;
64 }
65 }
66
67 /**
68 * CreditCardType implementations define how validation is performed
69 * for one type/brand of credit card.
70 *
71 * @since 1.1.2
72 */
73 public interface CreditCardType {
74
75 /**
76 * Returns true if the card number matches this type of credit
77 * card. Note that this method is <strong>not</strong> responsible
78 * for analyzing the general form of the card number because
79 * {@code CreditCardValidator} performs those checks before
80 * calling this method. It is generally only required to valid the
81 * length and prefix of the number to determine if it's the correct
82 * type.
83 *
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 *
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 }