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