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