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