Tuesday, December 29, 2009

Credit card validator in JSF

If you have a JSF page with credit card information you might want to validate the credit card number the user is entering. Different credit card companies (VISA,Master & Amex) have credit card numbers that follow different validation rules. All major credit cards will satisfy a Cyclic Redundancy Check (CRC) called Luhn Algorithm.

Luhn Algorithm

Step1: Sum all odd digits of the credit card number
Step2: Take each even digit multiply it by 2 if the result is greater than 9 subtract 9 from it then add it to the Sum of Step1
Step3: If sum is divisible by 10 then CRC passed else failed

To implement this lets do the following
1.In your JSP page which has the credit card form add the validator to the the card number input text field


<h:inputText label="Credit Card Number:"
id="inpCCnumber" required="true"
value="#{backing_ccbean.cardNumber}">
<f:validator validatorId="CreditCardValidator"/>
</h:inputText>




As you can see the custom credit card validator is called CreditCardValidator. The value is stored in the backing_ccbean. Assume there is a drop down list from which the user selects the card type(Visa,Master or Amex) and this value is stored in the same backing bean backing_ccbean in the attribute cardType, however this value will be set to the model object only after the validation phase so we need to directly bind to the component



<h:selectOneListBox label="Credit card type" value="#{backing_ccbean.cardType}"
binding="#{backing_requestscope.cardTypeChoice}">
<f:selectItem itemValue="1" itemLabel="Visa" />
<f:selectItem itemValue="2" itemLabel="Master Card" />
<f:selectItem itemValue="3" itemLabel="American Express" />
</h:selectOneListBox>




backing_requestscope.cardTypeChoice maps to a javax.faces.component.html.HtmlSelectOneListBox this way we get the users selection in the validation phase of the JSF life cycle.

2. The next step would be to define CreditCardValidator in faces-config.xml. Add the following XML element to your faces-config


<validator>
<validator-id>CreditCardValidator</validator-id>
<validator-class>validators.MyCreditCardValidator</validator-class>
</validator>


The XML element is simple there is an ID and a Class we have called it MyCreditCardValidator.

3. Now let us write the java class MyCreditCardValidator in the package validators





package validators;

public class MyCreditCardValidator implements javax.faces.validator.Validator{


public void validate(FacesContext ctx,UIComponent component,Object value) throws ValidatorException{
String ccnum=(String)value;
// get the card type form the binding object instead of the model object
HtmlSelectOneListBox cardChoiceComp= (HtmlSelectOneListBox) context.getApplication().createValueBinding("#{backing_requestscope.cardTypeChoice}").getValue(context);
int cardType = ((Integer)cardChoiceComp.getValue()).intValue();

boolean validCard = validateCard(ccnum,cardType);

if (validCard==false) throw new ValidatorException(new FacesMessage(" Invalid credit card format!!!!"));

}

private boolean validateCard(String ccnum,int cardType){
// visa cards always start with 4 and are either 13 or 16 digits in length
final int VISA=1;
// master cards are 16 digits in length and the first 2 digits range from 51 to 55
final int MASTER=2;
// Amex cards are 15 digits in length and the first 2 digits range from 34 to 37
final int AMEX=3;
switch(type) {
case VISA:
if ((ccnum.length() != 13 && ccnum.length() != 16) ||
Integer.parseInt(ccnum.substring(0,1))!=4)
return false;
break;
case MASTER:
if (ccnum.length() != 16 ||
Integer.parseInt(ccnum.substring(0,2)) <> 55)
return false;
break;
case AMEX:
if (ccnum.length() != 15 ||
(Integer.parseInt(ccnum.substring(0,2)) != 34 &&
Integer.parseInt(ccnum.substring(0,2))) != 37))
return false;
break;
}

// luhn validate
char[] charArr = ccnum.toCharArray();
int[] num = new int[charArr.length];
int total =0;
for(int i=0;i -1; i--){
if (alternate){ // multiply each even digit by 2 then add it to total
num[i] *=2;
if (num[i] > 9) num[i] -=9;
}
total +=num[i];
alternate = !alternate;

}
if (total % 10 != 0) return false;

return true;

}


}





In the MyCreditCardValidator class the most important thing to notice is how the value of card type is got since the validate method is called in the validation phase of a JSF life cycle the model objects are not set with the value hence the only way to get the value of a component other than the component that is being validated is thru the component binding.

No comments: