1 // Copyright 2004-2008 Castle Project - http://www.castleproject.org/
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
7 // http://www.apache.org/licenses/LICENSE-2.0
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
18 using System
.Collections
;
20 using System
.Text
.RegularExpressions
;
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>
34 /// It is possible to specify more than a single card type.
35 /// You can also specify exceptions for test cards.
38 public class CreditCardValidator
: AbstractValidator
40 private CardType allowedTypes
= CardType
.All
;
41 private string[] exceptions
= new string[] {};
44 /// Initializes a new credit card validator.
46 public CreditCardValidator()
51 /// Initializes a new credit card validator.
53 /// <param name="allowedTypes">The card types to accept.</param>
54 public CreditCardValidator(CardType allowedTypes
)
56 this.allowedTypes
= allowedTypes
;
60 /// Initializes a new credit card validator.
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
;
69 /// Initializes a new credit card validator.
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
;
80 /// Gets a value indicating whether this validator supports browser validation.
83 /// <see langword="true"/> if browser validation is supported; otherwise, <see langword="false"/>.
85 public override bool SupportsBrowserValidation
91 /// Applies the browser validation by setting up one or
92 /// more input rules on <see cref="IBrowserValidationGenerator"/>.
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
,
106 /// Gets the allowed credit card types.
108 /// <value>The <see cref="CardType"/> representing the allowed types.</value>
109 public CardType AllowedTypes
111 get { return allowedTypes; }
115 /// An array of card numbers to skip checking for (eg. gateway test numbers).
117 /// <value>A <see cref="Array"/>representing the card numbers to skip checking.</value>
118 public string[] Exceptions
120 get { return exceptions; }
124 /// Validate that the propety value matches a valid (formatted) credit card
125 /// Note: null values are consider OK always.
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)
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
))
148 cardNumber
+= digit
.ToString();
150 else if (digit
== ' ' || digit
== '-')
152 //Skip the space or dash
156 //If it's not one of the above then it shouldn't be here
161 //Check if it's in the exceptions
162 foreach(string exception
in exceptions
)
164 if (cardNumber
== exception
)
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
))
176 //Check the LUHN output
177 if (!IsLuhnValid(cardNumber
))
185 private bool IsLuhnValid(string cardNumber
)
187 int length
= cardNumber
.Length
;
190 int offset
= length
% 2;
191 byte[] digits
= new ASCIIEncoding().GetBytes(cardNumber
);
193 for(int i
= 0; i
< length
; i
++)
196 if (((i
+ offset
) % 2) == 0)
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)
282 /// Returns the key used to internationalize error messages
285 protected override string MessageKey
287 get { return MessageConstants.InvalidCreditCardMessage; }
291 /// Define the known card types
293 [Flags
, Serializable
]
305 /// American Express Card
329 /// All (known) cards
331 All
= Amex
| DinersClub
| Discover
| Discover
| enRoute
| JCB
| MasterCard
| VISA