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:
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"> </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.
Comments
Be the first to write a comment
You must me logged in to write a comment.