View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.validator.routines;
18  
19  import org.apache.commons.validator.routines.checkdigit.CheckDigit;
20  import org.apache.commons.validator.routines.checkdigit.LuhnCheckDigit;
21  import java.io.Serializable;
22  import java.util.Collections;
23  import java.util.List;
24  import java.util.ArrayList;
25  
26  /**
27   * Perform credit card validations.
28   *
29   * <p>
30   * By default, all supported card types are allowed.  You can specify which
31   * cards should pass validation by configuring the validation options. For
32   * example,
33   * </p>
34   *
35   * <pre>
36   * <code>CreditCardValidator ccv = new CreditCardValidator(CreditCardValidator.AMEX + CreditCardValidator.VISA);</code>
37   * </pre>
38   *
39   * <p>
40   * configures the validator to only pass American Express and Visa cards.
41   * If a card type is not directly supported by this class, you can implement
42   * the CreditCardType interface and pass an instance into the
43   * <code>addAllowedCardType</code> method.
44   * </p>
45   *
46   * <p>
47   * For a similar implementation in Perl, reference Sean M. Burke's
48   * <a href="http://www.speech.cs.cmu.edu/~sburke/pub/luhn_lib.html">script</a>.
49   * More information can be found in Michael Gilleland's essay 
50   * <a href="http://web.archive.org/web/20120614072656/http://www.merriampark.com/anatomycc.htm">Anatomy of Credit Card Numbers</a>.
51   * </p>
52   *
53   * @version $Revision: 1739207 $
54   * @since Validator 1.4
55   */
56  public class CreditCardValidator implements Serializable {
57  
58      private static final long serialVersionUID = 5955978921148959496L;
59  
60      /**
61       * Option specifying that no cards are allowed.  This is useful if
62       * you want only custom card types to validate so you turn off the
63       * default cards with this option.
64       *
65       * <pre>
66       * <code>
67       * CreditCardValidator v = new CreditCardValidator(CreditCardValidator.NONE);
68       * v.addAllowedCardType(customType);
69       * v.isValid(aCardNumber);
70       * </code>
71       * </pre>
72       */
73      public static final long NONE = 0;
74  
75      /**
76       * Option specifying that American Express cards are allowed.
77       */
78      public static final long AMEX = 1 << 0;
79  
80      /**
81       * Option specifying that Visa cards are allowed.
82       */
83      public static final long VISA = 1 << 1;
84  
85      /**
86       * Option specifying that Mastercard cards are allowed.
87       */
88      public static final long MASTERCARD = 1 << 2;
89  
90      /**
91       * Option specifying that Discover cards are allowed.
92       */
93      public static final long DISCOVER = 1 << 3; // CHECKSTYLE IGNORE MagicNumber
94  
95      /**
96       * Option specifying that Diners cards are allowed.
97       */
98      public static final long DINERS = 1 << 4; // CHECKSTYLE IGNORE MagicNumber
99  
100     /**
101      * Option specifying that VPay (Visa) cards are allowed.
102      * @since 1.5.0
103      */
104     public static final long VPAY = 1 << 5; // CHECKSTYLE IGNORE MagicNumber
105 
106     /**
107      * Option specifying that Mastercard cards (pre Oct 2016 only) are allowed.
108      * @deprecated for use until Oct 2016 only
109      */
110     @Deprecated
111     public static final long MASTERCARD_PRE_OCT2016 = 1 << 6; // CHECKSTYLE IGNORE MagicNumber
112 
113 
114     /**
115      * The CreditCardTypes that are allowed to pass validation.
116      */
117     private final List<CodeValidator> cardTypes = new ArrayList<CodeValidator>();
118 
119     /**
120      * Luhn checkdigit validator for the card numbers.
121      */
122     private static final CheckDigit LUHN_VALIDATOR = LuhnCheckDigit.LUHN_CHECK_DIGIT;
123 
124     /** American Express (Amex) Card Validator */
125     public static final CodeValidator AMEX_VALIDATOR = new CodeValidator("^(3[47]\\d{13})$", LUHN_VALIDATOR);
126 
127     /** Diners Card Validator */
128     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);
129 
130     /** Discover Card regular expressions */
131     private static final RegexValidator DISCOVER_REGEX = new RegexValidator(new String[] {"^(6011\\d{12})$", "^(64[4-9]\\d{13})$", "^(65\\d{14})$"});
132 
133     /** Discover Card Validator */
134     public static final CodeValidator DISCOVER_VALIDATOR = new CodeValidator(DISCOVER_REGEX, LUHN_VALIDATOR);
135 
136     /** Mastercard regular expressions */
137     private static final RegexValidator MASTERCARD_REGEX = new RegexValidator(
138         new String[] {
139             "^(5[1-5]\\d{14})$",  // 51 - 55 (pre Oct 2016)
140             // valid from October 2016
141             "^(2221\\d{12})$",    // 222100 - 222199
142             "^(222[2-9]\\d{12})$",// 222200 - 222999
143             "^(22[3-9]\\d{13})$", // 223000 - 229999
144             "^(2[3-6]\\d{14})$",  // 230000 - 269999
145             "^(27[01]\\d{13})$",  // 270000 - 271999
146             "^(2720\\d{12})$",    // 272000 - 272099
147         });
148 
149     /** Mastercard Card Validator */
150     public static final CodeValidator MASTERCARD_VALIDATOR = new CodeValidator(MASTERCARD_REGEX, LUHN_VALIDATOR);
151 
152     /** 
153      * Mastercard Card Validator (pre Oct 2016)
154      * @deprecated for use until Oct 2016 only
155      */
156     @Deprecated
157     public static final CodeValidator MASTERCARD_VALIDATOR_PRE_OCT2016 = new CodeValidator("^(5[1-5]\\d{14})$", LUHN_VALIDATOR);
158 
159     /** Visa Card Validator */
160     public static final CodeValidator VISA_VALIDATOR = new CodeValidator("^(4)(\\d{12}|\\d{15})$", LUHN_VALIDATOR);
161 
162     /** VPay (Visa) Card Validator 
163      * @since 1.5.0
164      */
165     public static final CodeValidator VPAY_VALIDATOR = new CodeValidator("^(4)(\\d{12,18})$", LUHN_VALIDATOR);
166 
167     /**
168      * Create a new CreditCardValidator with default options.
169      * The default options are:
170      * AMEX, VISA, MASTERCARD and DISCOVER
171      */
172     public CreditCardValidator() {
173         this(AMEX + VISA + MASTERCARD + DISCOVER);
174     }
175 
176     /**
177      * Create a new CreditCardValidator with the specified options.
178      * @param options Pass in
179      * CreditCardValidator.VISA + CreditCardValidator.AMEX to specify that
180      * those are the only valid card types.
181      */
182     public CreditCardValidator(long options) {
183         super();
184 
185         if (isOn(options, VISA)) {
186             this.cardTypes.add(VISA_VALIDATOR);
187         }
188 
189         if (isOn(options, VPAY)) {
190             this.cardTypes.add(VPAY_VALIDATOR);
191         }
192 
193         if (isOn(options, AMEX)) {
194             this.cardTypes.add(AMEX_VALIDATOR);
195         }
196 
197         if (isOn(options, MASTERCARD)) {
198             this.cardTypes.add(MASTERCARD_VALIDATOR);
199         }
200 
201         if (isOn(options, MASTERCARD_PRE_OCT2016)) {
202             this.cardTypes.add(MASTERCARD_VALIDATOR_PRE_OCT2016);
203         }
204 
205         if (isOn(options, DISCOVER)) {
206             this.cardTypes.add(DISCOVER_VALIDATOR);
207         }
208 
209         if (isOn(options, DINERS)) {
210             this.cardTypes.add(DINERS_VALIDATOR);
211         }
212     }
213 
214     /**
215      * Create a new CreditCardValidator with the specified {@link CodeValidator}s.
216      * @param creditCardValidators Set of valid code validators
217      */
218     public CreditCardValidator(CodeValidator[] creditCardValidators) {
219         if (creditCardValidators == null) {
220             throw new IllegalArgumentException("Card validators are missing");
221         }
222         Collections.addAll(cardTypes, creditCardValidators);
223     }
224 
225     /**
226      * Checks if the field is a valid credit card number.
227      * @param card The card number to validate.
228      * @return Whether the card number is valid.
229      */
230     public boolean isValid(String card) {
231         if (card == null || card.length() == 0) {
232             return false;
233         }
234         for (CodeValidator cardType : cardTypes) {
235             if (cardType.isValid(card)) {
236                 return true;
237             }
238         }
239         return false;
240     }
241 
242     /**
243      * Checks if the field is a valid credit card number.
244      * @param card The card number to validate.
245      * @return The card number if valid or <code>null</code>
246      * if invalid.
247      */
248     public Object validate(String card) {
249         if (card == null || card.length() == 0) {
250             return null;
251         }
252         Object result = null;
253         for (CodeValidator cardType : cardTypes) {
254             result = cardType.validate(card);
255             if (result != null) {
256                 return result;
257             }
258         }
259         return null;
260 
261     }
262     /**
263      * Tests whether the given flag is on.  If the flag is not a power of 2
264      * (ie. 3) this tests whether the combination of flags is on.
265      *
266      * @param options The options specified.
267      * @param flag Flag value to check.
268      *
269      * @return whether the specified flag value is on.
270      */
271     private boolean isOn(long options, long flag) {
272         return (options & flag) > 0;
273     }
274 
275 }