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