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.routines;
18
19 import java.io.Serializable;
20 import java.util.ArrayList;
21 import java.util.Collections;
22 import java.util.List;
23
24 import org.apache.commons.validator.GenericValidator;
25 import org.apache.commons.validator.routines.checkdigit.CheckDigit;
26 import org.apache.commons.validator.routines.checkdigit.LuhnCheckDigit;
27
28 /**
29 * Perform credit card validations.
30 *
31 * <p>
32 * By default, AMEX + VISA + MASTERCARD + DISCOVER card types are allowed. You can specify which
33 * cards should pass validation by configuring the validation options. For
34 * example,
35 * </p>
36 *
37 * <pre>
38 * {@code CreditCardValidator ccv = new CreditCardValidator(CreditCardValidator.AMEX + CreditCardValidator.VISA);}
39 * </pre>
40 *
41 * <p>
42 * configures the validator to only pass American Express and Visa cards.
43 * If a card type is not directly supported by this class, you can create an
44 * instance of the {@link CodeValidator} class and pass it to a {@link CreditCardValidator}
45 * constructor along with any existing validators. For example:
46 * </p>
47 *
48 * <pre>
49 * <code>CreditCardValidator ccv = new CreditCardValidator(
50 * new CodeValidator[] {
51 * CreditCardValidator.AMEX_VALIDATOR,
52 * CreditCardValidator.VISA_VALIDATOR,
53 * new CodeValidator("^(4)(\\d{12,18})$", LUHN_VALIDATOR) // add VPAY
54 * };</code>
55 * </pre>
56 *
57 * <p>
58 * Alternatively you can define a validator using the {@link CreditCardRange} class.
59 * For example:
60 * </p>
61 *
62 * <pre>
63 * <code>CreditCardValidator ccv = new CreditCardValidator(
64 * new CreditCardRange[]{
65 * new CreditCardRange("300", "305", 14, 14), // Diners
66 * new CreditCardRange("3095", null, 14, 14), // Diners
67 * new CreditCardRange("36", null, 14, 14), // Diners
68 * new CreditCardRange("38", "39", 14, 14), // Diners
69 * new CreditCardRange("4", null, new int[] {13, 16}), // VISA
70 * }
71 * );
72 * </code>
73 * </pre>
74 * <p>
75 * This can be combined with a list of {@code CodeValidator}s
76 * </p>
77 * <p>
78 * More information can be found in Michael Gilleland's essay
79 * <a href="https://web.archive.org/web/20120614072656/http://www.merriampark.com/anatomycc.htm">Anatomy of Credit Card Numbers</a>.
80 * </p>
81 *
82 * @since 1.4
83 */
84 public class CreditCardValidator implements Serializable {
85
86 /**
87 * Class that represents a credit card range.
88 * @since 1.6
89 */
90 public static class CreditCardRange {
91 final String low; // for example, 34 or 644
92 final String high; // for example, 34 or 65
93 final int minLen; // for example, 16 or -1
94 final int maxLen; // for example, 19 or -1
95 final int[] lengths; // for example, 16,18,19
96
97 /**
98 * Create a credit card range specifier for use in validation
99 * of the number syntax including the IIN range.
100 * <p>
101 * The low and high parameters may be shorter than the length
102 * of an IIN (currently 6 digits) in which case subsequent digits
103 * are ignored and may range from 0-9.
104 * </p>
105 * <p>
106 * The low and high parameters may be different lengths.
107 * for example, Discover "644" and "65".
108 * </p>
109 * @param low the low digits of the IIN range
110 * @param high the high digits of the IIN range
111 * @param minLen the minimum length of the entire number
112 * @param maxLen the maximum length of the entire number
113 */
114 public CreditCardRange(final String low, final String high, final int minLen, final int maxLen) {
115 this.low = low;
116 this.high = high;
117 this.minLen = minLen;
118 this.maxLen = maxLen;
119 this.lengths = null;
120 }
121
122 /**
123 * Create a credit card range specifier for use in validation
124 * of the number syntax including the IIN range.
125 * <p>
126 * The low and high parameters may be shorter than the length
127 * of an IIN (currently 6 digits) in which case subsequent digits
128 * are ignored and may range from 0-9.
129 * </p>
130 * <p>
131 * The low and high parameters may be different lengths.
132 * for example, Discover "644" and "65".
133 * </p>
134 * @param low the low digits of the IIN range
135 * @param high the high digits of the IIN range
136 * @param lengths array of valid lengths
137 */
138 public CreditCardRange(final String low, final String high, final int [] lengths) {
139 this.low = low;
140 this.high = high;
141 this.minLen = -1;
142 this.maxLen = -1;
143 this.lengths = lengths.clone();
144 }
145 }
146
147 private static final long serialVersionUID = 5955978921148959496L;
148
149 private static final int MIN_CC_LENGTH = 12; // minimum allowed length
150
151 private static final int MAX_CC_LENGTH = 19; // maximum allowed length
152
153 /**
154 * Option specifying that no cards are allowed. This is useful if
155 * you want only custom card types to validate so you turn off the
156 * default cards with this option.
157 *
158 * <pre>
159 * {@code
160 * CreditCardValidator v = new CreditCardValidator(CreditCardValidator.NONE);
161 * v.addAllowedCardType(customType);
162 * v.isValid(aCardNumber);
163 * }
164 * </pre>
165 */
166 public static final long NONE = 0;
167
168 /**
169 * Option specifying that American Express cards are allowed.
170 */
171 public static final long AMEX = 1 << 0;
172
173 /**
174 * Option specifying that Visa cards are allowed.
175 */
176 public static final long VISA = 1 << 1;
177
178 /**
179 * Option specifying that Mastercard cards are allowed.
180 */
181 public static final long MASTERCARD = 1 << 2;
182
183 /**
184 * Option specifying that Discover cards are allowed.
185 */
186 public static final long DISCOVER = 1 << 3; // CHECKSTYLE IGNORE MagicNumber
187
188 /**
189 * Option specifying that Diners cards are allowed.
190 */
191 public static final long DINERS = 1 << 4; // CHECKSTYLE IGNORE MagicNumber
192
193 /**
194 * Option specifying that VPay (Visa) cards are allowed.
195 * @since 1.5.0
196 */
197 public static final long VPAY = 1 << 5; // CHECKSTYLE IGNORE MagicNumber
198
199 /**
200 * Option specifying that Mastercard cards (pre Oct 2016 only) are allowed.
201 * @deprecated for use until Oct 2016 only
202 */
203 @Deprecated
204 public static final long MASTERCARD_PRE_OCT2016 = 1 << 6; // CHECKSTYLE IGNORE MagicNumber
205
206 /**
207 * Luhn checkdigit validator for the card numbers.
208 */
209 private static final CheckDigit LUHN_VALIDATOR = LuhnCheckDigit.LUHN_CHECK_DIGIT;
210
211 /**
212 * American Express (Amex) Card Validator
213 * <ul>
214 * <li>34xxxx (15)</li>
215 * <li>37xxxx (15)</li>
216 * </ul>
217 */
218 public static final CodeValidator AMEX_VALIDATOR = new CodeValidator("^(3[47]\\d{13})$", LUHN_VALIDATOR);
219
220 /**
221 * Diners Card Validator
222 * <ul>
223 * <li>300xxx - 305xxx (14)</li>
224 * <li>3095xx (14)</li>
225 * <li>36xxxx (14)</li>
226 * <li>38xxxx (14)</li>
227 * <li>39xxxx (14)</li>
228 * </ul>
229 */
230 public static final CodeValidator DINERS_VALIDATOR = new CodeValidator("^(30[0-5]\\d{11}|3095\\d{10}|36\\d{12}|3[8-9]\\d{12})$", LUHN_VALIDATOR);
231
232 /**
233 * Discover Card regular expressions
234 * <ul>
235 * <li>6011xx (16)</li>
236 * <li>644xxx - 65xxxx (16)</li>
237 * </ul>
238 */
239 private static final RegexValidator DISCOVER_REGEX = new RegexValidator("^(6011\\d{12,13})$", "^(64[4-9]\\d{13})$", "^(65\\d{14})$", "^(62[2-8]\\d{13})$");
240
241 /** Discover Card Validator */
242 public static final CodeValidator DISCOVER_VALIDATOR = new CodeValidator(DISCOVER_REGEX, LUHN_VALIDATOR);
243
244 /**
245 * Mastercard regular expressions
246 * <ul>
247 * <li>2221xx - 2720xx (16)</li>
248 * <li>51xxx - 55xxx (16)</li>
249 * </ul>
250 */
251 private static final RegexValidator MASTERCARD_REGEX = new RegexValidator(
252 "^(5[1-5]\\d{14})$", // 51 - 55 (pre Oct 2016)
253 // valid from October 2016
254 "^(2221\\d{12})$", // 222100 - 222199
255 "^(222[2-9]\\d{12})$", // 222200 - 222999
256 "^(22[3-9]\\d{13})$", // 223000 - 229999
257 "^(2[3-6]\\d{14})$", // 230000 - 269999
258 "^(27[01]\\d{13})$", // 270000 - 271999
259 "^(2720\\d{12})$" // 272000 - 272099
260 );
261
262 /** Mastercard Card Validator */
263 public static final CodeValidator MASTERCARD_VALIDATOR = new CodeValidator(MASTERCARD_REGEX, LUHN_VALIDATOR);
264
265 /**
266 * Mastercard Card Validator (pre Oct 2016)
267 * @deprecated for use until Oct 2016 only
268 */
269 @Deprecated
270 public static final CodeValidator MASTERCARD_VALIDATOR_PRE_OCT2016 = new CodeValidator("^(5[1-5]\\d{14})$", LUHN_VALIDATOR);
271
272 /**
273 * Visa Card Validator
274 * <p>
275 * 4xxxxx (13 or 16)
276 * </p>
277 */
278 public static final CodeValidator VISA_VALIDATOR = new CodeValidator("^(4)(\\d{12}|\\d{15})$", LUHN_VALIDATOR);
279
280 /**
281 * VPay (Visa) Card Validator
282 * <p>
283 * 4xxxxx (13-19)
284 * </p>
285 * @since 1.5.0
286 */
287 public static final CodeValidator VPAY_VALIDATOR = new CodeValidator("^(4)(\\d{12,18})$", LUHN_VALIDATOR);
288
289 // package protected for unit test access
290 static CodeValidator createRangeValidator(final CreditCardRange[] creditCardRanges, final CheckDigit digitCheck) {
291 return new CodeValidator(
292 // must be numeric (rest of validation is done later)
293 new RegexValidator("(\\d+)") {
294 private static final long serialVersionUID = 1L;
295 private final transient CreditCardRange[] ccr = creditCardRanges.clone();
296
297 @Override
298 public boolean isValid(final String value) {
299 return validate(value) != null;
300 }
301
302 @Override
303 public String[] match(final String value) {
304 return new String[] { validate(value) };
305 }
306
307 @Override
308 // must return full string
309 public String validate(final String value) {
310 if (super.match(value) != null) {
311 final int length = value.length();
312 for (final CreditCardRange range : ccr) {
313 if (validLength(length, range)) {
314 if (range.high == null) { // single prefix only
315 if (value.startsWith(range.low)) {
316 return value;
317 }
318 } else if (range.low.compareTo(value) <= 0 // no need to trim value here
319 &&
320 // here we have to ignore digits beyond the prefix
321 range.high.compareTo(value.substring(0, range.high.length())) >= 0) {
322 return value;
323 }
324 }
325 }
326 }
327 return null;
328 }
329 }, digitCheck);
330 }
331
332 /**
333 * Creates a new generic CreditCardValidator which validates the syntax and check digit only.
334 * Does not check the Issuer Identification Number (IIN)
335 *
336 * @return the validator
337 * @since 1.6
338 */
339 public static CreditCardValidator genericCreditCardValidator() {
340 return genericCreditCardValidator(MIN_CC_LENGTH, MAX_CC_LENGTH);
341 }
342
343 /**
344 * Creates a new generic CreditCardValidator which validates the syntax and check digit only.
345 * Does not check the Issuer Identification Number (IIN)
346 *
347 * @param length exact length
348 * @return the validator
349 * @since 1.6
350 */
351 public static CreditCardValidator genericCreditCardValidator(final int length) {
352 return genericCreditCardValidator(length, length);
353 }
354
355 /**
356 * Creates a new generic CreditCardValidator which validates the syntax and check digit only.
357 * Does not check the Issuer Identification Number (IIN)
358 *
359 * @param minLen minimum allowed length
360 * @param maxLen maximum allowed length
361 * @return the validator
362 * @since 1.6
363 */
364 public static CreditCardValidator genericCreditCardValidator(final int minLen, final int maxLen) {
365 return new CreditCardValidator(new CodeValidator[] {new CodeValidator("(\\d+)", minLen, maxLen, LUHN_VALIDATOR)});
366 }
367
368 // package protected for unit test access
369 static boolean validLength(final int valueLength, final CreditCardRange range) {
370 if (range.lengths != null) {
371 for (final int length : range.lengths) {
372 if (valueLength == length) {
373 return true;
374 }
375 }
376 return false;
377 }
378 return valueLength >= range.minLen && valueLength <= range.maxLen;
379 }
380
381 /**
382 * The CreditCardTypes that are allowed to pass validation.
383 */
384 private final List<CodeValidator> cardTypes = new ArrayList<>();
385
386 /**
387 * Constructs a new CreditCardValidator with default options.
388 * The default options are:
389 * AMEX, VISA, MASTERCARD and DISCOVER
390 */
391 public CreditCardValidator() {
392 this(AMEX + VISA + MASTERCARD + DISCOVER);
393 }
394
395 /**
396 * Constructs a new CreditCardValidator with the specified {@link CodeValidator}s.
397 * @param creditCardValidators Set of valid code validators
398 */
399 public CreditCardValidator(final CodeValidator[] creditCardValidators) {
400 if (creditCardValidators == null) {
401 throw new IllegalArgumentException("Card validators are missing");
402 }
403 Collections.addAll(cardTypes, creditCardValidators);
404 }
405
406 /**
407 * Constructs a new CreditCardValidator with the specified {@link CodeValidator}s
408 * and {@link CreditCardRange}s.
409 * <p>
410 * This can be used to combine predefined validators such as {@link #MASTERCARD_VALIDATOR}
411 * with additional validators using the simpler {@link CreditCardRange}s.
412 * @param creditCardValidators Set of valid code validators
413 * @param creditCardRanges Set of valid code validators
414 * @since 1.6
415 */
416 public CreditCardValidator(final CodeValidator[] creditCardValidators, final CreditCardRange[] creditCardRanges) {
417 if (creditCardValidators == null) {
418 throw new IllegalArgumentException("Card validators are missing");
419 }
420 if (creditCardRanges == null) {
421 throw new IllegalArgumentException("Card ranges are missing");
422 }
423 Collections.addAll(cardTypes, creditCardValidators);
424 Collections.addAll(cardTypes, createRangeValidator(creditCardRanges, LUHN_VALIDATOR));
425 }
426
427 /**
428 * Constructs a new CreditCardValidator with the specified {@link CreditCardRange}s.
429 * @param creditCardRanges Set of valid code validators
430 * @since 1.6
431 */
432 public CreditCardValidator(final CreditCardRange[] creditCardRanges) {
433 if (creditCardRanges == null) {
434 throw new IllegalArgumentException("Card ranges are missing");
435 }
436 Collections.addAll(cardTypes, createRangeValidator(creditCardRanges, LUHN_VALIDATOR));
437 }
438
439 /**
440 * Constructs a new CreditCardValidator with the specified options.
441 * @param options Pass in
442 * CreditCardValidator.VISA + CreditCardValidator.AMEX to specify that
443 * those are the only valid card types.
444 */
445 public CreditCardValidator(final long options) {
446 if (isOn(options, VISA)) {
447 cardTypes.add(VISA_VALIDATOR);
448 }
449
450 if (isOn(options, VPAY)) {
451 cardTypes.add(VPAY_VALIDATOR);
452 }
453
454 if (isOn(options, AMEX)) {
455 cardTypes.add(AMEX_VALIDATOR);
456 }
457
458 if (isOn(options, MASTERCARD)) {
459 cardTypes.add(MASTERCARD_VALIDATOR);
460 }
461
462 if (isOn(options, MASTERCARD_PRE_OCT2016)) {
463 cardTypes.add(MASTERCARD_VALIDATOR_PRE_OCT2016);
464 }
465
466 if (isOn(options, DISCOVER)) {
467 cardTypes.add(DISCOVER_VALIDATOR);
468 }
469
470 if (isOn(options, DINERS)) {
471 cardTypes.add(DINERS_VALIDATOR);
472 }
473 }
474
475 /**
476 * Tests whether the given flag is on. If the flag is not a power of 2
477 * (for example, 3) this tests whether the combination of flags is on.
478 *
479 * @param options The options specified.
480 * @param flag Flag value to check.
481 * @return whether the specified flag value is on.
482 */
483 private boolean isOn(final long options, final long flag) {
484 return (options & flag) > 0;
485 }
486
487 /**
488 * Checks if the field is a valid credit card number.
489 * @param card The card number to validate.
490 * @return Whether the card number is valid.
491 */
492 public boolean isValid(final String card) {
493 if (GenericValidator.isBlankOrNull(card)) {
494 return false;
495 }
496 for (final CodeValidator cardType : cardTypes) {
497 if (cardType.isValid(card)) {
498 return true;
499 }
500 }
501 return false;
502 }
503
504 /**
505 * Checks if the field is a valid credit card number.
506 * @param card The card number to validate.
507 * @return The card number if valid or {@code null}
508 * if invalid.
509 */
510 public Object validate(final String card) {
511 if (GenericValidator.isBlankOrNull(card)) {
512 return null;
513 }
514 Object result = null;
515 for (final CodeValidator cardType : cardTypes) {
516 result = cardType.validate(card);
517 if (result != null) {
518 return result;
519 }
520 }
521 return null;
522
523 }
524
525 }