Credit Card Validation and Verification

This chapter goes through credit card validation and verification, such as is vital during the checkout procedure of an online shopping application. The chapter as a whole covers all aspects of the checkout procedure in detail.

This sample is taken from Chapter 7: "Credit Card Validation and Verification" of the Glasshaus title "Usable Shopping Carts"

PHP/MySQL

The first item we need to take care of is terminating the user session so that when the customer next returns to the site, he'll be assigned a new session ID and start with a new, empty cart. That's the reason why we put the session ID value in a hidden field on the previous page  so we could continue to associate the customer with his cart and customer records while ending the session. We want to call the session-termination code just once, when the page ccform.php is first loaded, and we don't want to execute this block again, lest we begin a new session. To do this, we check to see if a POST variable named cc_submit has been set. If it hasn't than we know the user hasn't yet submitted the credit card information form shown earlier, which means we're on the initial page load and we should kill the session.

<?php

  if( !isset($HTTP_POST_VARS["$cc_submit"]) )

  {

First we call session_start() so that we can access the other functions and variables relating to the session we're about to end:

    session_start();

Following that, we call session_unset() to unset any session variables  strictly speaking, we haven't set any of those explicitly, but it's good practice to call the function anyway. Next, we call session_destroy(), which ends the session by deleting any session data stored on the server. We finish by deleting the session cookie if one was used to maintain the session:

    session_unset();

    session_destroy();

    if( isset($HTTP_COOKIE_VARS[session_name()]) )

     unset( $HTTP_COOKIE_VARS[session_name()] );

  }

  require_once "../includes/db.inc";

  require_once "../includes/location.inc";

?>

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">

<html>

<head>

  <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />

  <title>TuneIn - <?php echo $title; ?></title>

  <link rel="stylesheet" href="../styles/tunein.css" type="text/css">

</head> <body>

  <table width="100%" border="0" cellspacing="0" cellpadding="0">

    <tr> <!--Masthead table row-->

      <td bgcolor="#CCCCCC" width="150" class="tunein">

        <h1>Tune<em class="in">In</em>!</h1>

      </td>

      <td bgcolor="#CCCCCC" align="right">&nbsp;</td>

    </tr>

  </table>

  <table width="100%" border="0" cellspacing="0" cellpadding="0">

    <tr>

      <td valign="top">

This next part should look familiar as it's almost identical to the first portion of the code we used in Step 2 to include the error-checking routines for validation:

<?php

  $err_msg = "<p class=\"error\">There were problems

                                   with the following fields:</p>\n<p>";

  require_once "../includes/validate.inc";

  $valid = TRUE;

  if( isset($HTTP_POST_VARS["$cc_submit"]) )

  {

    $cc_submit = $HTTP_POST_VARS["$cc_submit"];

    $valid = validate();

  }

  if( $err_msg !=

  "<p class=\"error\">There were problems with the following fields:</p>\n<p>" )

  {

    echo "<table border=\"2\" bordercolor=\"#CC0033\"

                 bgcolor=\"#FFFEE\" cellpadding=\"2\">\n<tr><td>";

    echo $err_msg;

    echo "</td></tr>\n</table>\n";

  }

  if( isset($cc_submit) && $valid )

  {

Once the form is validated, we're ready to process the order. First, we retrieve some values that were passed from the previous page via POST:

    $ship_type = $HTTP_POST_VARS["ship_type"];

    $order_total = $HTTP_POST_VARS["order_total"];

We're still using the variable $s_id to hold the value of the session ID, but now we'll maintain it between page loads by storing it as the value of a hidden form field and retrieving it from $HTTP_POST_VARS:

    $s_id = $HTTP_POST_VARS["s_id"];

Then we create a new record in the orders table. It's assumed that only records relating to paid orders are stored here:

    $o_query = "INSERT INTO orders

               (session_id,shipping_type_id,order_total,order_datetime) ";

    $o_query .= "VALUES ('$s_id','$ship_type','$order_total',NOW())";

    $o_result = mysql_query($o_query)

      or die("<p>There was a problem in processing your order. Please contact

      us by email or phone for further assistance. Please be ready to supply

      your reference code, which is <b>$s_id</b>, so that we can

      retrieve your ordering information.</p>");

Next, we need to update our inventory, so that we don't try to oversell our stock. We start by retrieving all the items in the user's cart:

    $i_query = "SELECT item_type_id,product_or_event_code,quantity ";

    $i_query .= "FROM carts WHERE session_id='$s_id'";

    $i_result = mysql_query($i_query)

      or email();

Now we need to run an UPDATE query for each item that was just paid for. Because the structures for storing albums and tickets are different, we'll need to assemble this query on the fly, depending upon the item type. If the item's an album, the query will look something like this:

UPDATE product_codes

SET product_code_inventory = product_code_inventory - $i_quantity

WHERE product_code_id = $i_code;

where $i_quantity is the quantity ordered and $i_code is the item's product_code_id. If the item is a ticket to a show, our query will be in the form:

UPDATE events

SET event_seats_sold = event_seats_sold + $i_quantity

WHERE event_id = $i_code;

Of course, in this case, $i_code stands in for event_id. We can boil these two queries down to something like:

UPDATE $table SET $change WHERE $id = $i_code;

where $table, $change and $id take the place of those portions of the two queries that we show in italics.

So, for each item in the user's cart, what we need to do in assembling the SQL is to determine if the item in question is an album or a ticket and substitute the corresponding values into the string making up the query that we actually want to send to MySQL:

    while($i_row = mysql_fetch_assoc($i_result))

    {

      $i_type = $i_row["item_type_id"];

      $i_code = $i_row["product_or_event_code"];

      $i_quantity = $i_row["quantity"];

If the item is an album, those values will be those relating to the product_codes table:

      if($i_type == 1)

      {

        $table = "product_codes";

        $id = "product_code_id";

        $change =
           "product_code_inventory = product_code_inventory - $i_quantity";

      }

Otherwise, we need to interpolate the values relating to the events table:

      else

      {

        $table = "events";

        $id = "event_id";

        $change = "event_seats_sold = event_seats_sold + $i_quantity";

      }

We then put together the complete query and submit it using the mysql_query() function.

      $u_query = "UPDATE $table SET $change WHERE $id = $i_code";

      $u_result = mysql_query($u_query);

    }

?>

Now we can notify the user that the order's been processed (at least so far as the onsite database in concerned).

<h2>Thank You!</h2><p>You order in the amount of $<?php printf("%.2f",$order_total); ?> has been processed.</p>

<?php

While we're at it, let's also send the customer a confirming e-mail.

Setting up e-mail support in PHP is usually quite easy, if it's not already configured on your server. It's a little different on Windows and Unix, but in both cases it involves setting a variable in the php.ini file. For Win32 platforms, the SMTP configuration variable is set to the name or IP address of your mailserver, e.g. SMTP=mail.tunein.site. In addition, the sendmail_from configuration directive can be set to a desired return address, for example, sendmail_from=shopping_cart@tunein.site. If you're running a test server in your home or office, you can even set this to the same mail server you use for your outgoing personal e-mail. (Don't do this for sending more than a few test e-mails without checking with your ISP or system administrator! You could run afoul of someone's anti-spamming policy.) For Unix-style servers, we set the sendmail_path configuration variable to the correct path on the system to sendmail or its equivalent; in most cases this will be sendmail_path=usr/sbin/sendmail or sendmail_path=usr/lib/sendmail; if you're using Qmail, this path is likely to be var/qmail/bin/sendmail or var/qmail/bin/qmail-inject. If you encounter problems, check your system's documentation or ask the administrator for the system, network, ISP or site host that you're using.

We get the customer's e-mail address and name from the customers table and assign their values to the variable $to and $name; and we assign a suitable subject line string to the variable $subject as well.

    $c_query = "SELECT customer_first_name,customer_last_name,

                                                         customer_email ";

    $c_query .= "FROM customers WHERE session_id=$s_id";

    $c_result = mysql_query($c_query);

    $c_row = mysql_fetch_assoc($c_result);

    $to = $c_row["customer_email"];

    $name = $c_row["customer_first_name"] . " " .

                                             $c_row["customer_last_name"];

    $subject = "TuneIn! Order Confirmation";

We'll store the body of the e-mail in a variable named $message, which we'll build up as we go along, beginning with an appropriate greeting:

    $message = "Dear $name,\n\nYour order for\n\n";

What follows is essentially the same code that we used to generate the Cart Detail display for the page cart.php. The only substantial difference is that we're generating plain text rather than HTML, so we do our formatting with ASCII linebreak and tab characters (\n and \t).

    $a_query = "SELECT c.cart_id,c.quantity,pg.product_group_title,

                   f.format_description,pc.product_code_price ";

    $a_query .= "* CASE WHEN ISNULL(s.special_percentage) OR

                     s.start_date> CURDATE() OR s.end_date<CURDATE() ";

    $a_query .= "THEN 1 ELSE (100-s.special_percentage)*.01 END

                     AS price FROM product_codes pc ";

    $a_query .= "LEFT JOIN product_groups pg USING (product_group_id) ";

    $a_query .= "LEFT JOIN formats f ON f.format_id=pc.format_id ";

    $a_query .= "LEFT JOIN carts c

                     ON pc.product_code_id=c.product_code_or_event_id ";

    $a_query .= "LEFT JOIN specials s

                     ON pc.product_code_id=s.product_group_or_event_id ";

    $a_query .= "WHERE c.session_id='$s_id' AND c.item_type_id=1";

    $a_result = mysql_query($a_query);

    $album_total = 0;

    if(mysql_num_rows($a_result) > 0)

    {

      while($a_row = mysql_fetch_assoc($a_result))

      {

        $c_id = $a_row["cart_id"];

        $quantity = $a_row["quantity"];

        $price = $a_row["price"];

        $code = $a_row["product_or_event_code"];

        $title = $a_row["product_group_title"];

        $format = $a_row["format_description"];

        $item_amount = $quantity * $price;

        $album_total += $item_amount;

        $message .= "($quantity)\t$format -- $title @ \$";

        $message .= sprintf("%.2f", $price) . "--> \t\$"

                                     . sprintf("%.2f", $item_amount);

The sprintf() function works to format output in a similar fashion to printf(), and takes the same sorts of arguments. The only difference is that sprintf() returns a string instead of outputting it immediately.

        $message .= "\n";

      }

    }

    $message .= "\n\n";

    $ticket_total = 0;

    //  ticket detail -- similar to that used for cart.php...

    $t_query = "SELECT c.cart_id,c.quantity,e.event_name,

                                    e.event_datetime,e.event_price ";

    $t_query .= "* CASE WHEN ISNULL(s.special_percentage)

                 OR s.start_date>CURDATE() OR s.end_date<CURDATE() ";

    $t_query .= "THEN 1 ELSE (100-s.special_percentage)*.01 END

                               AS price,v.venue_city,st.state_code ";

    $t_query .= "FROM events e LEFT JOIN venues v USING (venue_id) ";

    $t_query .= "LEFT JOIN states st USING (state_id) ";

    $t_query .= "LEFT JOIN tunein.carts c

                             ON e.event_id=c.product_code_or_event_id ";

    $t_query .= "LEFT JOIN tunein.specials s ON

                               e.event_id=s.product_group_or_event_id ";

    $t_query .= "WHERE c.session_id='$s_id' AND c.item_type_id=2";

    $t_result = mysql_query($t_query);

Of course, you should include some form of user-friendly error-handling for each of the calls to mysql_query(). In cases where the failure of a query doesn't critically impact the application for the user, you might want to use something like the following code:

if( $t_result !== mysql_query($t_query) )

  mail("tech_admin@tunein.site","Non-fatal DB Error",

      "The query $t_query run on page $PHP_SELF for visitor $s_id failed.");

else

{

  if( mysql_numrows($t_result) > 0 )

  {

    // ... etc. ...

  }

}

It sends an e-mail automatically to someone in Technical Support and have the matter investigated.

    if(mysql_num_rows($t_result) > 0)

    {

      while($t_row = mysql_fetch_assoc($t_result))

      {

        $c_id = $t_row["cart_id"];

        $quantity = $t_row["quantity"];

        $price = $t_row["price"];

        $code = $t_row["event_id"];

        $event = $t_row["event_name"];

        $datetime = strtotime($t_row["event_datetime"]);

        $date = date( "F jS", $datetime);

        $time = date( "g:i A", $datetime);

        $city = $t_row["venue_city"];

        $item_amount = $quantity * $price;

        $ticket_total += $item_amount;

        $message .= "($quantity)\t$event -- $city, $date, $time @ \$";

        $message .= sprintf("%.2f", $price) . "--> \t\$" .

                                        sprintf("%.2f", $item_amount);

        $message .= "\n";

      }

    }

We let the customer know the time and date on which the order was processed.

    $currtime = date("g:i:s");

    $currdate = date("l, j F, Y");

    $message .= "\nwas processed at $currtime on $currdate.\n\n";

We also provide confirmation of the shipping method and the amount charged, as well as the total for merchandise and the total amount charged. We also include the session ID to use as a reference number.

    $merch_total = $album_total + $ticket_total;

    $shipping = $order_total - $merch_total;

    $s_query = "SELECT shipping_type_description FROM shipping_types ";

    $s_query .= "WHERE shipping_type_id = $ship_type";

    $s_result = mysql_query($s_query);

    $s_description = mysql_result($s_result,0);

    $message .= "MERCHANDISE TOTAL: \$";

    $message .= sprintf("%.2f",$merch_total) . "\n";

    $message .= "SHIPPING ($s_description): \$";

    $message .= sprintf("%.2f",$shipping) . "\n";

    $message .= "ORDER TOTAL CHARGES: \$";

    $message .= sprintf("%.2f",$order_total) . "\n";

    $message .= "\n\nThank for your order. Your reference number is $s_id.\n";

To send the e-mail, we just call the PHP mail() function with $to, $subject and $message as its arguments:

    if( mail($to, $subject, $message) )

    {

?>

You can include additional headers in the mail such as mimetype and X-Sender. The sender field can also be changed via the sendmail_from directive in the php.ini file (on Windows).

We display a message to the customer indicating that the e-mail's been sent:

<p>You will be receiving a confirmation notice via email shortly.</p>

<p>Your order reference code is <?php echo $s_id; ?>.</p>
<?php

    }

...unless, of course, something went wrong when we tried to send it:

    else

    {

?>

<p>Your order was processed but we had a problem in sending a confirmation
notice to you. Please contact us to verify your email address. Thanks!</p>

<?php

  }  // end if($cc_number && $valid)

Now we take care of the case where either the credit card information form hasn't yet been submitted, or it has been but there were one or more validation errors. This is all quite similar to what we used for the Contact and Address form earlier in the chapter. If your credit card verification service requires more information on the cardholder, you can probably copy and paste the HTML for those form fields from checkout.php into this file, perhaps prefixing the field names with "Cardholder0" (e.g. Cardholder0Address0Line01_address_required) to avoid confusion, as we've done with the Cardholder Name and Cardholder ZIP Code fields already. The information in this form should probably not be saved anywhere unless you plan to set up user accounts and obtain permission to do so from each customer.

Do not save credit card account numbers unless you do via a secure connection and the database in which you save them can't be read from the Web.


  else

  {

?>

<h2>Credit Card Information</h2>

<p>All fields are required.</p>

<script src="../scripts/validate.js"></script>

<form action="<?php echo $PHP_SELF; ?>" method="POST"

      onsubmit="return validate(this);">

<table width="80%" cellpadding="1" cellspacing="0" border="0">

  <tr>

    <th class="cart" colspan="2">Your name as it appears on the card:</th>

  </tr>

  <tr>

    <td class="cart" colspan="2" align="center">

      <input type="text" name="Cardholder0Name_required_alphabetic" size="50"

        value="<?php report("Cardholder0Name_required_alphabetic"); ?>" />    </td>

  </tr>

  <tr>

    <td colspan="2"><hr noshade="noshade" /></td>

  </tr>

  <tr>

    <th class="cart" colspan="2">Type of card:</th>

  </tr>

Note that there's some repetitive code here where we display the credit card types and accompanying radio buttons. Since we only use this code once and there's just three card types to worry, we don't bother with doing so here, but if we were to re-use this display elsewhere on the site, or if we began accepting more types of card, we might want to consider storing the types in an array or even in another database table.

  <tr>

    <td class="cart" align="right" width="50%">Visa</td>

    <td class="cart">

      <input type="radio" name="Credit0Card0Type_required"

        value="Visa"

<?php

 if( isset($HTTP_POST_VARS["Credit0Card0Type_required"])

      &&

      $HTTP_POST_VARS["Credit0Card0Type_required"] == "Visa" )

    echo " checked=\"checked\"";

?> />

    </td>

  </tr>

  <tr>

    <td class="cart" align="right">MasterCard</td>

    <td class="cart">

      <input type="radio" name="Credit0Card0Type_required"

        value="MasterCard"
<?php

  if( isset($HTTP_POST_VARS["Credit0Card0Type_required"])

      &&

      $HTTP_POST_VARS["Credit0Card0Type_required"] == "MasterCard" )

    echo " checked=\"checked\"";

?> />

    </td>

  </tr>

  <tr>

    <td class="cart" align="right">American Express</td>

    <td class="cart">

      <input type="radio" name="Credit0Card0Type_required"

        value="American Express"

<?php

  if( isset($HTTP_POST_VARS["Credit0Card0Type_required"])

      &&

      $HTTP_POST_VARS["Credit0Card0Type_required"] == "American Express" )

    echo " checked=\"checked\"";

?> />

    </td>

  </tr>

  <tr>

    <td colspan="2"><hr noshade="noshade" /></td>

  </tr>

  <tr>

    <th class="cart" colspan="2">Expiration date:</th>

  </tr>

  <tr>

Here we make use of the date() and mktime() functions to display dropdowns from which the user can select the card expiration month and year. The latter is called as

mktime( hour , minute , second , month , day , year );

and returns a Unix timestamp which is equivalent to the number of seconds elapsed since 12:00:00 AM Universal Time on 01 January 1970, also known as the Unix Epoch. It's not a good idea to omit any of these 6 arguments, all of which should be integers; doing so can lead to unexpected results. The function can take an optional seventh argument to indicate whether or not the time and date are Daylight Saving Time  use a 1 for DST and a zero for standard time. The default is zero.

    <td class="cart" align="center">Month:<br />

      <select name="Expiration0Month_required">

          <option value=""

<?php

  if( !isset($HTTP_POST_VARS["Expiration0Month_required"]) )

    echo " selected=\"selected\"";

?>>[choose month:]</option>

<?php

This for() loop generates a set of 12 <option> tags whose values are the integers from 1 to 12, and whose text contains the three-letter abbreviations for the months in order ("Jan" to "Dec"). We use the same technique to set the selected option to the expiration month previously selected on page reload as we did for the State dropdown in the Address form seen earlier in this chapter:

    for ($m=1;$m<13;$m++)

    {

      $month = date ("M",mktime(0,0,0,$m,1,0));

      echo "<option value=\"$m\"";

      if( isset($HTTP_POST_VARS["Expiration0Month_required"])

           &&

           $HTTP_POST_VARS["Expiration0Month_required"] == $m)

        echo " selected=\"selected\"";

      echo ">$month</option>\n";

    }

?>

        </select>

    </td>

    <td class="cart" align="center">Year:<br />

      <select name="Expiration0Year_required">

        <option value=""

<?php if( !isset($HTTP_POST_VARS["Expiration0Year_required"]) )

        echo " selected=\"selected\"";

?>>[choose year:]</option>

<?php

The dropdown for the card's year of expiration is generated in a similar fashion, except that we start with the current year and display options for it and the 5 years following. As most credit and bank cards are issued for 1 to 4 year periods, this should be a sufficient span of time:

    $thisyear = date("Y");

    for($y=0;$y<=5;$y++)

    {

      $theyear = $thisyear + $y;

      echo "<option value=\"$thisyear\"";

      if( isset($HTTP_POST_VARS["Expiration0Year_required"]) && $HTTP_POST_VARS["Expiration0Year_required"] == $thisyear)

        echo " selected=\"selected\"";

      echo ">$theyear</option>\n";

    }

?>

        </select>

    </td>

  </tr>

  <tr>

    <td colspan="2"><hr noshade="noshade" /></td>

  </tr>

  <tr>

    <td class="cart" align="center">Card Number:<br />

      <input type="text" name="Credit0Card0Number_ccnumber_required" size="16"

        value="<?php report("Credit0Card0Number_ccnumber_required"); ?>" />

    </td>

    <td class="cart" align="center">Zip Code where you receive your statement:<br />

      <input type="text" name="Cardholder0Zip0Code_uszip_required" size="10"

             value="<?php report("Cardholder0Zip0Code_uszip_required"); ?> />

    </td>

  </tr>

  <tr>

    <td colspan="2"><hr noshade="noshade" /></td>

  </tr>

  <tr>

    <td align="center">

      <input type="submit" name="cc_submit" class="search" value="Submit" />

    </td>

    <td align="center">

      <input type="reset" name="reset" class="search" value="Reset" />

    </td>

  </tr>

We also display the amount to be charged to the customer's card, and provide them a reference number in case there are any problems.

  <tr>

    <td class="cart">

      Amount to be charged: $<php echo $HTTP_POST_VARS["order_total"]; ?>

    </td>

    <td>

      Order Reference Code: <php echo $HTTP_POST_VARS["s_id"]; ?>

    </td>

  </tr>

</table>

  <input type="hidden" name="ship_type"

         value="<?php echo $HTTP_POST_VARS["ship_type"]; ?>" />

  <input type="hidden" name="order_total"

         value="<?php echo $HTTP_POST_VARS["order_total"]; ?>" />

  <input type="hidden" name="s_id" value="<?php echo $s_id; ?>" />

</form>

<?php

  }

?>

      </td>

    </tr>

  </table>

  <p><a href="checkout_help.html" target="_blank">HELP</a></p>

</body>

</html>

You may have noticed that we've omitted one crucial step here, which is obtaining the actual authorization of the sale. The method for doing this will vary according to the processor or clearinghouse that's used; in general, you'll be provided with code and/or programming APIs and guidelines to employ. The scenario might look something like this:

1.    Customer fills out secure credit card information form and the card number is validated

2.    The credit card form data form data required by the card processing agency is posted to the processor's server via a secure connection

3.    Authorisation code or "declined" code is posted back to a script on your server (also using a secure connection)

4.    If an authorisation is returned from the processor, display success message to customer,  update inventory and send confirmation e-mail

5.    If the sale is rejected, return customer to the Payment Options screen, displaying a message indicating that the card was declined and perhaps provide appropriate contact information or advise the customer to contact his bank or credit card provider to ascertain the nature of the problem.

George Petrov

George PetrovGeorge Petrov is a renowned software writer and developer whose extensive skills brought numerous extensions, articles and knowledge to the DMXzone- the online community for professional Adobe Dreamweaver users. The most popular for its over high-quality Dreamweaver extensions and templates.

George is also the founder of Wappler.io - the most Advanced Web & App Builder

See All Postings From George Petrov >>

Comments

Be the first to write a comment

You must me logged in to write a comment.