View Javadoc
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 }