Classes in this File | Line Coverage | Branch Coverage | Complexity | ||||
CreditCardValidator |
|
| 3.3333333333333335;3.333 | ||||
CreditCardValidator$1 |
|
| 3.3333333333333335;3.333 | ||||
CreditCardValidator$CreditCardRange |
|
| 3.3333333333333335;3.333 |
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 create an | |
42 | * instance of the {@link CodeValidator} class and pass it to a {@link CreditCardValidator} | |
43 | * constructor along with any existing validators. For example: | |
44 | * </p> | |
45 | * | |
46 | * <pre> | |
47 | * <code>CreditCardValidator ccv = new CreditCardValidator( | |
48 | * new CodeValidator[] { | |
49 | * CreditCardValidator.AMEX_VALIDATOR, | |
50 | * CreditCardValidator.VISA_VALIDATOR, | |
51 | * new CodeValidator("^(4)(\\d{12,18})$", LUHN_VALIDATOR) // add VPAY | |
52 | * };</code> | |
53 | * </pre> | |
54 | * | |
55 | * <p> | |
56 | * Alternatively you can define a validator using the {@link CreditCardRange} class. | |
57 | * For example: | |
58 | * </p> | |
59 | * | |
60 | * <pre> | |
61 | * <code>CreditCardValidator ccv = new CreditCardValidator( | |
62 | * new CreditCardRange[]{ | |
63 | * new CreditCardRange("300", "305", 14, 14), // Diners | |
64 | * new CreditCardRange("3095", null, 14, 14), // Diners | |
65 | * new CreditCardRange("36", null, 14, 14), // Diners | |
66 | * new CreditCardRange("38", "39", 14, 14), // Diners | |
67 | * new CreditCardRange("4", null, new int[] {13, 16}), // VISA | |
68 | * } | |
69 | * ); | |
70 | * </code> | |
71 | * </pre> | |
72 | * <p> | |
73 | * This can be combined with a list of {@code CodeValidator}s | |
74 | * </p> | |
75 | * <p> | |
76 | * More information can be found in Michael Gilleland's essay | |
77 | * <a href="http://web.archive.org/web/20120614072656/http://www.merriampark.com/anatomycc.htm">Anatomy of Credit Card Numbers</a>. | |
78 | * </p> | |
79 | * | |
80 | * @version $Revision: 1782740 $ | |
81 | * @since Validator 1.4 | |
82 | */ | |
83 | public class CreditCardValidator implements Serializable { | |
84 | ||
85 | private static final long serialVersionUID = 5955978921148959496L; | |
86 | ||
87 | private static final int MIN_CC_LENGTH = 12; // minimum allowed length | |
88 | ||
89 | private static final int MAX_CC_LENGTH = 19; // maximum allowed length | |
90 | ||
91 | /** | |
92 | * Class that represents a credit card range. | |
93 | * @since 1.6 | |
94 | */ | |
95 | public static class CreditCardRange { | |
96 | final String low; // e.g. 34 or 644 | |
97 | final String high; // e.g. 34 or 65 | |
98 | final int minLen; // e.g. 16 or -1 | |
99 | 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 | 14 | public CreditCardRange(String low, String high, int minLen, int maxLen) { |
119 | 14 | this.low = low; |
120 | 14 | this.high = high; |
121 | 14 | this.minLen = minLen; |
122 | 14 | this.maxLen = maxLen; |
123 | 14 | this.lengths = null; |
124 | 14 | } |
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 | 7 | public CreditCardRange(String low, String high, int [] lengths) { |
142 | 7 | this.low = low; |
143 | 7 | this.high = high; |
144 | 7 | this.minLen = -1; |
145 | 7 | this.maxLen = -1; |
146 | 7 | this.lengths = lengths.clone(); |
147 | 7 | } |
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 | 16 | private final List<CodeValidator> cardTypes = new ArrayList<CodeValidator>(); |
208 | ||
209 | /** | |
210 | * Luhn checkdigit validator for the card numbers. | |
211 | */ | |
212 | 1 | 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 | 1 | 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 | 1 | 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 | 1 | 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 | 1 | 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 | 1 | 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 | 1 | 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 | 1 | 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 | 1 | 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 | 1 | 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 | 2 | this(AMEX + VISA + MASTERCARD + DISCOVER); |
293 | 2 | } |
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 | 10 | super(); |
303 | ||
304 | 10 | if (isOn(options, VISA)) { |
305 | 3 | this.cardTypes.add(VISA_VALIDATOR); |
306 | } | |
307 | ||
308 | 10 | if (isOn(options, VPAY)) { |
309 | 1 | this.cardTypes.add(VPAY_VALIDATOR); |
310 | } | |
311 | ||
312 | 10 | if (isOn(options, AMEX)) { |
313 | 4 | this.cardTypes.add(AMEX_VALIDATOR); |
314 | } | |
315 | ||
316 | 10 | if (isOn(options, MASTERCARD)) { |
317 | 3 | this.cardTypes.add(MASTERCARD_VALIDATOR); |
318 | } | |
319 | ||
320 | 10 | if (isOn(options, MASTERCARD_PRE_OCT2016)) { |
321 | 0 | this.cardTypes.add(MASTERCARD_VALIDATOR_PRE_OCT2016); |
322 | } | |
323 | ||
324 | 10 | if (isOn(options, DISCOVER)) { |
325 | 3 | this.cardTypes.add(DISCOVER_VALIDATOR); |
326 | } | |
327 | ||
328 | 10 | if (isOn(options, DINERS)) { |
329 | 1 | this.cardTypes.add(DINERS_VALIDATOR); |
330 | } | |
331 | 10 | } |
332 | ||
333 | /** | |
334 | * Create a new CreditCardValidator with the specified {@link CodeValidator}s. | |
335 | * @param creditCardValidators Set of valid code validators | |
336 | */ | |
337 | 3 | public CreditCardValidator(CodeValidator[] creditCardValidators) { |
338 | 3 | if (creditCardValidators == null) { |
339 | 1 | throw new IllegalArgumentException("Card validators are missing"); |
340 | } | |
341 | 2 | Collections.addAll(cardTypes, creditCardValidators); |
342 | 2 | } |
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 | 2 | public CreditCardValidator(CreditCardRange[] creditCardRanges) { |
350 | 2 | if (creditCardRanges == null) { |
351 | 0 | throw new IllegalArgumentException("Card ranges are missing"); |
352 | } | |
353 | 2 | Collections.addAll(cardTypes, createRangeValidator(creditCardRanges, LUHN_VALIDATOR)); |
354 | 2 | } |
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 | 1 | public CreditCardValidator(CodeValidator[] creditCardValidators, CreditCardRange[] creditCardRanges) { |
367 | 1 | if (creditCardValidators == null) { |
368 | 0 | throw new IllegalArgumentException("Card validators are missing"); |
369 | } | |
370 | 1 | if (creditCardRanges == null) { |
371 | 0 | throw new IllegalArgumentException("Card ranges are missing"); |
372 | } | |
373 | 1 | Collections.addAll(cardTypes, creditCardValidators); |
374 | 1 | Collections.addAll(cardTypes, createRangeValidator(creditCardRanges, LUHN_VALIDATOR)); |
375 | 1 | } |
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 | 1 | 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 | 0 | 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 | 1 | 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 | 129 | if (card == null || card.length() == 0) { |
420 | 4 | return false; |
421 | } | |
422 | 125 | for (CodeValidator cardType : cardTypes) { |
423 | 227 | if (cardType.isValid(card)) { |
424 | 41 | return true; |
425 | } | |
426 | } | |
427 | 84 | 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 | 15 | if (card == null || card.length() == 0) { |
438 | 1 | return null; |
439 | } | |
440 | 14 | Object result = null; |
441 | 14 | for (CodeValidator cardType : cardTypes) { |
442 | 14 | result = cardType.validate(card); |
443 | 14 | if (result != null) { |
444 | 9 | return result; |
445 | } | |
446 | } | |
447 | 5 | return null; |
448 | ||
449 | } | |
450 | ||
451 | // package protected for unit test access | |
452 | static boolean validLength(int valueLength, CreditCardRange range) { | |
453 | 81 | if (range.lengths != null) { |
454 | 23 | for(int length : range.lengths) { |
455 | 19 | if (valueLength == length) { |
456 | 7 | return true; |
457 | } | |
458 | } | |
459 | 4 | return false; |
460 | } | |
461 | 70 | 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 | 4 | return new CodeValidator( |
467 | // must be numeric (rest of validation is done later) | |
468 | 4 | new RegexValidator("(\\d+)") { |
469 | private static final long serialVersionUID = 1L; | |
470 | 4 | private CreditCardRange[] ccr = creditCardRanges.clone(); |
471 | @Override | |
472 | // must return full string | |
473 | public String validate(String value) { | |
474 | 29 | if (super.match(value) != null) { |
475 | 29 | int length = value.length(); |
476 | 84 | for(CreditCardRange range : ccr) { |
477 | 68 | if (validLength(length, range)) { |
478 | 18 | if (range.high == null) { // single prefix only |
479 | 5 | if (value.startsWith(range.low)) { |
480 | 2 | return value; |
481 | } | |
482 | 13 | } 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 | 11 | return value; |
487 | } | |
488 | } | |
489 | } | |
490 | } | |
491 | 16 | return null; |
492 | } | |
493 | @Override | |
494 | public boolean isValid(String value) { | |
495 | 0 | return validate(value) != null; |
496 | } | |
497 | @Override | |
498 | public String[] match(String value) { | |
499 | 0 | 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 | 70 | return (options & flag) > 0; |
515 | } | |
516 | ||
517 | } |