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