Credit Card format check

Greg K's picture

He has: 2,145 posts

Joined: Nov 2003

Hi, I had needed this for a program I am working on, and thought I would post the code here in case anyone else may need something like it.

This is a test page (PHP) that lets you enter a credit card info for a purchase (when testing Do Not Use Actual Card Numbers for security reasons unless you have a secure connection or doing on a standalone server/computer). It does basic "Pre checks" of card info that should be done before submitting the card info to a card processing center.

One thing that bothers me about most places to enter a credit card, they want no dashes or spaces, however they just make it so much easier to verify what you typed in correctly. This script lets you type whatever you want and strips out everything except the numbers.

Note, In looking at other sites that take credit cards, they show pictures of the security code being 3 digits for Visa/MC/Disc and 4 for AMEX. I'm not sure if this is the "standard", so I didn't include this as part of the check.

The page is very straight forward for testing, displays results at the top and has form at bottom. The code has some notes, and IMO, if you can't figure out what this is doing, well you shouldn't be playing around with coding credit card processing.

If you have any comments or suggestions for how to improve/opitmize this checking, I'm all ears.

Here is the code, enjoy:

PS I decided to go ahead and add code so that the script will automatically generate the "YEARS" in the drop down, and now will also make sure the card is not already expired.

<?php
<html>
<
head><title>CC Number PreCheck</title></head>

<
body>

    <
p>You asked me to process:
      <
br>Card: <b>= $_POST['cc'] </b>
      <
br>Exp: <b>= $_POST['month'] /= $_POST['year']</b>
      <
br>Sec: <b>= $_POST['sec'] </b>
    </
p>
 

 
// Strip the Credit Card data so it is Numbers Only

 
$ccLen = strlen($_POST['cc']);
 
$ccStr = \"\";
  for (
$t=0;$t<$ccLen;$t++)
  {
   
$thisOne = substr($_POST['cc'],$t,1);
    if (
$thisOne >= \"0\" && $thisOne <= \"9\") $ccStr .= $thisOne;
  }

  // MERGE MONTH/YEAR into 4 digits

 
$expStr = $_POST['month'] . $_POST['year'];


  // Strip the Security Code so that it is Numbers Only

 
$secLen = strlen($_POST['sec']);
 
$secStr = \"\";
  for (
$t=0;$t<$secLen;$t++)
  {
   
$thisOne = substr($_POST['sec'],$t,1);
    if (
$thisOne >= \"0\" && $thisOne <= \"9\") $secStr .= $thisOne;
  }

 
$errors = array();
 
$cardType = \"\";

  if (strlen(
$ccStr)<13)
  {
   
$errors[] = \"Credit Card Lenght was below minimum for any card\";
  }
  else
  {
   
$firstDigit = substr($ccStr,0,1);
    switch (
$firstDigit)
    {
      case '5':
       
$cardType = \"MasterCard\";
        if (strLen(
$ccStr) != 16)
         
$errors[] = \"MasterCard must be 16 numbers\";
        else
         
$cardFormat = substr($ccStr,0,4) . \" \" .
                        substr(
$ccStr,4,4) . \" \" .
                        substr(
$ccStr,8,4) . \" \" .
                        substr(
$ccStr,12,4);
        break;
      case '4':
       
$cardType = \"Visa\";
        if (strLen(
$ccStr) != 16 && strLen($ccStr) != 13)
         
$errors[] = \"Visa must be either 13 or 16 numbers\";
        else
          if (strLen(
$ccStr) == 16)
           
$cardFormat = substr($ccStr,0,4) . \" \" .
                          substr(
$ccStr,4,4) . \" \" .
                          substr(
$ccStr,8,4) . \" \" .
                          substr(
$ccStr,12,4);
          else
           
$cardFormat = substr($ccStr,0,4) . \" \" .
                          substr(
$ccStr,4,3) . \" \" .
                          substr(
$ccStr,7,3) . \" \" .
                          substr(
$ccStr,10,3);
        break;
      case '3':
       
$cardType = \"AMEX or CB\";
        if (strLen(
$ccStr) != 15)
         
$errors[] = \"AMEX must be 15 numbers\";
        else
         
$cardFormat = substr($ccStr,0,4) . \" \" .
                        substr(
$ccStr,4,6) . \" \" .
                        substr(
$ccStr,10,5);
        break;
      case '6':
       
$cardType = \"Discover\";
        if (strLen(
$ccStr) != 16)
         
$errors[] = \"Visa must be either 13 or 16 numbers\";
        else
         
$cardFormat = substr($ccStr,0,4) . \" \" .
                        substr(
$ccStr,4,4) . \" \" .
                        substr(
$ccStr,8,4) . \" \" .
                        substr(
$ccStr,12,4);
        break;
      default:
       
$cardType = \"UNKNOWN\";
       
$errors[] = \"This is an unknown card number\";
    }

    if (count(
$errors) == 0)
    {
      // Now that we have what looks like a credit card number
      // Do a checksum verification on it

      // Reverse the order of the numbers
     
$rev = strrev($ccStr);

      // Step through the numbers, doubling all the even numbers
      // These get added into a string NOT to a total value
     
$cDigits = \"\";
      for (
$t=0; $t<strlen($rev); $t++)
      {
       
$c = substr($rev,$t,1);
        if (
$t%2 == 1) $c *= 2;
       
$cDigits .= $c;
      }

      // Now we add up a total of all individual numbers
      // a 12 from doubling above gets added up as +1+2+ not +12+
     
$nDigits = 0;
      for (
$t=0; $t<strlen($cDigits); $t++)
       
$nDigits += substr($cDigits,$t,1);

      // If all is well the total should be a multiple of 10
      if (
$nDigits != 0 && $nDigits % 10 == 0)
       
$modCheck = $nDigits % 10;
      else
      {
       
$modCheck = $nDigits % 10;
       
$errors[] = \"Card Checksum Failed\";
      }
    }
  }

 
    <p>This is what I got out of it:
      <br>Card: <b>=
$ccStr </b>
      <br>Length: <b>= strlen(
$ccStr) </b>
      <br>Exp Date: <b>=
$expStr </b>
      <br>SecCode: <b>=
$secStr </b>
      <br>Length: <b>= strlen(
$secStr) </b></p>
    <p>And so it appears that this card has
      if (count(
$errors) == 0)
        echo \"a <b>valid</b> format and is of type: <b>\" .
$cardType . \"</b>\";
      else
      {
        echo \"an <b>invalid \" .
$cardType . \"</b> card format because:<br>\";
        echo implode(\"<br>\",
$errors);
      } </p>
    <p>Here are variables from the checking:
      <br>Reverse of card numbers: <b>=
$rev </b>
      <br>Digits (evens doubled): <b>=
$cDigits </b>
      <br>Sum of those digits: <b>=
$nDigits </b>
      <br>Mod Check: <b>=
$modCheck </b></p>
    <p>The card formated for it's type is: <b>=
$cardFormat </b></p>
   

      // Don't forget to make sure it's not already expired!

     
$thisYear = date(\"y\");
     
$thisMonth = date(\"m\");

      if (
$_POST['year'] <= $thisYear && $_POST['month'] < $thisMonth)
        echo \"<p>Oh No! <b>The Card is expired!!!</b></p>\n\";

   
<hr>
    <form action=\"=
$HTTP_SERVER_VARS['PHP_SELF']; \" method=\"post\">
      Card Number: <input type=\"text\" size=\"30\" name=\"cc\"><br>
      Exp Date: <select name=\"month\">
                  <option value=\"01\">1 (Jan)</option>
                  <option value=\"02\">2 (Feb)</option>
                  <option value=\"03\">3 (Mar)</option>
                  <option value=\"04\">4 (Apr)</option>
                  <option value=\"05\">5 (May)</option>
                  <option value=\"06\">6 (Jun)</option>
                  <option value=\"07\">7 (Jul)</option>
                  <option value=\"08\">8 (Aug)</option>
                  <option value=\"09\">9 (Sep)</option>
                  <option value=\"10\">10 (Oct)</option>
                  <option value=\"11\">11 (Nov)</option>
                  <option value=\"12\">12 (Dec)</option>
                </select>
                <select name=\"year\">
               
                  //
$thisYear was set aboove
                  // Adjust the number below to change how many years get displayed.

                  for (
$t=$thisYear;$t<$thisYear+8;$t++)
                  {
                   
$twoDigits = str_pad($t, 2, \"0\", STR_PAD_LEFT);
                    echo \"<option value=\\"
\" . $twoDigits . \"\\">20\" . $twoDigits . \"</option>\";
                  }
               
                </select><br>
      Scurity Code: <input type=\"text\" size=\"30\" name=\"sec\"><br>
      <input type=\"Submit\">
    </form>
</body>
</html>
?>

-Greg

Abhishek Reddy's picture

He has: 3,348 posts

Joined: Jul 2001

Surely using regular expressions would abbreviate a lot of this code. I might have a crack at it later.

It might be a good idea to turn it into a class, so the code is more easily deployed. Smiling

Busy's picture

He has: 6,151 posts

Joined: May 2001

try this site for php expressions
http://www.regexplib.com/DisplayPatterns.aspx?cattabindex=2&categoryId=3

hopefully you can find something usefull

Greg K's picture

He has: 2,145 posts

Joined: Nov 2003

Here is the only one on that link for Credit Cards:

Quote:
Expression: ^((4\d{3})|(5[1-5]\d{2}))(-?|\040?)(\d{4}(-?|\040?)){3}|^(3[4,7]\d{2})(-?|\040?)\d{6}(-?|\040?)\d{5}

Description: Credit card validator for AMEX, VISA, MasterCard only. Allows spaces, dashes, or no separator between digit groups according to the layout (4-6-5 for AMEX, 4-4-4-4 for Visa and Mastercard)

Matches: [3711-078176-01234], [4123 5123 6123 7123], [5123412361237123]

Non-Matches: [3711-4123-5123-6112]

This would be quite close, just need to add one for Discover cards (starts with a 6) and one for the older (don't know if really need them, but better safe than sorry) 13 digit (4-3-3-3) visas

Then would still need to use the other code for doing the checksum on the card number.

I'll try adapting this in later today.

-Greg

Busy's picture

He has: 6,151 posts

Joined: May 2001

Another way is to force the required input length.

Using form input boxs set them to required length, ie 4,3,3,3 or whatever they are.
If you want to get fancy you could use a javascript switch, so they choose a card type, and on that choice the fields are displayed (if you needed 6,4,4 or whatever) then you could just validate the fields seperatly, then join them and do whatever with them - store, send ...

visa
input(maxsize 4) - input(maxsize 4) - input(maxsize 4) - input(maxsize 4)

amex
input(maxsize 4) - input(maxsize 6) - input(maxsize 5)

Want to join the discussion? Create an account or log in if you already have one. Joining is fast, free and painless! We’ll even whisk you back here when you’ve finished.