Removed untyped contructor from ComponentRegistration and add a protected setter.
[castle.git] / Components / Validator / Castle.Components.Validator / Validators / CreditCardValidator.cs
blobd697da713d41780dfc5ba78f62e5b4ae71b47030
1 // Copyright 2004-2008 Castle Project - http://www.castleproject.org/
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
15 namespace Castle.Components.Validator
17 using System;
18 using System.Collections;
19 using System.Text;
20 using System.Text.RegularExpressions;
22 /// <summary>
23 /// This validator validate that the is a valid credit card number in:
24 /// <list type="unordered">
25 /// <item> Amex </item>
26 /// <item> DinersClub </item>
27 /// <item> Discover </item>
28 /// <item> Discover </item>
29 /// <item> enRoute </item>
30 /// <item> JCB </item>
31 /// <item> MasterCard </item>
32 /// <item> VISA</item>
33 /// </list>
34 /// It is possible to specify more than a single card type.
35 /// You can also specify exceptions for test cards.
36 /// </summary>
37 [Serializable]
38 public class CreditCardValidator : AbstractValidator
40 private CardType allowedTypes = CardType.All;
41 private string[] exceptions = new string[] {};
43 /// <summary>
44 /// Initializes a new credit card validator.
45 /// </summary>
46 public CreditCardValidator()
50 /// <summary>
51 /// Initializes a new credit card validator.
52 /// </summary>
53 /// <param name="allowedTypes">The card types to accept.</param>
54 public CreditCardValidator(CardType allowedTypes)
56 this.allowedTypes = allowedTypes;
59 /// <summary>
60 /// Initializes a new credit card validator.
61 /// </summary>
62 /// <param name="exceptions">An array of card numbers to skip checking for (eg. gateway test numbers). Only digits should be provided for the exceptions.</param>
63 public CreditCardValidator(string[] exceptions)
65 this.exceptions = exceptions;
68 /// <summary>
69 /// Initializes a new credit card validator.
70 /// </summary>
71 /// <param name="allowedTypes">The card types to accept.</param>
72 /// <param name="exceptions">An array of card numbers to skip checking for (eg. gateway test numbers). Only digits should be provided for the exceptions.</param>
73 public CreditCardValidator(CardType allowedTypes, string[] exceptions)
75 this.allowedTypes = allowedTypes;
76 this.exceptions = exceptions;
79 /// <summary>
80 /// Gets a value indicating whether this validator supports browser validation.
81 /// </summary>
82 /// <value>
83 /// <see langword="true"/> if browser validation is supported; otherwise, <see langword="false"/>.
84 /// </value>
85 public override bool SupportsBrowserValidation
87 get { return false; }
90 /// <summary>
91 /// Applies the browser validation by setting up one or
92 /// more input rules on <see cref="IBrowserValidationGenerator"/>.
93 /// </summary>
94 /// <param name="config">The config.</param>
95 /// <param name="inputType">Type of the input.</param>
96 /// <param name="generator">The generator.</param>
97 /// <param name="attributes">The attributes.</param>
98 /// <param name="target">The target.</param>
99 public override void ApplyBrowserValidation(BrowserValidationConfiguration config, InputElementType inputType,
100 IBrowserValidationGenerator generator, IDictionary attributes,
101 string target)
105 /// <summary>
106 /// Gets the allowed credit card types.
107 /// </summary>
108 /// <value>The <see cref="CardType"/> representing the allowed types.</value>
109 public CardType AllowedTypes
111 get { return allowedTypes; }
114 /// <summary>
115 /// An array of card numbers to skip checking for (eg. gateway test numbers).
116 /// </summary>
117 /// <value>A <see cref="Array"/>representing the card numbers to skip checking.</value>
118 public string[] Exceptions
120 get { return exceptions; }
123 /// <summary>
124 /// Validate that the propety value matches a valid (formatted) credit card
125 /// Note: null values are consider OK always.
126 /// </summary>
127 /// <param name="instance"></param>
128 /// <param name="fieldValue"></param>
129 /// <returns><c>true</c> if the field is OK</returns>
130 public override bool IsValid(object instance, object fieldValue)
132 //If the input is null then there's nothing to validate here
133 if (fieldValue == null)
135 return true;
138 //Get the raw string
139 string cardNumberRaw = fieldValue.ToString();
141 //Strip any spaces or dashes
142 string cardNumber = string.Empty;
143 foreach(char digit in cardNumberRaw.ToCharArray())
145 if (char.IsNumber(digit))
147 //Keep the number
148 cardNumber += digit.ToString();
150 else if (digit == ' ' || digit == '-')
152 //Skip the space or dash
154 else
156 //If it's not one of the above then it shouldn't be here
157 return false;
161 //Check if it's in the exceptions
162 foreach(string exception in exceptions)
164 if (cardNumber == exception)
166 return true;
170 //Check to see if it's in the allowed types list, has the correct initial digits and has the right number of digits
171 if (!IsValidCardType(cardNumber))
173 return false;
176 //Check the LUHN output
177 if (!IsLuhnValid(cardNumber))
179 return false;
182 return true;
185 private bool IsLuhnValid(string cardNumber)
187 int length = cardNumber.Length;
189 int sum = 0;
190 int offset = length % 2;
191 byte[] digits = new ASCIIEncoding().GetBytes(cardNumber);
193 for(int i = 0; i < length; i++)
195 digits[i] -= 48;
196 if (((i + offset) % 2) == 0)
198 digits[i] *= 2;
201 sum += (digits[i] > 9) ? digits[i] - 9 : digits[i];
204 return (sum % 10 == 0);
207 private bool IsValidCardType(string cardNumber)
209 if ((allowedTypes & CardType.MasterCard) != 0)
211 if (Regex.IsMatch(cardNumber, "^(51|52|53|54|55)"))
213 return (cardNumber.Length == 16);
217 if ((allowedTypes & CardType.VISA) != 0)
219 if (Regex.IsMatch(cardNumber, "^(4)"))
221 return (cardNumber.Length == 13 || cardNumber.Length == 16);
225 if ((allowedTypes & CardType.Amex) != 0)
227 if (Regex.IsMatch(cardNumber, "^(34|37)"))
229 return (cardNumber.Length == 15);
233 if ((allowedTypes & CardType.DinersClub) != 0)
235 if (Regex.IsMatch(cardNumber, "^(300|301|302|303|304|305|36|38)"))
237 return (cardNumber.Length == 14);
241 if ((allowedTypes & CardType.enRoute) != 0)
243 if (Regex.IsMatch(cardNumber, "^(2014|2149)"))
245 return (cardNumber.Length == 15);
249 if ((allowedTypes & CardType.Discover) != 0)
251 if (Regex.IsMatch(cardNumber, "^(6011)"))
253 return (cardNumber.Length == 16);
257 if ((allowedTypes & CardType.JCB) != 0)
259 if (Regex.IsMatch(cardNumber, "^(3)"))
261 return (cardNumber.Length == 16);
265 if ((allowedTypes & CardType.JCB) != 0)
267 if (Regex.IsMatch(cardNumber, "^(2131|1800)"))
269 return (cardNumber.Length == 15);
273 if ((allowedTypes & CardType.Unknown) != 0)
275 return true;
278 return false;
281 /// <summary>
282 /// Returns the key used to internationalize error messages
283 /// </summary>
284 /// <value></value>
285 protected override string MessageKey
287 get { return MessageConstants.InvalidCreditCardMessage; }
290 /// <summary>
291 /// Define the known card types
292 /// </summary>
293 [Flags, Serializable]
294 public enum CardType
296 /// <summary>
297 /// MasterCard Card
298 /// </summary>
299 MasterCard = 0x0001,
300 /// <summary>
301 /// VISA Card
302 /// </summary>
303 VISA = 0x0002,
304 /// <summary>
305 /// American Express Card
306 /// </summary>
307 Amex = 0x0004,
308 /// <summary>
309 /// Diners Club Card
310 /// </summary>
311 DinersClub = 0x0008,
312 /// <summary>
313 /// enRoute Card
314 /// </summary>
315 enRoute = 0x0010,
316 /// <summary>
317 /// Discover Card
318 /// </summary>
319 Discover = 0x0020,
320 /// <summary>
321 /// JCB Card
322 /// </summary>
323 JCB = 0x0040,
324 /// <summary>
325 /// Unkown card
326 /// </summary>
327 Unknown = 0x0080,
328 /// <summary>
329 /// All (known) cards
330 /// </summary>
331 All = Amex | DinersClub | Discover | Discover | enRoute | JCB | MasterCard | VISA