View Javadoc

1   /*
2    * $Id: CreditCardValidator.java 280974 2005-09-15 00:06:59Z niallp $
3    * $Rev: 280974 $
4    * $Date: 2005-09-15 01:06:59 +0100 (Thu, 15 Sep 2005) $
5    *
6    * ====================================================================
7    * Copyright 2001-2005 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      * @return Whether the card number is valid.
127      */
128     public boolean isValid(String card) {
129         if ((card == null) || (card.length() < 13) || (card.length() > 19)) {
130             return false;
131         }
132 
133         if (!this.luhnCheck(card)) {
134             return false;
135         }
136         
137         Iterator types = this.cardTypes.iterator();
138         while (types.hasNext()) {
139             CreditCardType type = (CreditCardType) types.next();
140             if (type.matches(card)) {
141                 return true;
142             }
143         }
144 
145         return false;
146     }
147     
148     /***
149      * Add an allowed CreditCardType that participates in the card 
150      * validation algorithm.
151      * @param type The type that is now allowed to pass validation.
152      * @since Validator 1.1.2
153      */
154     public void addAllowedCardType(CreditCardType type){
155         this.cardTypes.add(type);
156     }
157 
158     /***
159      * Checks for a valid credit card number.
160      * @param cardNumber Credit Card Number.
161      * @return Whether the card number passes the luhnCheck.
162      */
163     protected boolean luhnCheck(String cardNumber) {
164         // number must be validated as 0..9 numeric first!!
165         int digits = cardNumber.length();
166         int oddOrEven = digits & 1;
167         long sum = 0;
168         for (int count = 0; count < digits; count++) {
169             int digit = 0;
170             try {
171                 digit = Integer.parseInt(cardNumber.charAt(count) + "");
172             } catch(NumberFormatException e) {
173                 return false;
174             }
175 
176             if (((count & 1) ^ oddOrEven) == 0) { // not
177                 digit *= 2;
178                 if (digit > 9) {
179                     digit -= 9;
180                 }
181             }
182             sum += digit;
183         }
184 
185         return (sum == 0) ? false : (sum % 10 == 0);
186     }
187     
188     /***
189      * CreditCardType implementations define how validation is performed
190      * for one type/brand of credit card.
191      * @since Validator 1.1.2
192      */
193     public interface CreditCardType {
194         
195         /***
196          * Returns true if the card number matches this type of credit
197          * card.  Note that this method is <strong>not</strong> responsible
198          * for analyzing the general form of the card number because 
199          * <code>CreditCardValidator</code> performs those checks before 
200          * calling this method.  It is generally only required to valid the
201          * length and prefix of the number to determine if it's the correct 
202          * type. 
203          * @param card The card number, never null.
204          * @return true if the number matches.
205          */
206         boolean matches(String card);
207         
208     }
209     
210     /***
211      *  Augmented to support Visa Carte Blue used in France
212      */
213     private class Visa implements CreditCardType {
214         private static final String PREFIX = "4,5,";
215         public boolean matches(String card) {
216             
217         String prefix2 = card.substring(0, 1) + ",";
218         return ((PREFIX.indexOf(prefix2) != -1)
219             && (card.length() == 13 || card.length() == 16));
220         }
221     }    
222             
223     private class Amex implements CreditCardType {
224         private static final String PREFIX = "34,37,";
225         public boolean matches(String card) {
226             String prefix2 = card.substring(0, 2) + ",";
227             return ((PREFIX.indexOf(prefix2) != -1) && (card.length() == 15));
228         }
229     }
230     
231     private class Discover implements CreditCardType {
232         private static final String PREFIX = "6011";
233         public boolean matches(String card) {
234             return (card.substring(0, 4).equals(PREFIX) && (card.length() == 16));
235         }
236     }
237     
238     private class Mastercard implements CreditCardType {
239         private static final String PREFIX = "51,52,53,54,55,";
240         public boolean matches(String card) {
241             String prefix2 = card.substring(0, 2) + ",";
242             return ((PREFIX.indexOf(prefix2) != -1) && (card.length() == 16));
243         }
244     }
245 
246 }