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 org.apache.commons.validator.routines.checkdigit.CheckDigit; 020import org.apache.commons.validator.routines.checkdigit.LuhnCheckDigit; 021import java.io.Serializable; 022import java.util.Collections; 023import java.util.List; 024import java.util.ArrayList; 025 026/** 027 * Perform credit card validations. 028 * 029 * <p> 030 * By default, AMEX + VISA + MASTERCARD + DISCOVER card types are allowed. You can specify which 031 * cards should pass validation by configuring the validation options. For 032 * example, 033 * </p> 034 * 035 * <pre> 036 * <code>CreditCardValidator ccv = new CreditCardValidator(CreditCardValidator.AMEX + CreditCardValidator.VISA);</code> 037 * </pre> 038 * 039 * <p> 040 * configures the validator to only pass American Express and Visa cards. 041 * If a card type is not directly supported by this class, you can create an 042 * instance of the {@link CodeValidator} class and pass it to a {@link CreditCardValidator} 043 * constructor along with any existing validators. For example: 044 * </p> 045 * 046 * <pre> 047 * <code>CreditCardValidator ccv = new CreditCardValidator( 048 * new CodeValidator[] { 049 * CreditCardValidator.AMEX_VALIDATOR, 050 * CreditCardValidator.VISA_VALIDATOR, 051 * new CodeValidator("^(4)(\\d{12,18})$", LUHN_VALIDATOR) // add VPAY 052 * };</code> 053 * </pre> 054 * 055 * <p> 056 * Alternatively you can define a validator using the {@link CreditCardRange} class. 057 * For example: 058 * </p> 059 * 060 * <pre> 061 * <code>CreditCardValidator ccv = new CreditCardValidator( 062 * new CreditCardRange[]{ 063 * new CreditCardRange("300", "305", 14, 14), // Diners 064 * new CreditCardRange("3095", null, 14, 14), // Diners 065 * new CreditCardRange("36", null, 14, 14), // Diners 066 * new CreditCardRange("38", "39", 14, 14), // Diners 067 * new CreditCardRange("4", null, new int[] {13, 16}), // VISA 068 * } 069 * ); 070 * </code> 071 * </pre> 072 * <p> 073 * This can be combined with a list of {@code CodeValidator}s 074 * </p> 075 * <p> 076 * More information can be found in Michael Gilleland's essay 077 * <a href="http://web.archive.org/web/20120614072656/http://www.merriampark.com/anatomycc.htm">Anatomy of Credit Card Numbers</a>. 078 * </p> 079 * 080 * @version $Revision$ 081 * @since Validator 1.4 082 */ 083public class CreditCardValidator implements Serializable { 084 085 private static final long serialVersionUID = 5955978921148959496L; 086 087 private static final int MIN_CC_LENGTH = 12; // minimum allowed length 088 089 private static final int MAX_CC_LENGTH = 19; // maximum allowed length 090 091 /** 092 * Class that represents a credit card range. 093 * @since 1.6 094 */ 095 public static class CreditCardRange { 096 final String low; // e.g. 34 or 644 097 final String high; // e.g. 34 or 65 098 final int minLen; // e.g. 16 or -1 099 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 public CreditCardRange(String low, String high, int minLen, int maxLen) { 119 this.low = low; 120 this.high = high; 121 this.minLen = minLen; 122 this.maxLen = maxLen; 123 this.lengths = null; 124 } 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 public CreditCardRange(String low, String high, int [] lengths) { 142 this.low = low; 143 this.high = high; 144 this.minLen = -1; 145 this.maxLen = -1; 146 this.lengths = lengths.clone(); 147 } 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 private final List<CodeValidator> cardTypes = new ArrayList<CodeValidator>(); 208 209 /** 210 * Luhn checkdigit validator for the card numbers. 211 */ 212 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 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 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 private static final RegexValidator DISCOVER_REGEX = new RegexValidator(new String[] {"^(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 * <p> 247 * 2221xx - 2720xx (16) <br> 248 * 51xxx - 55xxx (16) <br> 249 */ 250 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 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 */ 277 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 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 this(AMEX + VISA + MASTERCARD + DISCOVER); 293 } 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 super(); 303 304 if (isOn(options, VISA)) { 305 this.cardTypes.add(VISA_VALIDATOR); 306 } 307 308 if (isOn(options, VPAY)) { 309 this.cardTypes.add(VPAY_VALIDATOR); 310 } 311 312 if (isOn(options, AMEX)) { 313 this.cardTypes.add(AMEX_VALIDATOR); 314 } 315 316 if (isOn(options, MASTERCARD)) { 317 this.cardTypes.add(MASTERCARD_VALIDATOR); 318 } 319 320 if (isOn(options, MASTERCARD_PRE_OCT2016)) { 321 this.cardTypes.add(MASTERCARD_VALIDATOR_PRE_OCT2016); 322 } 323 324 if (isOn(options, DISCOVER)) { 325 this.cardTypes.add(DISCOVER_VALIDATOR); 326 } 327 328 if (isOn(options, DINERS)) { 329 this.cardTypes.add(DINERS_VALIDATOR); 330 } 331 } 332 333 /** 334 * Create a new CreditCardValidator with the specified {@link CodeValidator}s. 335 * @param creditCardValidators Set of valid code validators 336 */ 337 public CreditCardValidator(CodeValidator[] creditCardValidators) { 338 if (creditCardValidators == null) { 339 throw new IllegalArgumentException("Card validators are missing"); 340 } 341 Collections.addAll(cardTypes, creditCardValidators); 342 } 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 public CreditCardValidator(CreditCardRange[] creditCardRanges) { 350 if (creditCardRanges == null) { 351 throw new IllegalArgumentException("Card ranges are missing"); 352 } 353 Collections.addAll(cardTypes, createRangeValidator(creditCardRanges, LUHN_VALIDATOR)); 354 } 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 public CreditCardValidator(CodeValidator[] creditCardValidators, CreditCardRange[] creditCardRanges) { 367 if (creditCardValidators == null) { 368 throw new IllegalArgumentException("Card validators are missing"); 369 } 370 if (creditCardRanges == null) { 371 throw new IllegalArgumentException("Card ranges are missing"); 372 } 373 Collections.addAll(cardTypes, creditCardValidators); 374 Collections.addAll(cardTypes, createRangeValidator(creditCardRanges, LUHN_VALIDATOR)); 375 } 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 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 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 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 if (card == null || card.length() == 0) { 420 return false; 421 } 422 for (CodeValidator cardType : cardTypes) { 423 if (cardType.isValid(card)) { 424 return true; 425 } 426 } 427 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 if (card == null || card.length() == 0) { 438 return null; 439 } 440 Object result = null; 441 for (CodeValidator cardType : cardTypes) { 442 result = cardType.validate(card); 443 if (result != null) { 444 return result; 445 } 446 } 447 return null; 448 449 } 450 451 // package protected for unit test access 452 static boolean validLength(int valueLength, CreditCardRange range) { 453 if (range.lengths != null) { 454 for(int length : range.lengths) { 455 if (valueLength == length) { 456 return true; 457 } 458 } 459 return false; 460 } 461 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 return new CodeValidator( 467 // must be numeric (rest of validation is done later) 468 new RegexValidator("(\\d+)") { 469 private static final long serialVersionUID = 1L; 470 private CreditCardRange[] ccr = creditCardRanges.clone(); 471 @Override 472 // must return full string 473 public String validate(String value) { 474 if (super.match(value) != null) { 475 int length = value.length(); 476 for(CreditCardRange range : ccr) { 477 if (validLength(length, range)) { 478 if (range.high == null) { // single prefix only 479 if (value.startsWith(range.low)) { 480 return value; 481 } 482 } 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 return value; 487 } 488 } 489 } 490 } 491 return null; 492 } 493 @Override 494 public boolean isValid(String value) { 495 return validate(value) != null; 496 } 497 @Override 498 public String[] match(String value) { 499 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 return (options & flag) > 0; 515 } 516 517}