View Javadoc

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