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    *      http://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);</code>
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="http://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; // e.g. 34 or 644
92          final String high; // e.g. 34 or 65
93          final int minLen; // e.g. 16 or -1
94          final int maxLen; // e.g. 19 or -1
95          final int lengths[]; // e.g. 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          * e.g. 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          * e.g. 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      * </code>
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             this.cardTypes.add(VISA_VALIDATOR);
448         }
449 
450         if (isOn(options, VPAY)) {
451             this.cardTypes.add(VPAY_VALIDATOR);
452         }
453 
454         if (isOn(options, AMEX)) {
455             this.cardTypes.add(AMEX_VALIDATOR);
456         }
457 
458         if (isOn(options, MASTERCARD)) {
459             this.cardTypes.add(MASTERCARD_VALIDATOR);
460         }
461 
462         if (isOn(options, MASTERCARD_PRE_OCT2016)) {
463             this.cardTypes.add(MASTERCARD_VALIDATOR_PRE_OCT2016);
464         }
465 
466         if (isOn(options, DISCOVER)) {
467             this.cardTypes.add(DISCOVER_VALIDATOR);
468         }
469 
470         if (isOn(options, DINERS)) {
471             this.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      * (ie. 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      *
482      * @return whether the specified flag value is on.
483      */
484     private boolean isOn(final long options, final long flag) {
485         return (options & flag) > 0;
486     }
487 
488     /**
489      * Checks if the field is a valid credit card number.
490      * @param card The card number to validate.
491      * @return Whether the card number is valid.
492      */
493     public boolean isValid(final String card) {
494         if (GenericValidator.isBlankOrNull(card)) {
495             return false;
496         }
497         for (final CodeValidator cardType : cardTypes) {
498             if (cardType.isValid(card)) {
499                 return true;
500             }
501         }
502         return false;
503     }
504 
505     /**
506      * Checks if the field is a valid credit card number.
507      * @param card The card number to validate.
508      * @return The card number if valid or {@code null}
509      * if invalid.
510      */
511     public Object validate(final String card) {
512         if (GenericValidator.isBlankOrNull(card)) {
513             return null;
514         }
515         Object result = null;
516         for (final CodeValidator cardType : cardTypes) {
517             result = cardType.validate(card);
518             if (result != null) {
519                 return result;
520             }
521         }
522         return null;
523 
524     }
525 
526 }