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