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;
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);</code>
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</code> 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</code> 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     * </code>
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            this.cardTypes.add(Visa.INSTANCE);
181        }
182
183        if (f.isOn(AMEX)) {
184            this.cardTypes.add(Amex.INSTANCE);
185        }
186
187        if (f.isOn(MASTERCARD)) {
188            this.cardTypes.add(Mastercard.INSTANCE);
189        }
190
191        if (f.isOn(DISCOVER)) {
192            this.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        this.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) {
213            return false;
214        }
215        if (!this.luhnCheck(card)) {
216            return false;
217        }
218        for (final Object cardType : this.cardTypes) {
219            final CreditCardType type = (CreditCardType) cardType;
220            if (type.matches(card)) {
221                return true;
222            }
223        }
224        return false;
225    }
226
227    /**
228     * Checks for a valid credit card number.
229     * @param cardNumber Credit Card Number.
230     * @return Whether the card number passes the luhnCheck.
231     */
232    protected boolean luhnCheck(final String cardNumber) {
233        // number must be validated as 0..9 numeric first!!
234        final int digits = cardNumber.length();
235        final int oddOrEven = digits & 1;
236        long sum = 0;
237        for (int count = 0; count < digits; count++) {
238            int digit = 0;
239            try {
240                digit = Integer.parseInt(cardNumber.charAt(count) + "");
241            } catch (final NumberFormatException e) {
242                return false;
243            }
244            if ((count & 1 ^ oddOrEven) == 0) { // not
245                digit *= 2;
246                if (digit > 9) {
247                    digit -= 9;
248                }
249            }
250            sum += digit;
251        }
252        return sum != 0 && sum % 10 == 0;
253    }
254
255}