    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 java.io.Serializable;
    020import java.util.ArrayList;
    021import java.util.Collections;
    022import java.util.List;
    023
    024import org.apache.commons.validator.routines.checkdigit.CheckDigit;
    025import org.apache.commons.validator.routines.checkdigit.LuhnCheckDigit;
    026
    027/**
    028 * Perform credit card validations.
    029 *
    030 * <p>
    031 * By default, AMEX + VISA + MASTERCARD + DISCOVER card types are allowed.  You can specify which
    032 * cards should pass validation by configuring the validation options. For
    033 * example,
    034 * </p>
    035 *
    036 * <pre>
    037 * <code>CreditCardValidator ccv = new CreditCardValidator(CreditCardValidator.AMEX + CreditCardValidator.VISA);</code>
    038 * </pre>
    039 *
    040 * <p>
    041 * configures the validator to only pass American Express and Visa cards.
    042 * If a card type is not directly supported by this class, you can create an
    043 * instance of the {@link CodeValidator} class and pass it to a {@link CreditCardValidator}
    044 * constructor along with any existing validators. For example:
    045 * </p>
    046 *
    047 * <pre>
    048 * <code>CreditCardValidator ccv = new CreditCardValidator(
    049 *     new CodeValidator[] {
    050 *         CreditCardValidator.AMEX_VALIDATOR,
    051 *         CreditCardValidator.VISA_VALIDATOR,
    052 *         new CodeValidator("^(4)(\\d{12,18})$", LUHN_VALIDATOR) // add VPAY
    053 * };</code>
    054 * </pre>
    055 *
    056 * <p>
    057 * Alternatively you can define a validator using the {@link CreditCardRange} class.
    058 * For example:
    059 * </p>
    060 *
    061 * <pre>
    062 * <code>CreditCardValidator ccv = new CreditCardValidator(
    063 *    new CreditCardRange[]{
    064 *        new CreditCardRange("300", "305", 14, 14), // Diners
    065 *        new CreditCardRange("3095", null, 14, 14), // Diners
    066 *        new CreditCardRange("36",   null, 14, 14), // Diners
    067 *        new CreditCardRange("38",   "39", 14, 14), // Diners
    068 *        new CreditCardRange("4",    null, new int[] {13, 16}), // VISA
    069 *    }
    070 * );
    071 * </code>
    072 * </pre>
    073 * <p>
    074 * This can be combined with a list of {@code CodeValidator}s
    075 * </p>
    076 * <p>
    077 * More information can be found in Michael Gilleland's essay
    078 * <a href="http://web.archive.org/web/20120614072656/http://www.merriampark.com/anatomycc.htm">Anatomy of Credit Card Numbers</a>.
    079 * </p>
    080 *
    081 * @since 1.4
    082 */
    083public class CreditCardValidator implements Serializable {
    084
    085    /**
    086     * Class that represents a credit card range.
    087     * @since 1.6
    088     */
    089    public static class CreditCardRange {
    090        final String low; // e.g. 34 or 644
    091        final String high; // e.g. 34 or 65
    092        final int minLen; // e.g. 16 or -1
    093        final int maxLen; // e.g. 19 or -1
    094        final int lengths[]; // e.g. 16,18,19
    095
    096        /**
    097         * Create a credit card range specifier for use in validation
    098         * of the number syntax including the IIN range.
    099         * <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}