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 *      https://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);}
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 *
078 * @since 1.4
079 */
080public class CreditCardValidator implements Serializable {
081
082    /**
083     * Class that represents a credit card range.
084     *
085     * @since 1.6
086     */
087    public static class CreditCardRange {
088        final String low; // for example, 34 or 644
089        final String high; // for example, 34 or 65
090        final int minLen; // for example, 16 or -1
091        final int maxLen; // for example, 19 or -1
092        final int[] lengths; // for example, 16,18,19
093
094        /**
095         * Create a credit card range specifier for use in validation
096         * of the number syntax including the IIN range.
097         * <p>
098         * The low and high parameters may be shorter than the length
099         * of an IIN (currently 6 digits) in which case subsequent digits
100         * are ignored and may range from 0-9.
101         * </p>
102         * <p>
103         * The low and high parameters may be different lengths.
104         * for example, Discover "644" and "65".
105         * </p>
106         *
107         * @param low the low digits of the IIN range
108         * @param high the high digits of the IIN range
109         * @param minLen the minimum length of the entire number
110         * @param maxLen the maximum length of the entire number
111         */
112        public CreditCardRange(final String low, final String high, final int minLen, final int maxLen) {
113            this.low = low;
114            this.high = high;
115            this.minLen = minLen;
116            this.maxLen = maxLen;
117            this.lengths = null;
118        }
119
120        /**
121         * Create a credit card range specifier for use in validation
122         * of the number syntax including the IIN range.
123         * <p>
124         * The low and high parameters may be shorter than the length
125         * of an IIN (currently 6 digits) in which case subsequent digits
126         * are ignored and may range from 0-9.
127         * </p>
128         * <p>
129         * The low and high parameters may be different lengths.
130         * for example, Discover "644" and "65".
131         * </p>
132         *
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     * }
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     *
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     *
202     * @deprecated for use until Oct 2016 only
203     */
204    @Deprecated
205    public static final long MASTERCARD_PRE_OCT2016 = 1 << 6; // CHECKSTYLE IGNORE MagicNumber
206
207    /**
208     * Luhn checkdigit validator for the card numbers.
209     */
210    private static final CheckDigit LUHN_VALIDATOR = LuhnCheckDigit.LUHN_CHECK_DIGIT;
211
212    /**
213     * American Express (Amex) Card Validator
214     * <ul>
215     * <li>34xxxx (15)</li>
216     * <li>37xxxx (15)</li>
217     * </ul>
218     */
219    public static final CodeValidator AMEX_VALIDATOR = new CodeValidator("^(3[47]\\d{13})$", LUHN_VALIDATOR);
220
221    /**
222     * Diners Card Validator
223     * <ul>
224     * <li>300xxx - 305xxx (14)</li>
225     * <li>3095xx (14)</li>
226     * <li>36xxxx (14)</li>
227     * <li>38xxxx (14)</li>
228     * <li>39xxxx (14)</li>
229     * </ul>
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     * <ul>
236     * <li>6011xx (16)</li>
237     * <li>644xxx - 65xxxx (16)</li>
238     * </ul>
239     */
240    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})$");
241
242    /** Discover Card Validator */
243    public static final CodeValidator DISCOVER_VALIDATOR = new CodeValidator(DISCOVER_REGEX, LUHN_VALIDATOR);
244
245    /**
246     * Mastercard regular expressions
247     * <ul>
248     * <li>2221xx - 2720xx (16)</li>
249     * <li>51xxx - 55xxx (16)</li>
250     * </ul>
251     */
252    private static final RegexValidator MASTERCARD_REGEX = new RegexValidator(
253            "^(5[1-5]\\d{14})$",   // 51 - 55 (pre Oct 2016)
254            // valid from October 2016
255            "^(2221\\d{12})$",     // 222100 - 222199
256            "^(222[2-9]\\d{12})$", // 222200 - 222999
257            "^(22[3-9]\\d{13})$",  // 223000 - 229999
258            "^(2[3-6]\\d{14})$",   // 230000 - 269999
259            "^(27[01]\\d{13})$",   // 270000 - 271999
260            "^(2720\\d{12})$"      // 272000 - 272099
261        );
262
263    /** Mastercard Card Validator */
264    public static final CodeValidator MASTERCARD_VALIDATOR = new CodeValidator(MASTERCARD_REGEX, LUHN_VALIDATOR);
265
266    /**
267     * Mastercard Card Validator (pre Oct 2016)
268     *
269     * @deprecated for use until Oct 2016 only
270     */
271    @Deprecated
272    public static final CodeValidator MASTERCARD_VALIDATOR_PRE_OCT2016 = new CodeValidator("^(5[1-5]\\d{14})$", LUHN_VALIDATOR);
273
274    /**
275     * Visa Card Validator
276     * <p>
277     * 4xxxxx (13 or 16)
278     * </p>
279     */
280    public static final CodeValidator VISA_VALIDATOR = new CodeValidator("^(4)(\\d{12}|\\d{15})$", LUHN_VALIDATOR);
281
282    /**
283     * VPay (Visa) Card Validator
284     * <p>
285     * 4xxxxx (13-19)
286     * </p>
287     *
288     * @since 1.5.0
289     */
290    public static final CodeValidator VPAY_VALIDATOR = new CodeValidator("^(4)(\\d{12,18})$", LUHN_VALIDATOR);
291
292    // package protected for unit test access
293    static CodeValidator createRangeValidator(final CreditCardRange[] creditCardRanges, final CheckDigit digitCheck) {
294        return new CodeValidator(
295                // must be numeric (rest of validation is done later)
296                new RegexValidator("(\\d+)") {
297                    private static final long serialVersionUID = 1L;
298                    private final transient CreditCardRange[] ccr = creditCardRanges.clone();
299
300                    @Override
301                    public boolean isValid(final String value) {
302                        return validate(value) != null;
303                    }
304
305                    @Override
306                    public String[] match(final String value) {
307                        return new String[] { validate(value) };
308                    }
309
310                    @Override
311                    // must return full string
312                    public String validate(final String value) {
313                        if (super.match(value) != null) {
314                            final int length = value.length();
315                            for (final CreditCardRange range : ccr) {
316                                if (validLength(length, range)) {
317                                    if (range.high == null) { // single prefix only
318                                        if (value.startsWith(range.low)) {
319                                            return value;
320                                        }
321                                    } else if (range.low.compareTo(value) <= 0 // no need to trim value here
322                                            &&
323                                    // here we have to ignore digits beyond the prefix
324                                            range.high.compareTo(value.substring(0, range.high.length())) >= 0) {
325                                        return value;
326                                    }
327                                }
328                            }
329                        }
330                        return null;
331                    }
332                }, digitCheck);
333    }
334
335    /**
336     * Creates a new generic CreditCardValidator which validates the syntax and check digit only.
337     * Does not check the Issuer Identification Number (IIN)
338     *
339     * @return the validator
340     * @since 1.6
341     */
342    public static CreditCardValidator genericCreditCardValidator() {
343        return genericCreditCardValidator(MIN_CC_LENGTH, MAX_CC_LENGTH);
344    }
345
346    /**
347     * Creates a new generic CreditCardValidator which validates the syntax and check digit only.
348     * Does not check the Issuer Identification Number (IIN)
349     *
350     * @param length exact length
351     * @return the validator
352     * @since 1.6
353     */
354    public static CreditCardValidator genericCreditCardValidator(final int length) {
355        return genericCreditCardValidator(length, length);
356    }
357
358    /**
359     * Creates a new generic CreditCardValidator which validates the syntax and check digit only.
360     * Does not check the Issuer Identification Number (IIN)
361     *
362     * @param minLen minimum allowed length
363     * @param maxLen maximum allowed length
364     * @return the validator
365     * @since 1.6
366     */
367    public static CreditCardValidator genericCreditCardValidator(final int minLen, final int maxLen) {
368        return new CreditCardValidator(new CodeValidator[] {new CodeValidator("(\\d+)", minLen, maxLen, LUHN_VALIDATOR)});
369    }
370
371    // package protected for unit test access
372    static boolean validLength(final int valueLength, final CreditCardRange range) {
373        if (range.lengths != null) {
374            for (final int length : range.lengths) {
375                if (valueLength == length) {
376                    return true;
377                }
378            }
379            return false;
380        }
381        return valueLength >= range.minLen && valueLength <= range.maxLen;
382    }
383
384    /**
385     * The CreditCardTypes that are allowed to pass validation.
386     */
387    private final List<CodeValidator> cardTypes = new ArrayList<>();
388
389    /**
390     * Constructs a new CreditCardValidator with default options.
391     * The default options are:
392     * AMEX, VISA, MASTERCARD and DISCOVER
393     */
394    public CreditCardValidator() {
395        this(AMEX + VISA + MASTERCARD + DISCOVER);
396    }
397
398    /**
399     * Constructs a new CreditCardValidator with the specified {@link CodeValidator}s.
400     *
401     * @param creditCardValidators Set of valid code validators
402     */
403    public CreditCardValidator(final CodeValidator[] creditCardValidators) {
404        if (creditCardValidators == null) {
405            throw new IllegalArgumentException("Card validators are missing");
406        }
407        Collections.addAll(cardTypes, creditCardValidators);
408    }
409
410    /**
411     * Constructs a new CreditCardValidator with the specified {@link CodeValidator}s
412     * and {@link CreditCardRange}s.
413     * <p>
414     * This can be used to combine predefined validators such as {@link #MASTERCARD_VALIDATOR}
415     * with additional validators using the simpler {@link CreditCardRange}s.
416     *
417     * @param creditCardValidators Set of valid code validators
418     * @param creditCardRanges Set of valid code validators
419     * @since 1.6
420     */
421    public CreditCardValidator(final CodeValidator[] creditCardValidators, final CreditCardRange[] creditCardRanges) {
422        if (creditCardValidators == null) {
423            throw new IllegalArgumentException("Card validators are missing");
424        }
425        if (creditCardRanges == null) {
426            throw new IllegalArgumentException("Card ranges are missing");
427        }
428        Collections.addAll(cardTypes, creditCardValidators);
429        Collections.addAll(cardTypes, createRangeValidator(creditCardRanges, LUHN_VALIDATOR));
430    }
431
432    /**
433     * Constructs a new CreditCardValidator with the specified {@link CreditCardRange}s.
434     *
435     * @param creditCardRanges Set of valid code validators
436     * @since 1.6
437     */
438    public CreditCardValidator(final CreditCardRange[] creditCardRanges) {
439        if (creditCardRanges == null) {
440            throw new IllegalArgumentException("Card ranges are missing");
441        }
442        Collections.addAll(cardTypes, createRangeValidator(creditCardRanges, LUHN_VALIDATOR));
443    }
444
445    /**
446     * Constructs a new CreditCardValidator with the specified options.
447     *
448     * @param options Pass in
449     * CreditCardValidator.VISA + CreditCardValidator.AMEX to specify that
450     * those are the only valid card types.
451     */
452    public CreditCardValidator(final long options) {
453        if (isOn(options, VISA)) {
454            cardTypes.add(VISA_VALIDATOR);
455        }
456
457        if (isOn(options, VPAY)) {
458            cardTypes.add(VPAY_VALIDATOR);
459        }
460
461        if (isOn(options, AMEX)) {
462            cardTypes.add(AMEX_VALIDATOR);
463        }
464
465        if (isOn(options, MASTERCARD)) {
466            cardTypes.add(MASTERCARD_VALIDATOR);
467        }
468
469        if (isOn(options, MASTERCARD_PRE_OCT2016)) {
470            cardTypes.add(MASTERCARD_VALIDATOR_PRE_OCT2016);
471        }
472
473        if (isOn(options, DISCOVER)) {
474            cardTypes.add(DISCOVER_VALIDATOR);
475        }
476
477        if (isOn(options, DINERS)) {
478            cardTypes.add(DINERS_VALIDATOR);
479        }
480    }
481
482    /**
483     * Tests whether the given flag is on. If the flag is not a power of 2
484     * (for example, 3) this tests whether the combination of flags is on.
485     *
486     * @param options The options specified.
487     * @param flag Flag value to check.
488     * @return whether the specified flag value is on.
489     */
490    private boolean isOn(final long options, final long flag) {
491        return (options & flag) > 0;
492    }
493
494    /**
495     * Checks if the field is a valid credit card number.
496     *
497     * @param card The card number to validate.
498     * @return Whether the card number is valid.
499     */
500    public boolean isValid(final String card) {
501        if (GenericValidator.isBlankOrNull(card)) {
502            return false;
503        }
504        for (final CodeValidator cardType : cardTypes) {
505            if (cardType.isValid(card)) {
506                return true;
507            }
508        }
509        return false;
510    }
511
512    /**
513     * Checks if the field is a valid credit card number.
514     *
515     * @param card The card number to validate.
516     * @return The card number if valid or {@code null}
517     * if invalid.
518     */
519    public Object validate(final String card) {
520        if (GenericValidator.isBlankOrNull(card)) {
521            return null;
522        }
523        Object result = null;
524        for (final CodeValidator cardType : cardTypes) {
525            result = cardType.validate(card);
526            if (result != null) {
527                return result;
528            }
529        }
530        return null;
531
532    }
533
534}