SimpleshoPHP

Detailed Analysis - Part 6

checkout.php

We're down to the finish line! Our customer has picked some books and put them into her "book bag" -- now it's time to let her pay for them.

Note that the demo site runs this page in plain http mode, but in production this page would be served by a secure SSL server as an https page.

<?
require_once("startme.inc");
if (! isset($_SESSION["xvalid"])) {
        header("Location: main.php");
        exit;
}

As noted in the bookbag page, if there is no book bag, there is no point in running the rest of the code -- escort the customer back to the main page and bail out. Note that we can either display a message or do the redirect by writing an HTML header -- you cannot do both. (If you try, you'll get a warning that you have "already sent headers.") A good enhancement would be to add a message display to main.php and pass our message to it as a parameter, e.g.

header("Location:main.php?msg='Start by putting some books in your Book Bag.'");

Just something to add to the "to do" list.

We could drop all the database logic and just paint the form below. We'd be assuming the customer knows what's in her Book Bag. That's a little hard on her, though. Let's not be that lazy -- especially since we already have all the Book Bag display code we need.

$sql = "SELECT books.sku, title, author, format, price, bag.qty
FROM books, bag WHERE books.sku = bag.sku AND sess = '$PHPSESSID'";

$sql_result = mysql_query($sql,$connection) 
	or die ("Couldn't get Bookbag");

$num_rows = mysql_num_rows($sql_result);
if ($num_rows < 1) {
// echo "You have no Book Bag";
 header("Location:main.php");
}

Beside, it's a good sanity check. Why ask for a credit card when there's nothing in the Book Bag to check out?

Got this trick from O'Reilly's PHP Cookbook. It's got some awesome material, but since I haven't geared up to use PEAR and object-oriented PHP, it's overkill for this project. This trick is called a "here document" -- you are telling PHP to treat everything it sees as raw text until you get to the magic word, "END", on a line by itself. The nice part is that you don't have to go through and quote everything, or "echo" it, as you do when you're in PHP mode. It might even run a tiny bit faster. The bad part is you aren't in PHP mode, so PHP statements such as include don't work within the text block, nor do any of your variables. You also have watch out for PHP style comments -- they will get echoed to the screen, along with the rest of your text.

// echo the whole form in one big chunk
echo <<<END
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html><head>
<link rel="stylesheet" href="simple.css">
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">

Notice that we're still in the 'head' of the html page. We're about to ask the customer to enter a lot of data, and it only makes sense to check it before we let it into our processing machinery. We can push some of that work to the user's browser by sending it some javascript. This is "cookbook" stuff; if a required field still has its original empty value, pop up an "alert" box that explains the problem, and put the browser's "focus" back onto the field that needs to be fixed. Repeat until all the required fields have been given values. Fields we don't care about, we simply don't check. Doing it this way, while the form is still on the user's screen, is easier for her and for us.

Note that the javascript inself is wrapped in HTML comments. This is normal; a browser that 'speaks' javascript will still process it, while one that doesn't will simply ignore the whole block. And of course, we still have to check the user's data afterward, to protect ourselves against a user who has javascript turned off (or is actively trying to fool us). Javascript is for spotting innocent mistakes, not for thwarting terrorists.

<script language="JavaScript" type="text/javascript">
<!--
function checkit ( form )
{
        if (form.full_name.value =='') {
                alert('Please give your name as it appears on your card');
                form.full_name.focus();
                return false;
        }
        else if (form.address1.value =='') {
                alert('Please give your street address');
                form.address1.focus();
                return false;
        }
        else if (form.address3.value =='') {
                alert('Please complete your address');
                form.address3.focus();
                return false;
        }
        else if (form.address4.value =='') {
                alert('Please complete your address');
                form.address4.focus();
                return false;
        }
        else if (form.cc_type.value ==0) {
                alert('Please choose your type of credit card');
                form.cc_type.focus();
                return false;
        }
        else if (form.cc_num.value =='') {
                alert('Please enter your credit card number');
                form.cc_num.focus();
                return false;
        }
        else if (form.cc_exp_mon.value ==0) {
                alert('Please pick your credit card expiration month');
                form.cc_exp_mon.focus();
                return false;
        }
        else if (form.cc_exp_yr.value ==0) {
                alert('Please pick your credit card expiration year');
                form.cc_exp_yr.focus();
                return false;
        }
        return true;
}
//-->
</script>

Okay, that's our data validation routine. We will not actually apply it until the user clicks on the "Submit" button below.

<title>Silk Purse Books - silkpursebooks.com</title></head>
<body bgcolor="#ffffff">
END;

In order to use our "top.inc" include we have to drop out of "HERE document" mode briefly. It's worth it, since the use of these include files is fundamental if we are to keep our look consistent across all pages.

$location="Checkout";
include("top.inc");

Once we have included our familiar top.inc, we go back to "here document" mode:

echo <<<END
<!--- left --->
<!--- main --->
<form name="ccorder" action="checkout2.php" method="POST" 
 onSubmit="return checkit(this)">

Note the "onSubmit" call to our javascript function, "checkit"!

<h2>Your Payment Information</h2>
<table border=0 cellspacing=0 cellpadding=5>
<tr>
<td><strong>Full Name:</strong></td>
<td><input type=text name=full_name size=40></td>
</tr>
<tr>
<td><strong>E-Mail Address*:</strong></td>
<td><INPUT TYPE=text NAME=email SIZE=40</td>
</tr>
<tr>
<td><strong>Address Line 1:</strong></td>
<td><INPUT TYPE=text NAME=address1 SIZE=40</td>
</tr>
<tr>
<td><strong>Address Line 2:</strong></td>
<td><INPUT TYPE=text NAME=address2 SIZE=40</td>
</tr>
<tr>
<td><strong>City,State:</strong></td>
<td><INPUT TYPE=text NAME=address3 SIZE=40</td>
</tr>
<tr>
<td><strong>ZIP/Post Code:</strong></td>
<td><INPUT TYPE=text NAME=address4 SIZE=20</td>
</tr>
<tr>
<td><strong>Credit Card Type:</strong></td>
<td>
<SELECT NAME=cc_type>
<OPTION VALUE=0>-- Select One --</OPTION>
<OPTION VALUE=Visa>Visa</OPTION>
<OPTION VALUE=MasterCard>MasterCard</OPTION></td>
</SELECT> 
</td>
</tr>

Whee! Aren't HTML forms fun? Oh, my aching fingers....

<tr>
<td><strong>Credit Card Number:</strong></td>
<td><INPUT TYPE=text NAME=cc_num SIZE=20</td>
</tr>
<tr>
<td><strong>Credit Card Expiration Date:</strong> </td>
<td>

If you've never used a "select" dropdown in HTML before, this may look a little weird, but it's actually plain vanilla HTML.

<SELECT NAME=cc_exp_mon>
<OPTION VALUE=0>--</OPTION>
<OPTION VALUE=1>01</OPTION>
<OPTION VALUE=2>02</OPTION>
<OPTION VALUE=3>03</OPTION>
<OPTION VALUE=4>04</OPTION>
<OPTION VALUE=5>05</OPTION>
<OPTION VALUE=6>06</OPTION>
<OPTION VALUE=7>07</OPTION>
<OPTION VALUE=8>08</OPTION>
<OPTION VALUE=9>09</OPTION>
<OPTION VALUE=10>10</OPTION>
<OPTION VALUE=11>11</OPTION>
<OPTION VALUE=12>12</OPTION>
</SELECT> 
 / 
<SELECT NAME=cc_exp_yr>
<OPTION VALUE=0\">--</OPTION>
<OPTION VALUE=2005>2005</OPTION>
<OPTION VALUE=2006>2006</OPTION>
<OPTION VALUE=2007>2007</OPTION>
<OPTION VALUE=2008>2008</OPTION>
<OPTION VALUE=2009>2009</OPTION>
<OPTION VALUE=2010>2010</OPTION>
<OPTION VALUE=2011>2011</OPTION>
<OPTION VALUE=2012>2012</OPTION>
<OPTION VALUE=2013>2013</OPTION>
<OPTION VALUE=2014>2014</OPTION>
</SELECT>
</td>
</tr>
</table>

An editorial comment: There is no point in DEMANDING an email address unless you are willing to refuse service to people who won't give it -- and even then, if you insist, they're likely to make up an email address. If they do, it's also likely their "fake" address will be a real address that belongs to someone else. It turns out that people aren't very good at making up fake addresses. You are better off with no email address than a fake one. If your customer doesn't want to know when her order shipped, that's her choice.

<p>
*PRIVACY NOTICE: If you don't want to give us your email, you may leave this<br>
field blank.  Please note that without a valid email address we will not be<br>
able to tell you when your order has been shipped, or ask you how to resolve a problem.
</p>
<p>
<input type=submit value="Purchase Now">
</p>
</form>
END;
?>

When the user clicks the Purchase Now button, her browser will run the "onSubmit" call and process the form fields through our javascript function "checkit." If the javascript returns "true," then the browser will go on to the action step, action="checkout2.php". This doesn't guarantee the form data is valid, but at least we know the user entered something!

This next bit should look familiar -- it's our bookbag code, with one change. Can you figure out what it is?

<table width="90%" align=center border=0 cellpadding=3>
<tr bgcolor="#e7f0ff">
<td><b>TITLE</b><td><b>AUTHOR</b><td><b>FORMAT</b><td><b>PRICE</b><td>QTY</tr>
<?
$i = 0; $tot = 0; $tqty = 0;
while ($row = mysql_fetch_array($sql_result)) {
	$sku = $row["sku"];
	$title = $row["title"];
	$author = $row["author"];
	$format = $row["format"];
	$qty = $row["qty"]; $tqty += $qty;
	$price = sprintf("%0.2f",$row["price"]); $tot += ($price * $qty);
	if ($i++ % 2)
            $BGC="#e7f0ff";
	else
            $BGC="#ffffff";
	echo "
	<tr bgcolor=$BGC>
	<td>$title<td>$author<td>$format<td>$price<td>$qty</tr>
	";
}
$ptot = sprintf("\$%0.2f",$tot);
if ($i++ % 2)
   $BGC="#e7f0ff";
else
   $BGC="#ffffff";
echo "
<tr bgcolor=$BGC><td>&nbsp;<td>&nbsp;<td>Total:<td>$ptot<td>$tqty";
?>
</table>

The "one change" is we have taken off the "remove from bag" links. It's too late to dink around with the bookbag at this point -- at least, I wouldn't be comfortable trying to do it.


<!--- bottom --->
<? include("bottom.inc"); ?>
</body></html>

Next: checkout.php has collected payment information; checkout2.php is where we encrypt it and send it off to our "order fulfillment" office.


Summary

Detailed descriptions

SimpleshoPHP Home


You are invited to post comments or questions on the SimpleshoPHP forum at SourceForge.net.

SourceForge.net Logo


Copyright 2003-2005, Kevin Martin, dba Brass Cannon Consulting.
The project "SimpleshoPHP" is Free Software, distributed
under the LGPL as described at opensource.org