The current basic sanity check for card numbers (which is separate from the optional card validation routine) is broken. It uses intval($cc_data['cc_number']) == 0, but this would pass, e.g., "3dsfklh". Payment gateways should not try to send non-numeric card numbers to merchant processing services, so there *should* be a sanity check like this, but it should check that every digit is a numeric with the ctype_digit() PHP function. (The is_numeric function is inappropriate.)

N.B. Given that the credit card number validation routine currently doesn't work properly for Maestro cards (the identification of cards by their first digit is ambiguous), this sanity check is particularly helpful.

Comments

TR’s picture

Stupid weakly-typed languages... Yes, that should be fixed.

But Ubercart wouldn't pass a non-numeric to the gateway, because it would fail the card number checksum calculation. The checksum is the important part (and is "on" by default), the other stuff is, as you say, a sanity check.

solarian’s picture

Unfortunately the checksum doesn't seem to work with the test number for 18-digit Maestro cards given by my payment processing company, and having looked at the internal logic of the checksum, it doesn't handle card type identification properly. So this is just a sanity check for people (like me) who need to turn off the checksum feature.

TR’s picture

There are several issues here, and it's not clear we're talking about the same thing.

When I say "checksum" I mean a checksum computed using all the digits of the card number, according to the Luhn algorithm. All the test card numbers on the page you reference above pass this test (I checked them just now).

This checksum is computed in the function _valid_card_number(). That function *also* does 2 other checks - the faulty is_numeric() check, and a check on the first digit of the card number. On examination, it seems that the first digit will fail for International Maestro if and only if you don't also accept American Express. That's a valid point, and should be fixed.

I'm in favor of *more* validation, not less, since catching bad card numbers up-front avoids service charges from payment gateways and helps prevent problems for those stores that are doing manual processing of credit card numbers.

solarian’s picture

is_numeric() is *firstly* used in a test condition at the point where _valid_card_number() is called. I'm talking about that piece of code.

The _valid_card_number() function is broken too, I agree -- assuming all cards that start with '3' are Amex is one, and so is the use of is_numeric(), but it doesn't end there. One need only do some simple checking using Google to see that the same is true for those other tests based on the first digit of the card number. Clearly this function is in need of urgent review, since a lot of people shopping with Ubercart will find their cards rejected for no good reason. This is bad for merchants using Ubercart, and the default setting of this function should at least be 'off' for now.

I was not aware that payment gateways might charge for failed card transactions. Which companies do this?

rszrama’s picture

Title: credit.module -- card number sanity check » credit.module -- Improve _valid_card_number()
Category: bug » feature

I'm going through and doing some housecleaning so we can put out a 1.1 release and wanted to follow up here.

I don't see any bugs associated with this issue but take from it that the sanity checking should be more specific for the optional validation and inclusive of other cards. I'm not too fond of the whole checkboxes approach to specifying valid card types anyways, as it's limited as has been pointed out here.

The code block in question in the original post was

// Validate the CC number if that's turned on.
if (variable_get('uc_credit_validate_numbers', TRUE) || intval($cc_data['cc_number']) == 0) {
  if (!_valid_card_number($cc_data['cc_number'])) {
    drupal_set_message(t('You have entered an invalid credit card number.'), 'error');
    $return = FALSE;
  }
}

EDIT: Ok, Lyle pointed out I was reading the original post wrong. When you're saying it would pass, you're saying it would == 3 and not that it would == 0. So, this would still fail a checksum but only if you have number validation turned on. I don't see any reason for it not to be turned on, so that's a good workaround.

I committed a change to ctype_digit() as recommended for the 1.1.

solarian’s picture

Unfortunately the committed fix does not solve this problem in the manner I think it should.

<?php
// Validate the CC number if that's turned on.
      if (variable_get('uc_credit_validate_numbers', TRUE) || !ctype_digit($cc_data['cc_number'])) {
        if (!_valid_card_number($cc_data['cc_number'])) {
          drupal_set_message(t('You have entered an invalid credit card number.'), 'error');
          $return = FALSE;
        }
      }
?>

This simply turns on full card validation if there are non-digits in the card number. OK, it will then reject it; but in doing so it violates the shop owner's configuration, and critically it will also reject other cards that do not pass the _valid_card_number() function. This function is specifically what I (and others) are trying to avoid (for reasons best addressed elsewhere). What I am proposing (and I thought I submitted a patch, but I can't see it now) is a separate sanity check, as follows:

<?php
// Validate the CC number if that's turned on.
      if ((variable_get('uc_credit_validate_numbers', TRUE) && !_valid_card_number($cc_data['cc_number']))
            || !ctype_digit($cc_data['cc_number'])) {
          drupal_set_message(t('You have entered an invalid credit card number.'), 'error');
          $return = FALSE;
      }
?>

This will return FALSE if the user enters a card number that includes non-numeric characters; or, if card validation is turned on, it will apply that function.

Hope this makes it clear! The thing about == 0 or == 3 is just a nuisance in PHP -- I don't understand why a string like "5iuhggs" would cast to integer "5"...

rszrama’s picture

Status: Needs review » Reviewed & tested by the community

I see what ya mean. Made your change. Is this issue closed?

fwiw, most card types check out w/ the Luhn algorithm (there's a Wikipedia article on it), and when I tested the Maestro test number from Protx it worked fine.

solarian’s picture

Thanks for the commit.

Re: Maestro, not sure what's going on there -- indeed, UK Maestro does seem to work fine (and it does use the Luhn algorithm).

solarian’s picture

But International Maestro *doesn't* validate -- I think I must have been referring to that.

cYu’s picture

BrianV came by #drupal-ubercart today wondering why Ubercart rejects card numbers with spaces in them. For example 4111 1111 1111 1111. He stated...
"has anyone else had an issue like this? I mean, the client gets several phone calls per day from various customers about this. I would think other people would have noticed this"

Do gateways generally accept this type of number and is Ubercart incorrectly filtering these out as invalid?

rszrama’s picture

afaik, any payment gateway I've worked with does not accept spaces. I imagine we could strip them out when we filter the input. I guess this was a non-issue before we updated the CC number field to be 19 characters long instead of the old 16.

cYu’s picture

Yeah, it would be nice if a string replace was added where the $_POST is processed into the $cc_data array in uc_payment_method_credit()

  'cc_number' => check_plain(str_replace(" ", "",  $_POST['cc_number'])),
postcarbonjason’s picture

Regarding the CC# being able to accept numbers with spaces, I just ran into this as well - due to a weird bug (http://www.ubercart.org/forum/bug_reports/8098/cc_number_format_validati...) where optional review checkout review was causing "invalid" card numbers with spaces in them to be submitted to Authorize.net in spite of being flagged as invalid by the checkout form, it appears that the CC#'s with spaces were properly accepted by Auth.net in my case...

I agree that it would be nice to collapse out the spaces in the CC# field, for usability sake.

rszrama’s picture

Assigned: Unassigned » rszrama
rszrama’s picture

Version: 5.x-1.0 » 6.x-2.0-beta1
Status: Reviewed & tested by the community » Fixed

Funny how fixing one issue opens up others... never had this problem before upping the size of that textfield to 19 characters for Maestro cards. :P

Anyways, tweaked it to remove spaces and am closing this issue out.

Status: Fixed » Closed (fixed)

Automatically closed -- issue fixed for two weeks with no activity.

vdsteph’s picture

I am a beginner at PHP codes. Can I get assistance on how to validate a cc number? Here is what I have started and I know I am missing a function() but not sure how to write the code:



if (!isset($_GET['ccnumber']))
echo "<p>Enter your credit card number.</p>";
else {
$Payment = $_GET['ccnumber'];
$ValidPayment = str_replace("-", "", $Payment);
$ValidPayment = str_replace(" ", "", $ValidPayment);
}
if(is_numeric($ValidPayment))
echo "<p>You did not enter a valid credit card number!</p>";
else {
echo "<p>Your credit card number is {$ValidPayment}.</p>";
}

enctype="application/x-www-form-urlencoded">

 if
(!empty($_GET['ccnumber'])) echo $_GET['ccnumber'] 

" />