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