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: */
017: package org.apache.commons.validator;
018:
019: import java.util.ArrayList;
020: import java.util.Collection;
021: import java.util.Iterator;
022:
023: import org.apache.commons.validator.util.Flags;
024:
025: /**
026: * <p>Perform credit card validations.</p>
027: * <p>
028: * By default, all supported card types are allowed. You can specify which
029: * cards should pass validation by configuring the validation options. For
030: * example,<br/><code>CreditCardValidator ccv = new CreditCardValidator(CreditCardValidator.AMEX + CreditCardValidator.VISA);</code>
031: * configures the validator to only pass American Express and Visa cards.
032: * If a card type is not directly supported by this class, you can implement
033: * the CreditCardType interface and pass an instance into the
034: * <code>addAllowedCardType</code> method.
035: * </p>
036: * For a similar implementation in Perl, reference Sean M. Burke's
037: * <a href="http://www.speech.cs.cmu.edu/~sburke/pub/luhn_lib.html">script</a>.
038: * More information is also available
039: * <a href="http://www.merriampark.com/anatomycc.htm">here</a>.
040: *
041: * @version $Revision: 478334 $ $Date: 2006-11-22 21:31:54 +0000 (Wed, 22 Nov 2006) $
042: * @since Validator 1.1
043: */
044: public class CreditCardValidator {
045:
046: /**
047: * Option specifying that no cards are allowed. This is useful if
048: * you want only custom card types to validate so you turn off the
049: * default cards with this option.
050: * <br/>
051: * <pre>
052: * CreditCardValidator v = new CreditCardValidator(CreditCardValidator.NONE);
053: * v.addAllowedCardType(customType);
054: * v.isValid(aCardNumber);
055: * </pre>
056: * @since Validator 1.1.2
057: */
058: public static final int NONE = 0;
059:
060: /**
061: * Option specifying that American Express cards are allowed.
062: */
063: public static final int AMEX = 1 << 0;
064:
065: /**
066: * Option specifying that Visa cards are allowed.
067: */
068: public static final int VISA = 1 << 1;
069:
070: /**
071: * Option specifying that Mastercard cards are allowed.
072: */
073: public static final int MASTERCARD = 1 << 2;
074:
075: /**
076: * Option specifying that Discover cards are allowed.
077: */
078: public static final int DISCOVER = 1 << 3;
079:
080: /**
081: * The CreditCardTypes that are allowed to pass validation.
082: */
083: private Collection cardTypes = new ArrayList();
084:
085: /**
086: * Create a new CreditCardValidator with default options.
087: */
088: public CreditCardValidator() {
089: this (AMEX + VISA + MASTERCARD + DISCOVER);
090: }
091:
092: /**
093: * Create a new CreditCardValidator with the specified options.
094: * @param options Pass in
095: * CreditCardValidator.VISA + CreditCardValidator.AMEX to specify that
096: * those are the only valid card types.
097: */
098: public CreditCardValidator(int options) {
099: super ();
100:
101: Flags f = new Flags(options);
102: if (f.isOn(VISA)) {
103: this .cardTypes.add(new Visa());
104: }
105:
106: if (f.isOn(AMEX)) {
107: this .cardTypes.add(new Amex());
108: }
109:
110: if (f.isOn(MASTERCARD)) {
111: this .cardTypes.add(new Mastercard());
112: }
113:
114: if (f.isOn(DISCOVER)) {
115: this .cardTypes.add(new Discover());
116: }
117: }
118:
119: /**
120: * Checks if the field is a valid credit card number.
121: * @param card The card number to validate.
122: * @return Whether the card number is valid.
123: */
124: public boolean isValid(String card) {
125: if ((card == null) || (card.length() < 13)
126: || (card.length() > 19)) {
127: return false;
128: }
129:
130: if (!this .luhnCheck(card)) {
131: return false;
132: }
133:
134: Iterator types = this .cardTypes.iterator();
135: while (types.hasNext()) {
136: CreditCardType type = (CreditCardType) types.next();
137: if (type.matches(card)) {
138: return true;
139: }
140: }
141:
142: return false;
143: }
144:
145: /**
146: * Add an allowed CreditCardType that participates in the card
147: * validation algorithm.
148: * @param type The type that is now allowed to pass validation.
149: * @since Validator 1.1.2
150: */
151: public void addAllowedCardType(CreditCardType type) {
152: this .cardTypes.add(type);
153: }
154:
155: /**
156: * Checks for a valid credit card number.
157: * @param cardNumber Credit Card Number.
158: * @return Whether the card number passes the luhnCheck.
159: */
160: protected boolean luhnCheck(String cardNumber) {
161: // number must be validated as 0..9 numeric first!!
162: int digits = cardNumber.length();
163: int oddOrEven = digits & 1;
164: long sum = 0;
165: for (int count = 0; count < digits; count++) {
166: int digit = 0;
167: try {
168: digit = Integer.parseInt(cardNumber.charAt(count) + "");
169: } catch (NumberFormatException e) {
170: return false;
171: }
172:
173: if (((count & 1) ^ oddOrEven) == 0) { // not
174: digit *= 2;
175: if (digit > 9) {
176: digit -= 9;
177: }
178: }
179: sum += digit;
180: }
181:
182: return (sum == 0) ? false : (sum % 10 == 0);
183: }
184:
185: /**
186: * CreditCardType implementations define how validation is performed
187: * for one type/brand of credit card.
188: * @since Validator 1.1.2
189: */
190: public interface CreditCardType {
191:
192: /**
193: * Returns true if the card number matches this type of credit
194: * card. Note that this method is <strong>not</strong> responsible
195: * for analyzing the general form of the card number because
196: * <code>CreditCardValidator</code> performs those checks before
197: * calling this method. It is generally only required to valid the
198: * length and prefix of the number to determine if it's the correct
199: * type.
200: * @param card The card number, never null.
201: * @return true if the number matches.
202: */
203: boolean matches(String card);
204:
205: }
206:
207: /**
208: * Change to support Visa Carte Blue used in France
209: * has been removed - see Bug 35926
210: */
211: private class Visa implements CreditCardType {
212: private static final String PREFIX = "4";
213:
214: public boolean matches(String card) {
215: return (card.substring(0, 1).equals(PREFIX) && (card
216: .length() == 13 || card.length() == 16));
217: }
218: }
219:
220: private class Amex implements CreditCardType {
221: private static final String PREFIX = "34,37,";
222:
223: public boolean matches(String card) {
224: String prefix2 = card.substring(0, 2) + ",";
225: return ((PREFIX.indexOf(prefix2) != -1) && (card.length() == 15));
226: }
227: }
228:
229: private class Discover implements CreditCardType {
230: private static final String PREFIX = "6011";
231:
232: public boolean matches(String card) {
233: return (card.substring(0, 4).equals(PREFIX) && (card
234: .length() == 16));
235: }
236: }
237:
238: private class Mastercard implements CreditCardType {
239: private static final String PREFIX = "51,52,53,54,55,";
240:
241: public boolean matches(String card) {
242: String prefix2 = card.substring(0, 2) + ",";
243: return ((PREFIX.indexOf(prefix2) != -1) && (card.length() == 16));
244: }
245: }
246:
247: }
|