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