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}