Source code
Description: source: package: org.apache.commons.validator.routines, class: CreditCardValidator

    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 *      https://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.GenericValidator;
    025import org.apache.commons.validator.routines.checkdigit.CheckDigit;
    026import org.apache.commons.validator.routines.checkdigit.LuhnCheckDigit;
    027
    028/**
    029 * Perform credit card validations.
    030 *
    031 * <p>
    032 * By default, AMEX + VISA + MASTERCARD + DISCOVER card types are allowed.  You can specify which
    033 * cards should pass validation by configuring the validation options. For
    034 * example,
    035 * </p>
    036 *
    037 * <pre>
    038 * {@code CreditCardValidator ccv = new CreditCardValidator(CreditCardValidator.AMEX + CreditCardValidator.VISA);}
    039 * </pre>
    040 *
    041 * <p>
    042 * configures the validator to only pass American Express and Visa cards.
    043 * If a card type is not directly supported by this class, you can create an
    044 * instance of the {@link CodeValidator} class and pass it to a {@link CreditCardValidator}
    045 * constructor along with any existing validators. For example:
    046 * </p>
    047 *
    048 * <pre>
    049 * <code>CreditCardValidator ccv = new CreditCardValidator(
    050 *     new CodeValidator[] {
    051 *         CreditCardValidator.AMEX_VALIDATOR,
    052 *         CreditCardValidator.VISA_VALIDATOR,
    053 *         new CodeValidator("^(4)(\\d{12,18})$", LUHN_VALIDATOR) // add VPAY
    054 * };</code>
    055 * </pre>
    056 *
    057 * <p>
    058 * Alternatively you can define a validator using the {@link CreditCardRange} class.
    059 * For example:
    060 * </p>
    061 *
    062 * <pre>
    063 * <code>CreditCardValidator ccv = new CreditCardValidator(
    064 *    new CreditCardRange[]{
    065 *        new CreditCardRange("300", "305", 14, 14), // Diners
    066 *        new CreditCardRange("3095", null, 14, 14), // Diners
    067 *        new CreditCardRange("36",   null, 14, 14), // Diners
    068 *        new CreditCardRange("38",   "39", 14, 14), // Diners
    069 *        new CreditCardRange("4",    null, new int[] {13, 16}), // VISA
    070 *    }
    071 * );
    072 * </code>
    073 * </pre>
    074 * <p>
    075 * This can be combined with a list of {@code CodeValidator}s
    076 * </p>
    077 *
    078 * @since 1.4
    079 */
    080public class CreditCardValidator implements Serializable {
    081
    082    /**
    083     * Class that represents a credit card range.
    084     *
    085     * @since 1.6
    086     */
    087    public static class CreditCardRange {
    088        final String low; // for example, 34 or 644
    089        final String high; // for example, 34 or 65
    090        final int minLen; // for example, 16 or -1
    091        final int maxLen; // for example, 19 or -1
    092        final int[] lengths; // for example, 16,18,19
    093
    094        /**
    095         * Create a credit card range specifier for use in validation
    096         * of the number syntax including the IIN range.
    097         * <p>
    098         * The low and high parameters may be shorter than the length
    099         * of an IIN (currently 6 digits) in which case subsequent digits
    100         * are ignored and may range from 0-9.
    101         * </p>
    102         * <p>
    103         * The low and high parameters may be different lengths.
    104         * for example, Discover "644" and "65".
    105         * </p>
    106         *
    107         * @param low The low digits of the IIN range
    108         * @param high The high digits of the IIN range
    109         * @param minLen The minimum length of the entire number
    110         * @param maxLen The maximum length of the entire number
    111         */
    112        public CreditCardRange(final String low, final String high, final int minLen, final int maxLen) {
    113            this.low = low;
    114            this.high = high;
    115            this.minLen = minLen;
    116            this.maxLen = maxLen;
    117            this.lengths = null;
    118        }
    119
    120        /**
    121         * Create a credit card range specifier for use in validation
    122         * of the number syntax including the IIN range.
    123         * <p>
    124         * The low and high parameters may be shorter than the length
    125         * of an IIN (currently 6 digits) in which case subsequent digits
    126         * are ignored and may range from 0-9.
    127         * </p>
    128         * <p>
    129         * The low and high parameters may be different lengths.
    130         * for example, Discover "644" and "65".
    131         * </p>
    132         *
    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     * }
    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     *
    195     * @since 1.5.0
    196     */
    197    public static final long VPAY = 1 << 5; // CHECKSTYLE IGNORE MagicNumber
    198
    199    /**
    200     * Option specifying that Mastercard cards (pre Oct 2016 only) are allowed.
    201     *
    202     * @deprecated for use until Oct 2016 only
    203     */
    204    @Deprecated
    205    public static final long MASTERCARD_PRE_OCT2016 = 1 << 6; // CHECKSTYLE IGNORE MagicNumber
    206
    207    /**
    208     * Luhn checkdigit validator for the card numbers.
    209     */
    210    private static final CheckDigit LUHN_VALIDATOR = LuhnCheckDigit.LUHN_CHECK_DIGIT;
    211
    212    /**
    213     * American Express (Amex) Card Validator
    214     * <ul>
    215     * <li>34xxxx (15)</li>
    216     * <li>37xxxx (15)</li>
    217     * </ul>
    218     */
    219    public static final CodeValidator AMEX_VALIDATOR = new CodeValidator("^(3[47]\\d{13})$", LUHN_VALIDATOR);
    220
    221    /**
    222     * Diners Card Validator
    223     * <ul>
    224     * <li>300xxx - 305xxx (14)</li>
    225     * <li>3095xx (14)</li>
    226     * <li>36xxxx (14)</li>
    227     * <li>38xxxx (14)</li>
    228     * <li>39xxxx (14)</li>
    229     * </ul>
    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     * <ul>
    236     * <li>6011xx (16)</li>
    237     * <li>644xxx - 65xxxx (16)</li>
    238     * </ul>
    239     */
    240    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})$");
    241
    242    /** Discover Card Validator */
    243    public static final CodeValidator DISCOVER_VALIDATOR = new CodeValidator(DISCOVER_REGEX, LUHN_VALIDATOR);
    244
    245    /**
    246     * Mastercard regular expressions
    247     * <ul>
    248     * <li>2221xx - 2720xx (16)</li>
    249     * <li>51xxx - 55xxx (16)</li>
    250     * </ul>
    251     */
    252    private static final RegexValidator MASTERCARD_REGEX = new RegexValidator(
    253            "^(5[1-5]\\d{14})$",   // 51 - 55 (pre Oct 2016)
    254            // valid from October 2016
    255            "^(2221\\d{12})$",     // 222100 - 222199
    256            "^(222[2-9]\\d{12})$", // 222200 - 222999
    257            "^(22[3-9]\\d{13})$",  // 223000 - 229999
    258            "^(2[3-6]\\d{14})$",   // 230000 - 269999
    259            "^(27[01]\\d{13})$",   // 270000 - 271999
    260            "^(2720\\d{12})$"      // 272000 - 272099
    261        );
    262
    263    /** Mastercard Card Validator */
    264    public static final CodeValidator MASTERCARD_VALIDATOR = new CodeValidator(MASTERCARD_REGEX, LUHN_VALIDATOR);
    265
    266    /**
    267     * Mastercard Card Validator (pre Oct 2016)
    268     *
    269     * @deprecated for use until Oct 2016 only
    270     */
    271    @Deprecated
    272    public static final CodeValidator MASTERCARD_VALIDATOR_PRE_OCT2016 = new CodeValidator("^(5[1-5]\\d{14})$", LUHN_VALIDATOR);
    273
    274    /**
    275     * Visa Card Validator
    276     * <p>
    277     * 4xxxxx (13 or 16)
    278     * </p>
    279     */
    280    public static final CodeValidator VISA_VALIDATOR = new CodeValidator("^(4)(\\d{12}|\\d{15})$", LUHN_VALIDATOR);
    281
    282    /**
    283     * VPay (Visa) Card Validator
    284     * <p>
    285     * 4xxxxx (13-19)
    286     * </p>
    287     *
    288     * @since 1.5.0
    289     */
    290    public static final CodeValidator VPAY_VALIDATOR = new CodeValidator("^(4)(\\d{12,18})$", LUHN_VALIDATOR);
    291
    292    // package protected for unit test access
    293    static CodeValidator createRangeValidator(final CreditCardRange[] creditCardRanges, final CheckDigit digitCheck) {
    294        return new CodeValidator(
    295                // must be numeric (rest of validation is done later)
    296                new RegexValidator("(\\d+)") {
    297                    private static final long serialVersionUID = 1L;
    298                    private final transient CreditCardRange[] ccr = creditCardRanges.clone();
    299
    300                    @Override
    301                    public boolean isValid(final String value) {
    302                        return validate(value) != null;
    303                    }
    304
    305                    @Override
    306                    public String[] match(final String value) {
    307                        return new String[] { validate(value) };
    308                    }
    309
    310                    @Override
    311                    // must return full string
    312                    public String validate(final String value) {
    313                        if (super.match(value) != null) {
    314                            final int length = value.length();
    315                            for (final CreditCardRange range : ccr) {
    316                                if (validLength(length, range)) {
    317                                    if (range.high == null) { // single prefix only
    318                                        if (value.startsWith(range.low)) {
    319                                            return value;
    320                                        }
    321                                    } else if (range.low.compareTo(value) <= 0 // no need to trim value here
    322                                            &&
    323                                    // here we have to ignore digits beyond the prefix
    324                                            range.high.compareTo(value.substring(0, range.high.length())) >= 0) {
    325                                        return value;
    326                                    }
    327                                }
    328                            }
    329                        }
    330                        return null;
    331                    }
    332                }, digitCheck);
    333    }
    334
    335    /**
    336     * Creates a new generic CreditCardValidator which validates the syntax and check digit only.
    337     * Does not check the Issuer Identification Number (IIN)
    338     *
    339     * @return The validator
    340     * @since 1.6
    341     */
    342    public static CreditCardValidator genericCreditCardValidator() {
    343        return genericCreditCardValidator(MIN_CC_LENGTH, MAX_CC_LENGTH);
    344    }
    345
    346    /**
    347     * Creates a new generic CreditCardValidator which validates the syntax and check digit only.
    348     * Does not check the Issuer Identification Number (IIN)
    349     *
    350     * @param length exact length
    351     * @return The validator
    352     * @since 1.6
    353     */
    354    public static CreditCardValidator genericCreditCardValidator(final int length) {
    355        return genericCreditCardValidator(length, length);
    356    }
    357
    358    /**
    359     * Creates a new generic CreditCardValidator which validates the syntax and check digit only.
    360     * Does not check the Issuer Identification Number (IIN)
    361     *
    362     * @param minLen minimum allowed length
    363     * @param maxLen maximum allowed length
    364     * @return The validator
    365     * @since 1.6
    366     */
    367    public static CreditCardValidator genericCreditCardValidator(final int minLen, final int maxLen) {
    368        return new CreditCardValidator(new CodeValidator[] {new CodeValidator("(\\d+)", minLen, maxLen, LUHN_VALIDATOR)});
    369    }
    370
    371    // package protected for unit test access
    372    static boolean validLength(final int valueLength, final CreditCardRange range) {
    373        if (range.lengths != null) {
    374            for (final int length : range.lengths) {
    375                if (valueLength == length) {
    376                    return true;
    377                }
    378            }
    379            return false;
    380        }
    381        return valueLength >= range.minLen && valueLength <= range.maxLen;
    382    }
    383
    384    /**
    385     * The CreditCardTypes that are allowed to pass validation.
    386     */
    387    private final List<CodeValidator> cardTypes = new ArrayList<>();
    388
    389    /**
    390     * Constructs a new CreditCardValidator with default options.
    391     * The default options are:
    392     * AMEX, VISA, MASTERCARD and DISCOVER
    393     */
    394    public CreditCardValidator() {
    395        this(AMEX + VISA + MASTERCARD + DISCOVER);
    396    }
    397
    398    /**
    399     * Constructs a new CreditCardValidator with the specified {@link CodeValidator}s.
    400     *
    401     * @param creditCardValidators Set of valid code validators
    402     */
    403    public CreditCardValidator(final CodeValidator[] creditCardValidators) {
    404        if (creditCardValidators == null) {
    405            throw new IllegalArgumentException("Card validators are missing");
    406        }
    407        Collections.addAll(cardTypes, creditCardValidators);
    408    }
    409
    410    /**
    411     * Constructs a new CreditCardValidator with the specified {@link CodeValidator}s
    412     * and {@link CreditCardRange}s.
    413     * <p>
    414     * This can be used to combine predefined validators such as {@link #MASTERCARD_VALIDATOR}
    415     * with additional validators using the simpler {@link CreditCardRange}s.
    416     * </p>
    417     *
    418     * @param creditCardValidators Set of valid code validators
    419     * @param creditCardRanges Set of valid code validators
    420     * @since 1.6
    421     */
    422    public CreditCardValidator(final CodeValidator[] creditCardValidators, final CreditCardRange[] creditCardRanges) {
    423        if (creditCardValidators == null) {
    424            throw new IllegalArgumentException("Card validators are missing");
    425        }
    426        if (creditCardRanges == null) {
    427            throw new IllegalArgumentException("Card ranges are missing");
    428        }
    429        Collections.addAll(cardTypes, creditCardValidators);
    430        Collections.addAll(cardTypes, createRangeValidator(creditCardRanges, LUHN_VALIDATOR));
    431    }
    432
    433    /**
    434     * Constructs a new CreditCardValidator with the specified {@link CreditCardRange}s.
    435     *
    436     * @param creditCardRanges Set of valid code validators
    437     * @since 1.6
    438     */
    439    public CreditCardValidator(final CreditCardRange[] creditCardRanges) {
    440        if (creditCardRanges == null) {
    441            throw new IllegalArgumentException("Card ranges are missing");
    442        }
    443        Collections.addAll(cardTypes, createRangeValidator(creditCardRanges, LUHN_VALIDATOR));
    444    }
    445
    446    /**
    447     * Constructs a new CreditCardValidator with the specified options.
    448     *
    449     * @param options Pass in
    450     * CreditCardValidator.VISA + CreditCardValidator.AMEX to specify that
    451     * those are the only valid card types.
    452     */
    453    public CreditCardValidator(final long options) {
    454        if (isOn(options, VISA)) {
    455            cardTypes.add(VISA_VALIDATOR);
    456        }
    457
    458        if (isOn(options, VPAY)) {
    459            cardTypes.add(VPAY_VALIDATOR);
    460        }
    461
    462        if (isOn(options, AMEX)) {
    463            cardTypes.add(AMEX_VALIDATOR);
    464        }
    465
    466        if (isOn(options, MASTERCARD)) {
    467            cardTypes.add(MASTERCARD_VALIDATOR);
    468        }
    469
    470        if (isOn(options, MASTERCARD_PRE_OCT2016)) {
    471            cardTypes.add(MASTERCARD_VALIDATOR_PRE_OCT2016);
    472        }
    473
    474        if (isOn(options, DISCOVER)) {
    475            cardTypes.add(DISCOVER_VALIDATOR);
    476        }
    477
    478        if (isOn(options, DINERS)) {
    479            cardTypes.add(DINERS_VALIDATOR);
    480        }
    481    }
    482
    483    /**
    484     * Tests whether the given flag is on. If the flag is not a power of 2
    485     * (for example, 3) this tests whether the combination of flags is on.
    486     *
    487     * @param options The options specified.
    488     * @param flag Flag value to check.
    489     * @return whether the specified flag value is on.
    490     */
    491    private boolean isOn(final long options, final long flag) {
    492        return (options & flag) > 0;
    493    }
    494
    495    /**
    496     * Checks if the field is a valid credit card number.
    497     *
    498     * @param card The card number to validate.
    499     * @return Whether the card number is valid.
    500     */
    501    public boolean isValid(final String card) {
    502        if (GenericValidator.isBlankOrNull(card)) {
    503            return false;
    504        }
    505        for (final CodeValidator cardType : cardTypes) {
    506            if (cardType.isValid(card)) {
    507                return true;
    508            }
    509        }
    510        return false;
    511    }
    512
    513    /**
    514     * Checks if the field is a valid credit card number.
    515     *
    516     * @param card The card number to validate.
    517     * @return The card number if valid or {@code null}
    518     * if invalid.
    519     */
    520    public Object validate(final String card) {
    521        if (GenericValidator.isBlankOrNull(card)) {
    522            return null;
    523        }
    524        Object result = null;
    525        for (final CodeValidator cardType : cardTypes) {
    526            result = cardType.validate(card);
    527            if (result != null) {
    528                return result;
    529            }
    530        }
    531        return null;
    532
    533    }
    534
    535}