Some fancy websites show an error dialog when it is detected that an untrained shopper has entered a credit/debit card number as it is printed on their card with spaces. Is it possible in some way to write a Java web app that handles these numbers with spaces as if they were correct?
Sure. Compress out the spaces. There are probably a zillion ways to do it; I'd be tempted to use String.split() to break it up on the spaces, then concatente the four strings resulting. Or use StringBuffer.indexOf() and .delteCharAt().
...or as Cletus said, use replaceAll().
There are a number of options, but the most logical seems to be to just do a simple string replace to replace all the spaces with a closed character i.e. ''. This will reduce the credit card string to just one long string of numbers..then just process away
My view is that any Web app that rejects a credit card number with spaces isn't doing its job. When you receive a credit card number, it's easy enough to do:
String ccNumber = ccNumber.replaceAll("[\\s-]+", "");
to remove spaces and dashes (some use those too). Then validate the result. You'll simply annoy your users if you force them to remove spaces you could just as easily do.
As for how to validate, well that depends on a lot of things, such as which Web framework you're using and what validation options you've chosen. Struts 1 for example might or might not use Apache Commons Validator whereas Spring MVC will (probably) use Spring validation and so on. So I can't tell you exactly how to validate but I can tell you what to validate.
The first thing is that a CC number with spaces should not be rejected. Most people will find:
4123 0987 8876 2939
much easier to read than:
4123098788762939
which is really important if the user misses or mistypes a digit and needs to find why his or her credit card number failed validation. The replaceAll() at the top of this post covers this situation.
The second thing is that you display the credit card number (even when some of the digits are replaced with X for security reasons) in the correct way. I suggest you read through Anatomy of Credit Card Numbers.
That page gives you the rules for the number of digits and the valid prefixes. A robust Web application will implement these so you can tell if a credit card number is invalid before you try and use it. It can take up to 30 seconds (or possibly more) to submit credit card details to a payment gateway so you shouldn't do it until you are sure as you can be that the payment will be accepted. To do otherwise is to provide a really bad user experience. There is every chance the user will give up if it fails 1-2 times rather than wait.
As for displaying them, that depends on the # of digits:
- 16: 4 groups of 4 separated by a space;
- 15: like an American Express card ie 4-6-5 with a space between each group;
- 14: like a Diners Club card ie 4-6-4 with a space between each group;
- 13: Never seen 13 but 4-5-4 or 4-4-5 or 5-4-4 (or possibly 3-3-3-4) springs to mind.
The credit card number should be verified according to the checksum algorithm mentioned in the page before submitting for processing as part of a standard validation routine. That page has a Java implementation of that routine.
Every website that accepts credit card payment should be doing all of the above as an absolute minimum or you're simply throwing away business as a percentage of your users get frustrated.
So the short version is two simple rules:
- Be as forgiving as possible with user input; and
- Do absolutely everything possible to validate credit card details prior to submission.
Your question seems strange but I would think that it should be as easy as running the credit card numbers entered by users through a validation function which would first of all remove all white spaces.
This is rather trivial in any modern language with or without using regex.
Unfortunately not, which why instead those fancy web sites need to show an error dialog to the untrained shopper: to force the shopper to re-enter their number, in the format that the machine prefers.
Why, if only a machine could do "data processing", so that the machine itself could change the data format! Or, if only if there were no such thing as an "untrained" shopper! Alas!
Websites that force you to enter credit card numbers (and similar things) in a specific format - seriously annoy me.
Those people are inconveniencing their customers simply because they (the developers) are lazy. There is no reason not to except things like credit card numbers, phone numbers, etc in whatever format they are provided. The only limitation is what is REQUIRED to understand how to interpret the value.
You shouldn't care whether I enter 5555-4444-3333-2222 or 5555444433332222, just strip the dashes out if you don't like them - same with spaces. And with phone numbers, unless you are going to be auto-dialing the number, you probably don't even care what format its in so don't annoy your users unless you have to.
I would go as far as stripping out all non-numeric characters then checking that the length is valid before running the user input through real validation like Luhn's algorithm.
String ccNumber = input.replaceAll("\\D", "");
strips out all the non-digits from String input
.
Unfortunately, no. Java just cannot handle these requirements, since there is so much overhead involved in emulating the Java Virtual Machine on x86 chips, leaving no room for useful constructs like Perl's regular expressions, which can do it thusly:
$input =~ s/\D//g;
Java made an attempt at adding regular expressions a few years back, but they only ran on PowerPC chips, which are no longer used. The problem was that all regular expressions had to be contained as Strings instead of being a first class language construct, and thus doubling backslashes was required, but as everyone knows backslashes mean something different on the primary operating system for the x86 architecture.
My advice is to upgrade to Perl. Scheme is also known to be able to handle this situation as well as give a tremendous advantage over your competition, but Scheme runs only on LISP machines.
Easy.
- Your input space is a list of characters from some character set containing all characters.
- Your output space is a list of characters from some character set containing only numbers.
To solve this problem, we create an intermediate space containing only the numbers 0 to 9. We can create a new enum for this finite set. We'll call this our finger space, since it oddly contains the same number of members as we do fingers.
We then write two functions.
- Convert input space to finger space
- Convert finger space to output space
As we reduce the input space to the finger space, we just drop any character not found in the finger space. Converting from finger space to output space is even easier. We just find the same number in the output space.
The trick is for this to work with all character sets. I haven't figured out how to determine if a certain character matches a member in my finger set. Maybe I should post it as a question.
Advertisement on this website... For just $49.95 you can have a new special keyboard compatible with this online store. Click here to add the new keyboard in your cart and checkout. When checking out please enter your credit card number in the designated field. Please do not enter the spaces between the numbers as our store doesn't know how to deal with spaces between the numbers.
Tom,
The problem is solved technically, let's talk about it theoretically.
There are two schools of thought here. I do not think it is an acceptable answer to say "if the user can't figure it out it's their problem."
- Be firm about your user input and only accept credit card numbers that are well-formed. This requires keeping the user on the page until they get everything right.
- Be more lenient by assuming their intentions and adjusting their input for them (just be sure to give them a confirmation screen to verify the new input).
In my opinion, #2 is the way to go, you can use regular expressions (as stated above) to pull all spaces, special characters, etc. out of the cc# field and keep the user from having to enter their information again.
Either way you should inform the user of the proper input form (i.e. xxxx-xx-xxxx)
as a rule of thumb, I tend to appreciate sites that are more elegant about the way they handle user input.
For more tips on regular expressions check out regular-expressions . info
Good luck,
-Robert
I'm going to assume this is a real question even though it looks like some sort of troll or joke.
You should model your interface so that the user instinctively performs the input in a controlled manner. Simply put, ask them for the kind of card first, and then on the input form format the input to match the card. For example, assuming a 16 digit card like Visa or Mastercard, display 4 input boxes separated by spaces or dashes that limit input to 4 characters each and automatically move to the next box in the series after the user types the fourth digit.
It should look something like the following on the page:
Card Number:
[1234] - [1234] - [1234] - [1234]
or
Card Number:
[1234] - [123456] - [12345]
you could use javascript validation by using the onkeypress
event to check if the last character is valid and if not just remove it and maybe even flash up a message saying that an invalid character was entered. This way invalid numbers are never entered. It could also automatically enter a spacer character (space or -) in the format you want.
I wrote this pair of Perl functions in a shop where only Visa was allowed (...at Visa, actually...) back in 1998.
sub mod10_checkdigit
{
my($acct) = @_;
die "invalid account number in BRPS::mod10_checkdigit"
unless $acct =~ m%^\d+$%;
my(@digits) = split //, $acct;
my($len) = scalar(@digits);
print "# ($len) @digits\n" if ($ENV{PERL_BRPS_DEBUG});
my($i, $sum, $chk);
my($mul) = (($len % 2) == 1) ? 1 : 2;
$len--;
for ($i = 0; $i < $len; $i++)
{
my($val) = $mul * $digits[$i];
# Note that we need the digital root of the value, but $val is not
# greater than 18 (because $digits[$i] <= 9 and $mul <= 2).
$val -= 9 if ($val >= 10);
$sum += $val;
print "# $i: $digits[$i] * $mul => $val => $sum\n" if ($ENV{PERL_BRPS_DEBUG});
$mul = 3 - $mul;
}
$chk = 10 - ($sum % 10);
$chk = 0 if ($chk == 10);
return $chk;
}
sub validate_account
{
my($acct) = @_;
# Strip leading and trailing blanks
$acct =~ s/^\s*(\S.*\S)\s*$/$1/;
my($clean) = $acct;
# Check that account number is string of digits, blanks and dashes
return undef, "account number is not a sequence of digits, blanks and dashes"
unless $acct =~ m/^[- \d]+$/;
return undef, "account number is not a Visa account number"
unless $acct =~ m/^4/;
# Remove non-digits
$clean =~ s/\D//g;
return undef, "account number is neither 13 nor 16 digits"
unless length($clean) == 16 || length($clean) == 13;
# Punctuators must be reasonably consistent!
return undef, "invalid punctuation pattern"
unless ($acct =~ m/^\d{16}$/o or $acct =~ m/^\d{13}$/o or
$acct =~ m/^\d{4}[- ]\d{4}[- ]\d{4}[- ]\d{4}$/o or
$acct =~ m/^\d{4}[- ]\d{3}[- ]\d{3}[- ]\d{3}$/o);
# Determine check digit
my($chk) = mod10_checkdigit($clean);
return undef, "check digit on account number is incorrect"
unless $clean =~ m/$chk$/;
return $clean, "ok";
}
The allow plausible credit card numbers through. It wouldn't be hard to generalize to handle Mastercard, Discover, American Express too.
Rant
I do not like web sites that insist on me entering data in their internal format. Dammit - store the number as a large integer and send it as a pure digit string; that's fine for computers. But do let me enter recognizable human legible formats - even re-present the data in the human-legible format. There is far, far, far too much laziness in web sites that handle credit card numbers.
If you mean javascript, you could go with the "advance to next input" method:
Here's the HTML:
<form id="ccform" action="cc_submit.php" method="post">
<fieldset id="ccnumber">
<input id="firstset" type="text" maxlength="4" />
<input id="secondset" type="text" maxlength="4" />
<input id="thirdset" type="text" maxlength="4" />
<input id="fourthset" type="text" maxlength="4" />
</fieldset>
</form>
And here's the JS:
var ccfields;
function moveToNext(e) {
var field = e.currentTarget;
var chars = field.value.length;
var setnumb = Number(field.id.substr(3,1)) - 1;
if(chars >= 4 && setnumb < 3) {
ccfields[setnumb + 1].focus();
}
}
window.onload = function() {
ccfields = document.getElementById("ccnumber").getElementsByTagName("input");
for (var i = 0; i < ccfields.length; i++) {
ccfields[i].onkeyup = moveToNext;
}
};
Of course, you will want to add a function that checks for non-numbers and a function for taking the four fields and merging them into one string to pass back to the form. It also isn't a bad idea to use a js library like Jquery to ensure that events are handled the same way and simplify traversing through the inputs so you can use attributes like "name" without any confusion.
But generally, if people see 4 fields, it makes it easier to type in their number, and for those visitors who think "ah nuts, I have to use my mouse for each number" they are (or at least I am) pleased that the page is smart enough to know to move to the next field.
only one person seems to have mentioned the Luhn or mod 10 algorithm
Solution 1: How about just put in 4 text boxes that can take 4 digit numbers. Like how the license keys for software's are entered. You can trigger the boxes to change to the next in line upon entering a space character or the tab character.
Solution 2: Use regular expressions as mentioned in one of the comments described above. Problem being, you will be susceptible to injection attacks if its a web application.
JavaTM is great for server-side programming, while javascript is may be useful on the client side; for example during validation.
A valid credit card number varies in length between 12 (e.g. Maestro) and 19 (e.g. Solo, Switch). The client-side javascript could find out whether the card number is valid (only digits (with dashes or whitespace), suits the issuer authority, checksum's ok, ...) and perform a 1:1 mapping from a 'human-readable' (e.g.
American Express 3400 0100 2000 009
) to a internal representation, e.g.
<input ... id="ccid" value="340001002000009">
<input ... id="ccissuer" value="AMEX">
Once the credit card information validation has passed the validation during input, the values can be transparently converted into the internal form on-submit.
An example of a plain javascript function that does some checks on that matter may be found on the net, e.g. here http://www.braemoor.co.uk/software/creditcard.shtml.
So much for a description of an answer.