    001/*
    002 * Licensed to the Apache Software Foundation (ASF) under one or more
    003 * contributor license agreements.  See the NOTICE file distributed with
    004 * this work for additional information regarding copyright ownership.
    005 * The ASF licenses this file to You under the Apache License, Version 2.0
    006 * (the "License"); you may not use this file except in compliance with
    007 * the License.  You may obtain a copy of the License at
    008 *
    009 *      http://www.apache.org/licenses/LICENSE-2.0
    010 *
    011 * Unless required by applicable law or agreed to in writing, software
    012 * distributed under the License is distributed on an "AS IS" BASIS,
    013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    014 * See the License for the specific language governing permissions and
    015 * limitations under the License.
    016 */
    017package org.apache.commons.validator.routines;
    018
    019import org.apache.commons.validator.routines.checkdigit.CheckDigit;
    020import org.apache.commons.validator.routines.checkdigit.LuhnCheckDigit;
    021import java.io.Serializable;
    022import java.util.Collections;
    023import java.util.List;
    024import java.util.ArrayList;
    025
    026/**
    027 * Perform credit card validations.
    028 *
    029 * <p>
    030 * By default, AMEX + VISA + MASTERCARD + DISCOVER card types are allowed.  You can specify which
    031 * cards should pass validation by configuring the validation options. For
    032 * example,
    033 * </p>
    034 *
    035 * <pre>
    036 * <code>CreditCardValidator ccv = new CreditCardValidator(CreditCardValidator.AMEX + CreditCardValidator.VISA);</code>
    037 * </pre>
    038 *
    039 * <p>
    040 * configures the validator to only pass American Express and Visa cards.
    041 * If a card type is not directly supported by this class, you can create an
    042 * instance of the {@link CodeValidator} class and pass it to a {@link CreditCardValidator}
    043 * constructor along with any existing validators. For example:
    044 * </p>
    045 *
    046 * <pre>
    047 * <code>CreditCardValidator ccv = new CreditCardValidator(
    048 *     new CodeValidator[] {
    049 *         CreditCardValidator.AMEX_VALIDATOR,
    050 *         CreditCardValidator.VISA_VALIDATOR,
    051 *         new CodeValidator("^(4)(\\d{12,18})$", LUHN_VALIDATOR) // add VPAY
    052 * };</code>
    053 * </pre>
    054 *
    055 * <p>
    056 * Alternatively you can define a validator using the {@link CreditCardRange} class.
    057 * For example:
    058 * </p>
    059 *
    060 * <pre>
    061 * <code>CreditCardValidator ccv = new CreditCardValidator(
    062 *    new CreditCardRange[]{
    063 *        new CreditCardRange("300", "305", 14, 14), // Diners
    064 *        new CreditCardRange("3095", null, 14, 14), // Diners
    065 *        new CreditCardRange("36",   null, 14, 14), // Diners
    066 *        new CreditCardRange("38",   "39", 14, 14), // Diners
    067 *        new CreditCardRange("4",    null, new int[] {13, 16}), // VISA
    068 *    }
    069 * );
    070 * </code>
    071 * </pre>
    072 * <p>
    073 * This can be combined with a list of {@code CodeValidator}s
    074 * </p>
    075 * <p>
    076 * More information can be found in Michael Gilleland's essay
    077 * <a href="http://web.archive.org/web/20120614072656/http://www.merriampark.com/anatomycc.htm">Anatomy of Credit Card Numbers</a>.
    078 * </p>
    079 *
    080 * @version $Revision$
    081 * @since Validator 1.4
    082 */
    083public class CreditCardValidator implements Serializable {
    084
    085    private static final long serialVersionUID = 5955978921148959496L;
    086
    087    private static final int MIN_CC_LENGTH = 12; // minimum allowed length
    088
    089    private static final int MAX_CC_LENGTH = 19; // maximum allowed length
    090
    091    /**
    092     * Class that represents a credit card range.
    093     * @since 1.6
    094     */
    095    public static class CreditCardRange {
    096        final String low; // e.g. 34 or 644
    097        final String high; // e.g. 34 or 65
    098        final int minLen; // e.g. 16 or -1
    099        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,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     * <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}