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