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.routines;
018    
019    import org.apache.commons.validator.routines.checkdigit.CheckDigit;
020    import org.apache.commons.validator.routines.checkdigit.LuhnCheckDigit;
021    import java.io.Serializable;
022    import java.util.List;
023    import java.util.ArrayList;
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: 594937 $ $Date: 2007-11-14 17:34:43 +0100 (Mi, 14. Nov 2007) $
042     * @since Validator 1.4
043     */
044    public class CreditCardValidator implements Serializable {
045    
046        /**
047         * Option specifying that no cards are allowed.  This is useful if
048         * you want only custom card types to validate so you turn off the
049         * default cards with this option.
050         * <br/>
051         * <pre>
052         * CreditCardValidator v = new CreditCardValidator(CreditCardValidator.NONE);
053         * v.addAllowedCardType(customType);
054         * v.isValid(aCardNumber);
055         * </pre>
056         */
057        public static final long NONE = 0;
058    
059        /**
060         * Option specifying that American Express cards are allowed.
061         */
062        public static final long AMEX = 1 << 0;
063    
064        /**
065         * Option specifying that Visa cards are allowed.
066         */
067        public static final long VISA = 1 << 1;
068    
069        /**
070         * Option specifying that Mastercard cards are allowed.
071         */
072        public static final long MASTERCARD = 1 << 2;
073    
074        /**
075         * Option specifying that Discover cards are allowed.
076         */
077        public static final long DISCOVER = 1 << 3;
078    
079        /**
080         * Option specifying that Diners cards are allowed.
081         */
082        public static final long DINERS = 1 << 4;
083        
084        /**
085         * The CreditCardTypes that are allowed to pass validation.
086         */
087        private final List cardTypes = new ArrayList();
088    
089        /**
090         * Luhn checkdigit validator for the card numbers.
091         */
092        private static final CheckDigit LUHN_VALIDATOR = LuhnCheckDigit.INSTANCE;
093    
094        /** American Express (Amex) Card Validator */
095        public static final CodeValidator AMEX_VALIDATOR = new CodeValidator("^(3[47]\\d{13})$", LUHN_VALIDATOR);
096    
097        /** Diners Card Validator */
098        public static final CodeValidator DINERS_VALIDATOR = new CodeValidator("^(30[0-5]\\d{11}|36\\d{12})$", LUHN_VALIDATOR);
099    
100        /** Discover Card regular expressions */
101        private static final RegexValidator DISCOVER_REGEX = new RegexValidator(new String[] {"^(6011\\d{12})$", "^(65\\d{14})$"});
102    
103        /** Discover Card Validator */
104        public static final CodeValidator DISCOVER_VALIDATOR = new CodeValidator(DISCOVER_REGEX, LUHN_VALIDATOR);
105    
106        /** Mastercard Card Validator */
107        public static final CodeValidator MASTERCARD_VALIDATOR = new CodeValidator("^(5[1-5]\\d{14})$", LUHN_VALIDATOR);
108    
109        /** Visa Card Validator */
110        public static final CodeValidator VISA_VALIDATOR = new CodeValidator("^(4)(\\d{12}|\\d{15})$", LUHN_VALIDATOR);
111    
112        /**
113         * Create a new CreditCardValidator with default options.
114         */
115        public CreditCardValidator() {
116            this(AMEX + VISA + MASTERCARD + DISCOVER);
117        }
118    
119        /**
120         * Create a new CreditCardValidator with the specified options.
121         * @param options Pass in
122         * CreditCardValidator.VISA + CreditCardValidator.AMEX to specify that 
123         * those are the only valid card types.
124         */
125        public CreditCardValidator(long options) {
126            super();
127    
128            if (isOn(options, VISA)) {
129                this.cardTypes.add(VISA_VALIDATOR);
130            }
131    
132            if (isOn(options, AMEX)) {
133                this.cardTypes.add(AMEX_VALIDATOR);
134            }
135    
136            if (isOn(options, MASTERCARD)) {
137                this.cardTypes.add(MASTERCARD_VALIDATOR);
138            }
139    
140            if (isOn(options, DISCOVER)) {
141                this.cardTypes.add(DISCOVER_VALIDATOR);
142            }
143    
144            if (isOn(options, DINERS)) {
145                this.cardTypes.add(DINERS_VALIDATOR);
146            }
147        }
148    
149        /**
150         * Create a new CreditCardValidator with the specified {@link CodeValidator}s.
151         * @param creditCardValidators Set of valid code validators
152         */
153        public CreditCardValidator(CodeValidator[] creditCardValidators) {
154            if (creditCardValidators == null) {
155                throw new IllegalArgumentException("Card validators are missing");
156            }
157            for (int i = 0; i < creditCardValidators.length; i++) {
158                cardTypes.add(creditCardValidators[i]);
159            }
160        }
161    
162        /**
163         * Checks if the field is a valid credit card number.
164         * @param card The card number to validate.
165         * @return Whether the card number is valid.
166         */
167        public boolean isValid(String card) {
168            if (card == null || card.length() == 0) {
169                return false;
170            }
171            for (int i = 0; i < cardTypes.size(); i++) {
172                CodeValidator type = (CodeValidator)cardTypes.get(i);
173                if (type.isValid(card)) {
174                    return true;
175                }
176            }
177            return false;
178        }
179    
180        /**
181         * Checks if the field is a valid credit card number.
182         * @param card The card number to validate.
183         * @return The card number if valid or <code>null</code>
184         * if invalid.
185         */
186        public Object validate(String card) {
187            if (card == null || card.length() == 0) {
188                return null;
189            }
190            Object result = null;
191            for (int i = 0; i < cardTypes.size(); i++) {
192                CodeValidator type = (CodeValidator)cardTypes.get(i);
193                result = type.validate(card);
194                if (result != null) {
195                    return result ;
196                }
197            }
198            return null;
199    
200        }
201        /**
202         * Tests whether the given flag is on.  If the flag is not a power of 2 
203         * (ie. 3) this tests whether the combination of flags is on.
204         *
205         * @param options The options specified.
206         * @param flag Flag value to check.
207         *
208         * @return whether the specified flag value is on.
209         */
210        private boolean isOn(long options, long flag) {
211            return (options & flag) > 0;
212        }
213    
214    }