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.Collections;
023import java.util.List;
024import java.util.ArrayList;
025
026/**
027 * Perform credit card validations.
028 *
029 * <p>
030 * By default, all supported card types are allowed.  You can specify which
031 * cards should pass validation by configuring the validation options. For
032 * example,
033 * </p>
034 *
035 * <pre>
036 * <code>CreditCardValidator ccv = new CreditCardValidator(CreditCardValidator.AMEX + CreditCardValidator.VISA);</code>
037 * </pre>
038 *
039 * <p>
040 * configures the validator to only pass American Express and Visa cards.
041 * If a card type is not directly supported by this class, you can create an
042 * instance of the {@link CodeValidator} class and pass it to a {@link CreditCardValidator}
043 * constructor along with any existing validators. For example:
044 * </p>
045 *
046 * <pre>
047 * <code>CreditCardValidator ccv = new CreditCardValidator(
048 *     new CodeValidator[] {
049 *         CreditCardValidator.AMEX_VALIDATOR,
050 *         CreditCardValidator.VISA_VALIDATOR,
051 *         new CodeValidator("^(4)(\\d{12,18})$", LUHN_VALIDATOR) // add VPAY
052 * };</code>
053 * </pre>
054 *
055 * <p>
056 * Alternatively you can define a validator using the {@link CreditCardRange} class.
057 * For example:
058 * </p>
059 *
060 * <pre>
061 * <code>CreditCardValidator ccv = new CreditCardValidator(
062 *    new CreditCardRange[]{
063 *        new CreditCardRange("300", "305", 14, 14), // Diners
064 *        new CreditCardRange("3095", null, 14, 14), // Diners
065 *        new CreditCardRange("36",   null, 14, 14), // Diners
066 *        new CreditCardRange("38",   "39", 14, 14), // Diners
067 *        new CreditCardRange("4",    null, new int[] {13, 16}), // VISA
068 *    }
069 * );
070 * </code>
071 * </pre>
072 * <p>
073 * This can be combined with a list of {@code CodeValidator}s
074 * </p>
075 * <p>
076 * More information can be found in Michael Gilleland's essay 
077 * <a href="http://web.archive.org/web/20120614072656/http://www.merriampark.com/anatomycc.htm">Anatomy of Credit Card Numbers</a>.
078 * </p>
079 *
080 * @version $Revision: 1782740 $
081 * @since Validator 1.4
082 */
083public class CreditCardValidator implements Serializable {
084
085    private static final long serialVersionUID = 5955978921148959496L;
086
087    private static final int MIN_CC_LENGTH = 12; // minimum allowed length
088
089    private static final int MAX_CC_LENGTH = 19; // maximum allowed length
090
091    /**
092     * Class that represents a credit card range.
093     * @since 1.6
094     */
095    public static class CreditCardRange {
096        final String low; // e.g. 34 or 644
097        final String high; // e.g. 34 or 65
098        final int minLen; // e.g. 16 or -1
099        final int maxLen; // e.g. 19 or -1
100        final int lengths[]; // e.g. 16,18,19
101
102        /**
103         * Create a credit card range specifier for use in validation
104         * of the number syntax including the IIN range.
105         * <p>
106         * The low and high parameters may be shorter than the length
107         * of an IIN (currently 6 digits) in which case subsequent digits
108         * are ignored and may range from 0-9.
109         * <br>
110         * The low and high parameters may be different lengths.
111         * e.g. Discover "644" and "65".
112         * </p>
113         * @param low the low digits of the IIN range
114         * @param high the high digits of the IIN range
115         * @param minLen the minimum length of the entire number
116         * @param maxLen the maximum length of the entire number
117         */
118        public CreditCardRange(String low, String high, int minLen, int maxLen) {
119            this.low = low;
120            this.high = high;
121            this.minLen = minLen;
122            this.maxLen = maxLen;
123            this.lengths = null;
124        }
125
126        /**
127         * Create a credit card range specifier for use in validation
128         * of the number syntax including the IIN range.
129         * <p>
130         * The low and high parameters may be shorter than the length
131         * of an IIN (currently 6 digits) in which case subsequent digits
132         * are ignored and may range from 0-9.
133         * <br>
134         * The low and high parameters may be different lengths.
135         * e.g. Discover "644" and "65".
136         * </p>
137         * @param low the low digits of the IIN range 
138         * @param high the high digits of the IIN range
139         * @param lengths array of valid lengths
140         */
141        public CreditCardRange(String low, String high, int [] lengths) {
142            this.low = low;
143            this.high = high;
144            this.minLen = -1;
145            this.maxLen = -1;
146            this.lengths = lengths.clone();
147        }
148    }
149
150    /**
151     * Option specifying that no cards are allowed.  This is useful if
152     * you want only custom card types to validate so you turn off the
153     * default cards with this option.
154     *
155     * <pre>
156     * <code>
157     * CreditCardValidator v = new CreditCardValidator(CreditCardValidator.NONE);
158     * v.addAllowedCardType(customType);
159     * v.isValid(aCardNumber);
160     * </code>
161     * </pre>
162     */
163    public static final long NONE = 0;
164
165    /**
166     * Option specifying that American Express cards are allowed.
167     */
168    public static final long AMEX = 1 << 0;
169
170    /**
171     * Option specifying that Visa cards are allowed.
172     */
173    public static final long VISA = 1 << 1;
174
175    /**
176     * Option specifying that Mastercard cards are allowed.
177     */
178    public static final long MASTERCARD = 1 << 2;
179
180    /**
181     * Option specifying that Discover cards are allowed.
182     */
183    public static final long DISCOVER = 1 << 3; // CHECKSTYLE IGNORE MagicNumber
184
185    /**
186     * Option specifying that Diners cards are allowed.
187     */
188    public static final long DINERS = 1 << 4; // CHECKSTYLE IGNORE MagicNumber
189
190    /**
191     * Option specifying that VPay (Visa) cards are allowed.
192     * @since 1.5.0
193     */
194    public static final long VPAY = 1 << 5; // CHECKSTYLE IGNORE MagicNumber
195
196    /**
197     * Option specifying that Mastercard cards (pre Oct 2016 only) are allowed.
198     * @deprecated for use until Oct 2016 only
199     */
200    @Deprecated
201    public static final long MASTERCARD_PRE_OCT2016 = 1 << 6; // CHECKSTYLE IGNORE MagicNumber
202
203
204    /**
205     * The CreditCardTypes that are allowed to pass validation.
206     */
207    private final List<CodeValidator> cardTypes = new ArrayList<CodeValidator>();
208
209    /**
210     * Luhn checkdigit validator for the card numbers.
211     */
212    private static final CheckDigit LUHN_VALIDATOR = LuhnCheckDigit.LUHN_CHECK_DIGIT;
213
214    /**
215     * American Express (Amex) Card Validator
216     * <p>
217     * 34xxxx (15) <br>
218     * 37xxxx (15) <br>
219     */
220    public static final CodeValidator AMEX_VALIDATOR = new CodeValidator("^(3[47]\\d{13})$", LUHN_VALIDATOR);
221
222    /**
223     * Diners Card Validator
224     * <p>
225     * 300xxx - 305xxx (14) <br>
226     * 3095xx (14) <br>
227     * 36xxxx (14) <br>
228     * 38xxxx (14) <br>
229     * 39xxxx (14) <br>
230     */
231    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);
232
233    /**
234     * Discover Card regular expressions
235     * <p>
236     * 6011xx (16) <br>
237     * 644xxx - 65xxxx (16) <br>
238     */
239    private static final RegexValidator DISCOVER_REGEX = new RegexValidator(new String[] {"^(6011\\d{12})$", "^(64[4-9]\\d{13})$", "^(65\\d{14})$"});
240
241    /** Discover Card Validator */
242    public static final CodeValidator DISCOVER_VALIDATOR = new CodeValidator(DISCOVER_REGEX, LUHN_VALIDATOR);
243
244    /**
245     * Mastercard regular expressions
246     * <p>
247     * 2221xx - 2720xx (16) <br>
248     * 51xxx - 55xxx (16) <br>
249     */
250    private static final RegexValidator MASTERCARD_REGEX = new RegexValidator(
251        new String[] {
252            "^(5[1-5]\\d{14})$",  // 51 - 55 (pre Oct 2016)
253            // valid from October 2016
254            "^(2221\\d{12})$",    // 222100 - 222199
255            "^(222[2-9]\\d{12})$",// 222200 - 222999
256            "^(22[3-9]\\d{13})$", // 223000 - 229999
257            "^(2[3-6]\\d{14})$",  // 230000 - 269999
258            "^(27[01]\\d{13})$",  // 270000 - 271999
259            "^(2720\\d{12})$",    // 272000 - 272099
260        });
261
262    /** Mastercard Card Validator */
263    public static final CodeValidator MASTERCARD_VALIDATOR = new CodeValidator(MASTERCARD_REGEX, LUHN_VALIDATOR);
264
265    /** 
266     * Mastercard Card Validator (pre Oct 2016)
267     * @deprecated for use until Oct 2016 only
268     */
269    @Deprecated
270    public static final CodeValidator MASTERCARD_VALIDATOR_PRE_OCT2016 = new CodeValidator("^(5[1-5]\\d{14})$", LUHN_VALIDATOR);
271
272    /**
273     * Visa Card Validator
274     * <p>
275     * 4xxxxx (13 or 16)
276     */
277    public static final CodeValidator VISA_VALIDATOR = new CodeValidator("^(4)(\\d{12}|\\d{15})$", LUHN_VALIDATOR);
278
279    /** VPay (Visa) Card Validator
280     * <p>
281     * 4xxxxx (13-19) 
282     * @since 1.5.0
283     */
284    public static final CodeValidator VPAY_VALIDATOR = new CodeValidator("^(4)(\\d{12,18})$", LUHN_VALIDATOR);
285
286    /**
287     * Create a new CreditCardValidator with default options.
288     * The default options are:
289     * AMEX, VISA, MASTERCARD and DISCOVER
290     */
291    public CreditCardValidator() {
292        this(AMEX + VISA + MASTERCARD + DISCOVER);
293    }
294
295    /**
296     * Create a new CreditCardValidator with the specified options.
297     * @param options Pass in
298     * CreditCardValidator.VISA + CreditCardValidator.AMEX to specify that
299     * those are the only valid card types.
300     */
301    public CreditCardValidator(long options) {
302        super();
303
304        if (isOn(options, VISA)) {
305            this.cardTypes.add(VISA_VALIDATOR);
306        }
307
308        if (isOn(options, VPAY)) {
309            this.cardTypes.add(VPAY_VALIDATOR);
310        }
311
312        if (isOn(options, AMEX)) {
313            this.cardTypes.add(AMEX_VALIDATOR);
314        }
315
316        if (isOn(options, MASTERCARD)) {
317            this.cardTypes.add(MASTERCARD_VALIDATOR);
318        }
319
320        if (isOn(options, MASTERCARD_PRE_OCT2016)) {
321            this.cardTypes.add(MASTERCARD_VALIDATOR_PRE_OCT2016);
322        }
323
324        if (isOn(options, DISCOVER)) {
325            this.cardTypes.add(DISCOVER_VALIDATOR);
326        }
327
328        if (isOn(options, DINERS)) {
329            this.cardTypes.add(DINERS_VALIDATOR);
330        }
331    }
332
333    /**
334     * Create a new CreditCardValidator with the specified {@link CodeValidator}s.
335     * @param creditCardValidators Set of valid code validators
336     */
337    public CreditCardValidator(CodeValidator[] creditCardValidators) {
338        if (creditCardValidators == null) {
339            throw new IllegalArgumentException("Card validators are missing");
340        }
341        Collections.addAll(cardTypes, creditCardValidators);
342    }
343
344    /**
345     * Create a new CreditCardValidator with the specified {@link CreditCardRange}s.
346     * @param creditCardRanges Set of valid code validators
347     * @since 1.6
348     */
349    public CreditCardValidator(CreditCardRange[] creditCardRanges) {
350        if (creditCardRanges == null) {
351            throw new IllegalArgumentException("Card ranges are missing");
352        }
353        Collections.addAll(cardTypes, createRangeValidator(creditCardRanges, LUHN_VALIDATOR));
354    }
355
356    /**
357     * Create a new CreditCardValidator with the specified {@link CodeValidator}s
358     * and {@link CreditCardRange}s.
359     * <p>
360     * This can be used to combine predefined validators such as {@link #MASTERCARD_VALIDATOR}
361     * with additional validators using the simpler {@link CreditCardRange}s.
362     * @param creditCardValidators Set of valid code validators
363     * @param creditCardRanges Set of valid code validators
364     * @since 1.6
365     */
366    public CreditCardValidator(CodeValidator[] creditCardValidators, CreditCardRange[] creditCardRanges) {
367        if (creditCardValidators == null) {
368            throw new IllegalArgumentException("Card validators are missing");
369        }
370        if (creditCardRanges == null) {
371            throw new IllegalArgumentException("Card ranges are missing");
372        }
373        Collections.addAll(cardTypes, creditCardValidators);
374        Collections.addAll(cardTypes, createRangeValidator(creditCardRanges, LUHN_VALIDATOR));
375    }
376
377    /**
378     * Create a new generic CreditCardValidator which validates the syntax and check digit only.
379     * Does not check the Issuer Identification Number (IIN)
380     *
381     * @param minLen minimum allowed length
382     * @param maxLen maximum allowed length
383     * @return the validator
384     * @since 1.6
385     */
386    public static CreditCardValidator genericCreditCardValidator(int minLen, int maxLen) {
387        return new CreditCardValidator(new CodeValidator[] {new CodeValidator("(\\d+)", minLen, maxLen, LUHN_VALIDATOR)});
388    }
389
390    /**
391     * Create a new generic CreditCardValidator which validates the syntax and check digit only.
392     * Does not check the Issuer Identification Number (IIN)
393     *
394     * @param length exact length
395     * @return the validator
396     * @since 1.6
397     */
398    public static CreditCardValidator genericCreditCardValidator(int length) {
399        return genericCreditCardValidator(length, length);
400    }
401
402    /**
403     * Create a new generic CreditCardValidator which validates the syntax and check digit only.
404     * Does not check the Issuer Identification Number (IIN)
405     *
406     * @return the validator
407     * @since 1.6
408     */
409    public static CreditCardValidator genericCreditCardValidator() {
410        return genericCreditCardValidator(MIN_CC_LENGTH, MAX_CC_LENGTH);
411    }
412
413    /**
414     * Checks if the field is a valid credit card number.
415     * @param card The card number to validate.
416     * @return Whether the card number is valid.
417     */
418    public boolean isValid(String card) {
419        if (card == null || card.length() == 0) {
420            return false;
421        }
422        for (CodeValidator cardType : cardTypes) {
423            if (cardType.isValid(card)) {
424                return true;
425            }
426        }
427        return false;
428    }
429
430    /**
431     * Checks if the field is a valid credit card number.
432     * @param card The card number to validate.
433     * @return The card number if valid or <code>null</code>
434     * if invalid.
435     */
436    public Object validate(String card) {
437        if (card == null || card.length() == 0) {
438            return null;
439        }
440        Object result = null;
441        for (CodeValidator cardType : cardTypes) {
442            result = cardType.validate(card);
443            if (result != null) {
444                return result;
445            }
446        }
447        return null;
448
449    }
450
451    // package protected for unit test access
452    static boolean validLength(int valueLength, CreditCardRange range) {
453        if (range.lengths != null) {
454            for(int length : range.lengths) {
455                if (valueLength == length) {
456                    return true;
457                }
458            }
459            return false;
460        }
461        return valueLength >= range.minLen && valueLength <= range.maxLen;
462    }
463
464    // package protected for unit test access
465    static CodeValidator createRangeValidator(final CreditCardRange[] creditCardRanges, final CheckDigit digitCheck ) {
466        return new CodeValidator(
467                // must be numeric (rest of validation is done later)
468                new RegexValidator("(\\d+)") {
469                    private static final long serialVersionUID = 1L;
470                    private CreditCardRange[] ccr = creditCardRanges.clone();
471                    @Override
472                    // must return full string
473                    public String validate(String value) {
474                        if (super.match(value) != null) {
475                            int length = value.length();
476                            for(CreditCardRange range : ccr) {
477                                if (validLength(length, range)) {
478                                    if (range.high == null) { // single prefix only
479                                        if (value.startsWith(range.low)) {
480                                            return value;
481                                        }
482                                    } else if (range.low.compareTo(value) <= 0 // no need to trim value here
483                                                &&
484                                                // here we have to ignore digits beyond the prefix
485                                                range.high.compareTo(value.substring(0, range.high.length())) >= 0) {
486                                               return value;
487                                    }
488                                }
489                            }
490                        }
491                        return null;
492                    }
493                    @Override
494                    public boolean isValid(String value) {
495                        return validate(value) != null;
496                    }
497                    @Override
498                    public String[] match(String value) {
499                        return new String[]{validate(value)};
500                    }
501                }, digitCheck);
502    }
503
504    /**
505     * Tests whether the given flag is on.  If the flag is not a power of 2
506     * (ie. 3) this tests whether the combination of flags is on.
507     *
508     * @param options The options specified.
509     * @param flag Flag value to check.
510     *
511     * @return whether the specified flag value is on.
512     */
513    private boolean isOn(long options, long flag) {
514        return (options & flag) > 0;
515    }
516
517}