Coverage Report - org.apache.commons.validator.routines.CreditCardValidator
 
Classes in this File Line Coverage Branch Coverage Complexity
CreditCardValidator
93%
67/72
90%
45/50
3.333
CreditCardValidator$1
85%
12/14
81%
13/16
3.333
CreditCardValidator$CreditCardRange
100%
14/14
N/A
3.333
 
 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  14
         public CreditCardRange(String low, String high, int minLen, int maxLen) {
 119  14
             this.low = low;
 120  14
             this.high = high;
 121  14
             this.minLen = minLen;
 122  14
             this.maxLen = maxLen;
 123  14
             this.lengths = null;
 124  14
         }
 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  7
         public CreditCardRange(String low, String high, int [] lengths) {
 142  7
             this.low = low;
 143  7
             this.high = high;
 144  7
             this.minLen = -1;
 145  7
             this.maxLen = -1;
 146  7
             this.lengths = lengths.clone();
 147  7
         }
 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  16
     private final List<CodeValidator> cardTypes = new ArrayList<CodeValidator>();
 208  
 
 209  
     /**
 210  
      * Luhn checkdigit validator for the card numbers.
 211  
      */
 212  1
     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  1
     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  1
     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  1
     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  1
     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  1
     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  1
     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  1
     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  1
     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  1
     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  2
         this(AMEX + VISA + MASTERCARD + DISCOVER);
 293  2
     }
 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  10
         super();
 303  
 
 304  10
         if (isOn(options, VISA)) {
 305  3
             this.cardTypes.add(VISA_VALIDATOR);
 306  
         }
 307  
 
 308  10
         if (isOn(options, VPAY)) {
 309  1
             this.cardTypes.add(VPAY_VALIDATOR);
 310  
         }
 311  
 
 312  10
         if (isOn(options, AMEX)) {
 313  4
             this.cardTypes.add(AMEX_VALIDATOR);
 314  
         }
 315  
 
 316  10
         if (isOn(options, MASTERCARD)) {
 317  3
             this.cardTypes.add(MASTERCARD_VALIDATOR);
 318  
         }
 319  
 
 320  10
         if (isOn(options, MASTERCARD_PRE_OCT2016)) {
 321  0
             this.cardTypes.add(MASTERCARD_VALIDATOR_PRE_OCT2016);
 322  
         }
 323  
 
 324  10
         if (isOn(options, DISCOVER)) {
 325  3
             this.cardTypes.add(DISCOVER_VALIDATOR);
 326  
         }
 327  
 
 328  10
         if (isOn(options, DINERS)) {
 329  1
             this.cardTypes.add(DINERS_VALIDATOR);
 330  
         }
 331  10
     }
 332  
 
 333  
     /**
 334  
      * Create a new CreditCardValidator with the specified {@link CodeValidator}s.
 335  
      * @param creditCardValidators Set of valid code validators
 336  
      */
 337  3
     public CreditCardValidator(CodeValidator[] creditCardValidators) {
 338  3
         if (creditCardValidators == null) {
 339  1
             throw new IllegalArgumentException("Card validators are missing");
 340  
         }
 341  2
         Collections.addAll(cardTypes, creditCardValidators);
 342  2
     }
 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  2
     public CreditCardValidator(CreditCardRange[] creditCardRanges) {
 350  2
         if (creditCardRanges == null) {
 351  0
             throw new IllegalArgumentException("Card ranges are missing");
 352  
         }
 353  2
         Collections.addAll(cardTypes, createRangeValidator(creditCardRanges, LUHN_VALIDATOR));
 354  2
     }
 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  1
     public CreditCardValidator(CodeValidator[] creditCardValidators, CreditCardRange[] creditCardRanges) {
 367  1
         if (creditCardValidators == null) {
 368  0
             throw new IllegalArgumentException("Card validators are missing");
 369  
         }
 370  1
         if (creditCardRanges == null) {
 371  0
             throw new IllegalArgumentException("Card ranges are missing");
 372  
         }
 373  1
         Collections.addAll(cardTypes, creditCardValidators);
 374  1
         Collections.addAll(cardTypes, createRangeValidator(creditCardRanges, LUHN_VALIDATOR));
 375  1
     }
 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  1
         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  0
         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  1
         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  129
         if (card == null || card.length() == 0) {
 420  4
             return false;
 421  
         }
 422  125
         for (CodeValidator cardType : cardTypes) {
 423  227
             if (cardType.isValid(card)) {
 424  41
                 return true;
 425  
             }
 426  
         }
 427  84
         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  15
         if (card == null || card.length() == 0) {
 438  1
             return null;
 439  
         }
 440  14
         Object result = null;
 441  14
         for (CodeValidator cardType : cardTypes) {
 442  14
             result = cardType.validate(card);
 443  14
             if (result != null) {
 444  9
                 return result;
 445  
             }
 446  
         }
 447  5
         return null;
 448  
 
 449  
     }
 450  
 
 451  
     // package protected for unit test access
 452  
     static boolean validLength(int valueLength, CreditCardRange range) {
 453  81
         if (range.lengths != null) {
 454  23
             for(int length : range.lengths) {
 455  19
                 if (valueLength == length) {
 456  7
                     return true;
 457  
                 }
 458  
             }
 459  4
             return false;
 460  
         }
 461  70
         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  4
         return new CodeValidator(
 467  
                 // must be numeric (rest of validation is done later)
 468  4
                 new RegexValidator("(\\d+)") {
 469  
                     private static final long serialVersionUID = 1L;
 470  4
                     private CreditCardRange[] ccr = creditCardRanges.clone();
 471  
                     @Override
 472  
                     // must return full string
 473  
                     public String validate(String value) {
 474  29
                         if (super.match(value) != null) {
 475  29
                             int length = value.length();
 476  84
                             for(CreditCardRange range : ccr) {
 477  68
                                 if (validLength(length, range)) {
 478  18
                                     if (range.high == null) { // single prefix only
 479  5
                                         if (value.startsWith(range.low)) {
 480  2
                                             return value;
 481  
                                         }
 482  13
                                     } 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  11
                                                return value;
 487  
                                     }
 488  
                                 }
 489  
                             }
 490  
                         }
 491  16
                         return null;
 492  
                     }
 493  
                     @Override
 494  
                     public boolean isValid(String value) {
 495  0
                         return validate(value) != null;
 496  
                     }
 497  
                     @Override
 498  
                     public String[] match(String value) {
 499  0
                         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  70
         return (options & flag) > 0;
 515  
     }
 516  
 
 517  
 }