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