1. /*
  2. * $Header: /home/cvs/jakarta-commons/validator/src/share/org/apache/commons/validator/CreditCardValidator.java,v 1.16 2004/02/21 17:10:29 rleland Exp $
  3. * $Revision: 1.16 $
  4. * $Date: 2004/02/21 17:10:29 $
  5. *
  6. * ====================================================================
  7. * Copyright 2001-2004 The Apache Software Foundation
  8. *
  9. * Licensed under the Apache License, Version 2.0 (the "License");
  10. * you may not use this file except in compliance with the License.
  11. * You may obtain a copy of the License at
  12. *
  13. * http://www.apache.org/licenses/LICENSE-2.0
  14. *
  15. * Unless required by applicable law or agreed to in writing, software
  16. * distributed under the License is distributed on an "AS IS" BASIS,
  17. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  18. * See the License for the specific language governing permissions and
  19. * limitations under the License.
  20. */
  21. package org.apache.commons.validator;
  22. import java.util.ArrayList;
  23. import java.util.Collection;
  24. import java.util.Iterator;
  25. import org.apache.commons.validator.util.Flags;
  26. /**
  27. * <p>Perform credit card validations.</p>
  28. * <p>
  29. * By default, all supported card types are allowed. You can specify which
  30. * cards should pass validation by configuring the validation options. For
  31. * example,<br/><code>CreditCardValidator ccv = new CreditCardValidator(CreditCardValidator.AMEX + CreditCardValidator.VISA);</code>
  32. * configures the validator to only pass American Express and Visa cards.
  33. * If a card type is not directly supported by this class, you can implement
  34. * the CreditCardType interface and pass an instance into the
  35. * <code>addAllowedCardType</code> method.
  36. * </p>
  37. * For a similar implementation in Perl, reference Sean M. Burke's
  38. * <a href="http://www.speech.cs.cmu.edu/~sburke/pub/luhn_lib.html">script</a>.
  39. * More information is also available
  40. * <a href="http://www.merriampark.com/anatomycc.htm">here</a>.
  41. *
  42. * @since Validator 1.1
  43. */
  44. public class CreditCardValidator {
  45. /**
  46. * Option specifying that no cards are allowed. This is useful if
  47. * you want only custom card types to validate so you turn off the
  48. * default cards with this option.
  49. * <br/>
  50. * <pre>
  51. * CreditCardValidator v = new CreditCardValidator(CreditCardValidator.NONE);
  52. * v.addAllowedCardType(customType);
  53. * v.isValid(aCardNumber);
  54. * </pre>
  55. * @since Validator 1.1.2
  56. */
  57. public static final int NONE = 0;
  58. /**
  59. * Option specifying that American Express cards are allowed.
  60. */
  61. public static final int AMEX = 1 << 0;
  62. /**
  63. * Option specifying that Visa cards are allowed.
  64. */
  65. public static final int VISA = 1 << 1;
  66. /**
  67. * Option specifying that Mastercard cards are allowed.
  68. */
  69. public static final int MASTERCARD = 1 << 2;
  70. /**
  71. * Option specifying that Discover cards are allowed.
  72. */
  73. public static final int DISCOVER = 1 << 3;
  74. /**
  75. * The CreditCardTypes that are allowed to pass validation.
  76. */
  77. private Collection cardTypes = new ArrayList();
  78. /**
  79. * Create a new CreditCardValidator with default options.
  80. */
  81. public CreditCardValidator() {
  82. this(AMEX + VISA + MASTERCARD + DISCOVER);
  83. }
  84. /**
  85. * Create a new CreditCardValidator with the specified options.
  86. * @param options Pass in
  87. * CreditCardValidator.VISA + CreditCardValidator.AMEX to specify that
  88. * those are the only valid card types.
  89. */
  90. public CreditCardValidator(int options) {
  91. super();
  92. Flags f = new Flags(options);
  93. if (f.isOn(VISA)) {
  94. this.cardTypes.add(new Visa());
  95. }
  96. if (f.isOn(AMEX)) {
  97. this.cardTypes.add(new Amex());
  98. }
  99. if (f.isOn(MASTERCARD)) {
  100. this.cardTypes.add(new Mastercard());
  101. }
  102. if (f.isOn(DISCOVER)) {
  103. this.cardTypes.add(new Discover());
  104. }
  105. }
  106. /**
  107. * Checks if the field is a valid credit card number.
  108. * @param card The card number to validate.
  109. */
  110. public boolean isValid(String card) {
  111. if ((card == null) || (card.length() < 13) || (card.length() > 19)) {
  112. return false;
  113. }
  114. if (!this.luhnCheck(card)) {
  115. return false;
  116. }
  117. Iterator types = this.cardTypes.iterator();
  118. while (types.hasNext()) {
  119. CreditCardType type = (CreditCardType) types.next();
  120. if (type.matches(card)) {
  121. return true;
  122. }
  123. }
  124. return false;
  125. }
  126. /**
  127. * Add an allowed CreditCardType that participates in the card
  128. * validation algorithm.
  129. * @param type The type that is now allowed to pass validation.
  130. * @since Validator 1.1.2
  131. */
  132. public void addAllowedCardType(CreditCardType type){
  133. this.cardTypes.add(type);
  134. }
  135. /**
  136. * Checks for a valid credit card number.
  137. * @param cardNumber Credit Card Number.
  138. */
  139. protected boolean luhnCheck(String cardNumber) {
  140. // number must be validated as 0..9 numeric first!!
  141. int digits = cardNumber.length();
  142. int oddOrEven = digits & 1;
  143. long sum = 0;
  144. for (int count = 0; count < digits; count++) {
  145. int digit = 0;
  146. try {
  147. digit = Integer.parseInt(cardNumber.charAt(count) + "");
  148. } catch(NumberFormatException e) {
  149. return false;
  150. }
  151. if (((count & 1) ^ oddOrEven) == 0) { // not
  152. digit *= 2;
  153. if (digit > 9) {
  154. digit -= 9;
  155. }
  156. }
  157. sum += digit;
  158. }
  159. return (sum == 0) ? false : (sum % 10 == 0);
  160. }
  161. /**
  162. * Checks for a valid credit card number.
  163. * @param card Credit Card Number.
  164. * @deprecated This will be removed in a future release.
  165. */
  166. protected boolean isValidPrefix(String card) {
  167. if (card.length() < 13) {
  168. return false;
  169. }
  170. return new Visa().matches(card)
  171. || new Amex().matches(card)
  172. || new Mastercard().matches(card)
  173. || new Discover().matches(card);
  174. }
  175. /**
  176. * CreditCardType implementations define how validation is performed
  177. * for one type/brand of credit card.
  178. * @since Validator 1.1.2
  179. */
  180. public interface CreditCardType {
  181. /**
  182. * Returns true if the card number matches this type of credit
  183. * card. Note that this method is <strong>not</strong> responsible
  184. * for analyzing the general form of the card number because
  185. * <code>CreditCardValidator</code> performs those checks before
  186. * calling this method. It is generally only required to valid the
  187. * length and prefix of the number to determine if it's the correct
  188. * type.
  189. * @param card The card number, never null.
  190. * @return true if the number matches.
  191. */
  192. boolean matches(String card);
  193. }
  194. private class Visa implements CreditCardType {
  195. private static final String PREFIX = "4";
  196. public boolean matches(String card) {
  197. return (
  198. card.substring(0, 1).equals(PREFIX)
  199. && (card.length() == 13 || card.length() == 16));
  200. }
  201. }
  202. private class Amex implements CreditCardType {
  203. private static final String PREFIX = "34,37,";
  204. public boolean matches(String card) {
  205. String prefix2 = card.substring(0, 2) + ",";
  206. return ((PREFIX.indexOf(prefix2) != -1) && (card.length() == 15));
  207. }
  208. }
  209. private class Discover implements CreditCardType {
  210. private static final String PREFIX = "6011";
  211. public boolean matches(String card) {
  212. return (card.substring(0, 4).equals(PREFIX) && (card.length() == 16));
  213. }
  214. }
  215. private class Mastercard implements CreditCardType {
  216. private static final String PREFIX = "51,52,53,54,55,";
  217. public boolean matches(String card) {
  218. String prefix2 = card.substring(0, 2) + ",";
  219. return ((PREFIX.indexOf(prefix2) != -1) && (card.length() == 16));
  220. }
  221. }
  222. }