    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;
    018
    019import java.util.ArrayList;
    020import java.util.Collection;
    021
    022import org.apache.commons.validator.util.Flags;
    023
    024/**
    025 * Perform credit card validations.
    026 *
    027 * <p>
    028 * By default, all supported card types are allowed.  You can specify which
    029 * cards should pass validation by configuring the validation options. For
    030 * example,
    031 * </p>
    032 *
    033 * <pre>
    034 * {@code CreditCardValidator ccv = new CreditCardValidator(CreditCardValidator.AMEX + CreditCardValidator.VISA);}
    035 * </pre>
    036 *
    037 * <p>
    038 * configures the validator to only pass American Express and Visa cards.
    039 * If a card type is not directly supported by this class, you can implement
    040 * the CreditCardType interface and pass an instance into the
    041 * {@code addAllowedCardType} method.
    042 * </p>
    043 *
    044 * <p>
    045 * For a similar implementation in Perl, reference Sean M. Burke's
    046 * <a href="http://www.speech.cs.cmu.edu/~sburke/pub/luhn_lib.html">script</a>.
    047 * More information is also available
    048 * <a href="http://www.merriampark.com/anatomycc.htm">here</a>.
    049 * </p>
    050 *
    051 * @since 1.1
    052 * @deprecated Use the new CreditCardValidator in the routines package. This class
    053 * will be removed in a future release.
    054 */
    055// CHECKSTYLE:OFF (deprecated code)
    056@Deprecated
    057public class CreditCardValidator {
    058
    059    private static class Amex implements CreditCardType {
    060        static final Amex INSTANCE = new Amex();
    061        private static final String PREFIX = "34,37,";
    062        @Override
    063        public boolean matches(final String card) {
    064            final String prefix2 = card.substring(0, 2) + ",";
    065            return PREFIX.contains(prefix2) && card.length() == 15;
    066        }
    067    }
    068
    069    /**
    070     * CreditCardType implementations define how validation is performed
    071     * for one type/brand of credit card.
    072     * @since 1.1.2
    073     */
    074    public interface CreditCardType {
    075
    076        /**
    077         * Returns true if the card number matches this type of credit
    078         * card.  Note that this method is <strong>not</strong> responsible
    079         * for analyzing the general form of the card number because
    080         * {@code CreditCardValidator} performs those checks before
    081         * calling this method.  It is generally only required to valid the
    082         * length and prefix of the number to determine if it's the correct
    083         * type.
    084         * @param card The card number, never null.
    085         * @return true if the number matches.
    086         */
    087        boolean matches(String card);
    088
    089    }
    090
    091    private static class Discover implements CreditCardType {
    092        static final Discover INSTANCE = new Discover();
    093        private static final String PREFIX = "6011";
    094        @Override
    095        public boolean matches(final String card) {
    096            return card.substring(0, 4).equals(PREFIX) && card.length() == 16;
    097        }
    098    }
    099
    100    private static class Mastercard implements CreditCardType {
    101        static final Mastercard INSTANCE = new Mastercard();
    102        private static final String PREFIX = "51,52,53,54,55,";
    103        @Override
    104        public boolean matches(final String card) {
    105            final String prefix2 = card.substring(0, 2) + ",";
    106            return PREFIX.contains(prefix2) && card.length() == 16;
    107        }
    108    }
    109
    110    /**
    111     *  Change to support Visa Carte Blue used in France
    112     *  has been removed - see Bug 35926
    113     */
    114    private static class Visa implements CreditCardType {
    115        static final Visa INSTANCE = new Visa();
    116        private static final String PREFIX = "4";
    117
    118        @Override
    119        public boolean matches(final String card) {
    120            return card.substring(0, 1).equals(PREFIX) && (card.length() == 13 || card.length() == 16);
    121        }
    122    }
    123
    124    /**
    125     * Option specifying that no cards are allowed.  This is useful if
    126     * you want only custom card types to validate so you turn off the
    127     * default cards with this option.
    128     * <pre>
    129     * {@code
    130     * CreditCardValidator v = new CreditCardValidator(CreditCardValidator.NONE);
    131     * v.addAllowedCardType(customType);
    132     * v.isValid(aCardNumber);
    133     * }
    134     * </pre>
    135     * @since 1.1.2
    136     */
    137    public static final int NONE = 0;
    138
    139    /**
    140     * Option specifying that American Express cards are allowed.
    141     */
    142    public static final int AMEX = 1 << 0;
    143
    144    /**
    145     * Option specifying that Visa cards are allowed.
    146     */
    147    public static final int VISA = 1 << 1;
    148
    149    /**
    150     * Option specifying that Mastercard cards are allowed.
    151     */
    152    public static final int MASTERCARD = 1 << 2;
    153
    154    /**
    155     * Option specifying that Discover cards are allowed.
    156     */
    157    public static final int DISCOVER = 1 << 3;
    158
    159    /**
    160     * The CreditCardTypes that are allowed to pass validation.
    161     */
    162    private final Collection<CreditCardType> cardTypes = new ArrayList<>();
    163
    164    /**
    165     * Create a new CreditCardValidator with default options.
    166     */
    167    public CreditCardValidator() {
    168        this(AMEX + VISA + MASTERCARD + DISCOVER);
    169    }
    170
    171    /**
    172     * Creates a new CreditCardValidator with the specified options.
    173     * @param options Pass in
    174     * CreditCardValidator.VISA + CreditCardValidator.AMEX to specify that
    175     * those are the only valid card types.
    176     */
    177    public CreditCardValidator(final int options) {
    178        final Flags f = new Flags(options);
    179        if (f.isOn(VISA)) {
    180            cardTypes.add(Visa.INSTANCE);
    181        }
    182
    183        if (f.isOn(AMEX)) {
    184            cardTypes.add(Amex.INSTANCE);
    185        }
    186
    187        if (f.isOn(MASTERCARD)) {
    188            cardTypes.add(Mastercard.INSTANCE);
    189        }
    190
    191        if (f.isOn(DISCOVER)) {
    192            cardTypes.add(Discover.INSTANCE);
    193        }
    194    }
    195
    196    /**
    197     * Adds an allowed CreditCardType that participates in the card
    198     * validation algorithm.
    199     * @param type The type that is now allowed to pass validation.
    200     * @since 1.1.2
    201     */
    202    public void addAllowedCardType(final CreditCardType type){
    203        cardTypes.add(type);
    204    }
    205
    206    /**
    207     * Checks if the field is a valid credit card number.
    208     * @param card The card number to validate.
    209     * @return Whether the card number is valid.
    210     */
    211    public boolean isValid(final String card) {
    212        if (card == null || card.length() < 13 || card.length() > 19 || !luhnCheck(card)) {
    213            return false;
    214        }
    215        for (final Object cardType : cardTypes) {
    216            final CreditCardType type = (CreditCardType) cardType;
    217            if (type.matches(card)) {
    218                return true;
    219            }
    220        }
    221        return false;
    222    }
    223
    224    /**
    225     * Checks for a valid credit card number.
    226     * @param cardNumber Credit Card Number.
    227     * @return Whether the card number passes the luhnCheck.
    228     */
    229    protected boolean luhnCheck(final String cardNumber) {
    230        // number must be validated as 0..9 numeric first!!
    231        final int digits = cardNumber.length();
    232        final int oddOrEven = digits & 1;
    233        long sum = 0;
    234        for (int count = 0; count < digits; count++) {
    235            int digit = 0;
    236            try {
    237                digit = Integer.parseInt(cardNumber.charAt(count) + "");
    238            } catch (final NumberFormatException e) {
    239                return false;
    240            }
    241            if ((count & 1 ^ oddOrEven) == 0) { // not
    242                digit *= 2;
    243                if (digit > 9) {
    244                    digit -= 9;
    245                }
    246            }
    247            sum += digit;
    248        }
    249        return sum != 0 && sum % 10 == 0;
    250    }
    251
    252}