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