Chapter 23

Shopping Carts


CONTENTS


What Are Shopping Cart CGIs?

The familiar shopping cart you push through the local supermarket is an appropriate metaphor to chose for the type of convenience and control people have come to expect from conducting transactions in the everyday world. We push our shopping carts through the aisles while we pick the items we want and ignore those that don't interest us. We add and remove items from our carts almost without thinking. The shopping cart CGI is an attempt to translate that convenience and control to the purchases we can make on the World Wide Web.

Shopping cart CGIs are essentially complex and feature-laden order forms. The purpose of a shopping cart CGI is to make online purchases as easy as possible for the potential consumer and to give them the control over purchases to which they are so accustomed. In one form or another, shopping carts are the direction that commerce on the Internet is taking-they are the successor to the simple order form.

Simple order forms are generally limited in the amount of interactivity they give the customer. Generally, they allow only a limited number of products and choices. With a shopping cart CGI, you can browse through hundreds or thousands of different items; choose the quantity, size, color of the items you want; change your order; find out how much the order is going to cost; leave the order process and come back to it later; make sure that the items you want are currently in stock; and so on.

What This Chapter Covers

My goal in this chapter is not to develop for you the world's most sophisticated, full-featured shopping cart CGI. After all, there are enough possible features and shopping cart mechanisms to fill an entire bookshelf. Each company's point-of-sale issues are unique, and to make a program that could easily handle all companies' needs is beyond the scope of this book (and in all likelihood, beyond the scope of even the most sophisticated artificial intelligence). Instead, the goal of this chapter is to produce a reasonably simple shopping cart mechanism that the reader can expand and customize according to his needs and to explore the tools needed for more complex shopping programs.

In this chapter, you will learn:

Equipped with the tools and techniques learned in this chapter, you should be able to build a shopping cart CGI of your own, one full-featured enough for any application.

All the code in this chapter could have been written in any language, but it is written in Perl. Among most CGI developers, Perl is the language of choice because of the flexibility and efficiency it affords. There is a whole class of features in Perl that the author of the language, Larry Wall, refers to as "metamagical." These metamagical features such as dynamic array allocation and interpolated variable names make life a lot easier for programmers.

There are those who claim that Perl sacrifices elegance and style in favor of quick and dirty programming. This is probably a fair assessment. Style and elegance are fine in a computer science class room but generally mean very little to clients and superiors who need working code and have deadlines to meet. If you write code for a living in a high-pressure environment, Perl is a language you will appreciate.

The Basic Elements of a Shopping Cart CGI

This is probably a good time to clarify exactly what I am talking about when I use the term "cart." In the context in which I use this term, a "cart" is really no more than a list of products a visitor to your Web site wants to buy. The "cart" starts out empty and as the customer browses through your catalog, he adds or removes items from this list. Keeping track of the current cart contents throughout the shopper's visit to your site is the primary problem to be solved when designing this sort of program.

By their very nature, shopping cart type programs make life difficult for the programmer. This is often the trade-off when trying to make things as simple and natural for the user as possible. With simple order forms, the customer simply fills in or checks off what she wants to order. In regular practice, that becomes quite difficult. The first problem is simply the size of a potential product catalog. Next is the nature of consumerism. People change their minds or look for things in different orders than what you might potentially expect. Most of these problems can be categorized into the following areas:

The Product Catalog

Listing 23.1 is the simple product catalog that will be read into our program. This is a file that could easily be generated by any spreadsheet or database program. The fields are separated by tabs, and records are separated by newlines.

If you are dealing with more complex data structures or Database Management (DBM) files, refer to the section "Database Management," later in this chapter.


Listing 23.1. A simple product catalog.
T    "I Love CGI Programming Unleashed" T-Shirt    19.95
hat    "Cloth & Suede "Unleashed" Hat    9.95
keychain    "Unleashed" Keychain    4.95
mousepad    "Unleashed" Mouse Pad    4.95
bball    "Team CGI" Baseball Jersy    39.95
plate    "Unleashed" Commemorative Plate    29.95
coaster    "Unleashed" Coasters, set of 6    4.95
nitelite    "Unleashed" Glow-in-the-Dark Nite-Lite    2.95

The three fields per item in this catalog are product code, product description, and price. In this example, the product type simply indicates the page on which the product appears. I have chosen product codes that are very readable, but be aware that in most cases, product codes are likely to be much more cryptic, along the lines of pcX231 or GST352. In all likelihood, there will be a system linking the product code to information about the product itself (size, product type, expiration date, and so on). Finding out about this system can give you even more versatility in creating features for your shopping cart.

The catalog is read into an associative array called iteminfo. Each element of iteminfo is a description of the product followed by the price-for example, $iteminfo{T} = "I Love CGI Programming Unleashed" T-Shirt@19.95. The following code segment shows an example of how to read a file (catalog.txt) into a two-dimensional array. Notice the use of the angle brackets <> within the while statement. The while loop will cycle through each line of each file in PRODUCTS.

$filename = "catalog.txt";
open (PRODUCTS, $filename);
while (<PRODUCTS>) {
($type, $code, $name, $price) = split (/\t/);
$iteminfo{$type, $code} = "$name\@$price";
}

Note
Perl does not directly support multidimensional arrays, but it doesn't really matter because Perl emulates the support of multidimensional arrays flawlessly. The line
$iteminfo{$type, $code} = "$name\@$price";
is a shorthand method for
$iteminfo {join ($;, $type, $code)} = "$name\@price";
The special variable $; is the subscript separator for multidimensional array emulation. The default value is ASCII 34, a nonprintable character, but it can be set to any value you want. If you want the substring separator to be the character ";", you can produce the following all-punctuation Perl line:
$; = ";";

Using Hidden Input Fields to Maintain State

The simplest means of tracking state within the shopping cart CGI is through the use of hidden input fields in forms. The idea is to record past additions to the cart and include them every time the form is resubmitted.

The program in this chapter uses the subroutine makehidden to write the hidden input types. In order to distinguish items previously added to the cart, the item variable names are "pre-pended" with the word old. Here's the makehidden subroutine.

sub makehidden {
foreach (sort keys %input) {
($item, $variable) = $_ =~ /^(\w+);(\w+)/;
print "<INPUT TYPE=hidden NAME=\"old$item $variable\"
VALUE=\"$input{$_}\">\n" if ($input{$item, add} eq "on");
}
}

Here's an example of the HTML generated by the makehidden subroutine:

<INPUT TYPE=hidden NAME="oldT add" VALUE="on">
<INPUT TYPE=hidden NAME="oldT qty" VALUE="100">
<INPUT TYPE=hidden NAME="oldkeychain add" VALUE="on">
<INPUT TYPE=hidden NAME="oldkeychain qty" VALUE="60">

Caution
One possible problem with using hidden input fields is that they are not really all that hidden. Anyone can see the contents of the hidden field by looking at the source. It's possible that a user can download the page through her browser, change hidden fields by hand, and then resubmit the page with the changed information-an incredible potential security risk if the proper precautions are not taken.
The simplest way to avoid the problem of someone trying to submit a form in which he has changed the elements of the hidden input fields is to verify the path that the form is submitted from and reject input from improper paths.
Any time a form is submitted, the path that the form is submitted from is contained in the environment variable PATH.

Other Ways of Keeping Track of State

Although using hidden input is certainly a simple way of maintaining state, it is not the only or even the best way to do so. Here I will outline some of the most common methods used today for maintaining state.

htaccess and REMOTE_USER

One solution to the problem of identifying and remembering your customers is through the use of password-protected areas. Users are required to identify themselves as they enter. Thereafter, they carry their identification with them. The standard on the Internet for doing this is to protect your directories using the htaccess protocols found in most of the common Web servers run today.

Anyone who has devoted time to surfing the Web will be familiar with the pop-up boxes that ask you to enter your username and password before entering a secured or private area. These pop-up boxes appear any time a directory is accessed that has an .htaccess file in it. Listing 23.2 shows the Basic format of the .htaccess file, and Listing 23.3 shows a sample password file.


Listing 23.2. Sample .htaccess file.
AuthUserFile  /usr/local/etc/httpd/secure/.htpasswd
AuthGroupFile /usr/local/etc/httpd/secure/.htgroup
AuthName CGI Unleashed
AuthType Basic

<Limit GET>
require group users
</Limit>


Listing 23.3. Sample .htpasswd file.
admin:qA/YMMCtFfBqk
anadas:hzUyvHrqk.JTw
erica:69ZUiJhiMnnLw
dave:D.Q6DFMVLIKIo
guest:..ZkwGDiWjEEs

The .htusers file is usually very simple:

users: admin anadas dave erica guest

The easiest way to add new passwords to the .htpasswd file is through using the program htpasswd, which is usually found in the same directory as the Web server itself. You can, however, add users and passwords through a Perl script using the crypt function.

In this example the user's password as it is typed in is in $input{passowrd} and the login name is $input{login}.

$salt = pack("HH",$input{login});
$pass = crypt($input{password}, $salt);
open (PASSES, ">>secure/.htpasswd");
print PASSES "$input{login}:$pass\n";
close (PASSES);

Once identification has been verified, the username entered in the pop-up box is carried in the environment variable REMOTE_USER.

Session ID Embedding

At many places on the Internet, you might notice very long and strange looking URLs, often with a sequence of number and letters in the middle. In these cases, it's likely that information about the current state is maintained through session ID embedding.

Session ID embedding assigns a unique identifier to each customer as she comes to the Web site. This session ID is then passed either through the URL itself or through a hidden input field in a form.

By maintaining state in this way you avoid having to constantly pass all of the customer data every time a new page is generated. You can simply query the file containing the information about the session for any data you need.

Session IDs are recorded in a file along with relevant information and generally expire after a day or even after a few hours.

HTTP Cookies

HTTP cookies are a way to maintain state through the browser itself, even between sessions.

Cookie Recipes

You can use the Set-Cookie call in two ways to establish a cookie with the browser visiting your site: include it in the HTTP header information or use the <META HTTP-equiv> tag.

Here is a code example that uses HTTP header information to set cookies.

print "HTTP/1.0 200 OK\n\rSet-Cookie: ";
print "cookie=";

for (keys %input){
print "$_=$input{$_}&" unless $input{$_} eq $empty;
}
print "; path=/; expires=09-Nov-99 GMT\n\r\n\r";

Setting Cookies with <META HTTP-equiv>

Using the <META HTTP-equiv> tag, you can set cookies even without using CGI. All that is required is including the Set-Cookie arguments within the <HEAD> definition of your Web page.

Caution
Many features are not universally supported among different Web browsers. At the time of this writing, <META HTTP-equiv> is not standard HTML and not universally supported. If you plan to use the <META HTTP-equiv> tag to set cookies or any other nonstandard HTML tag, you should be careful to rigorously test your Web pages with any of the browsers you want to support.

Here is a code example using <META HTTP-EQUIV> to set cookies.

print "<HTML><HEAD>\n<META HTTP-EQUIV=\"Set-Cookie\" ";
print "Content=\"cookie=";

for (keys %input){
print "$_=$input{$_}&" unless $input{$_} eq $empty;
}
print "; path=/; expires=09-Nov-99 GMT\">\n";
}
else {
print "<HTML><HEAD>\n";

Building Customer Profiles

Using either client-side cookies or .htaccess, it becomes very simple to store data about your customers. The uses for this information include the following:

With either the cookies or the htaccess method, each customer should have a unique identifier. In the case of .htaccess, this is their login name, and in the case of cookies, it can be whatever you decide.

Here's a sample customer profile (cust127.profile):

alias:cust127
name:Maggie O'Connell
address:121 Sycamore Rd.
city:Cicely
state:Alaska
zip code:90210

Database Management

Although the goal of shopping carts is to provide a sophisticated means of interacting with the consumer, a sophisticated shopping cart system integrated into a real database back end can make life simpler for both customer and company. With true database integration, it's possible to add and delete new products quickly, change prices, keep track of inventory, and so on. If there is already a database with this information available, it should be built into the overall scheme of your shopping cart implementation.

Using DBMs

To those familiar with working with databases on UNIX systems, DBM files should be old news. I mention them here specifically in order to discuss the way that Perl handles DBMs. To those who are not familiar with DBMs, this brief discussion could also prove useful, if only for future reference.

In Perl, it is reasonably simple to turn an entire DBM file into an associative array, editing both the array and the DBM at the same time.

Here is an example of how to print the contents of a DBM file with Perl. The file that is being opened is an inventory DBM located in the directory files, and the file is being opened into an associative array called INVENTORY.

dbmopen (%INVENTORY, '/files/inventory');
while(($key, $val) = each %INVENTORY) {
print "$key = $val";
}
dbmclose (INVENTORY);

Using DMBs allows you to manipulate and extract information from large files without having to know the specific structure of the file itself. If DBM and NDBM (New Data Base Management) are available on your system, I highly recommend you become familiar with their use.

The Result of Your Labors, cart.cgi

The program listed here is a simple shopping cart with two pages of products. The information for the products is contained in the file catalog.txt. The program outputs HTML pages, including forms with hidden fields containing the current state of the shopping cart. This program produces either the cart contents page or a list of products for sale, or it mails the order depending on how the form was submitted.


Listing 23.4. cart.cgi: A simple shopping cart using hidden fields to transmit state.
#!/usr/bin/perl
print "Content-type: text/html\n\n";

# Simple Shopping Cart Using Hidden Fields to Transmit State

# Set Variables

$mailto="ken.hunt\@anadas.com";
$filename="catalog.txt";
$;=";";
$empty="";
$taxrate=.07;

# Determine Method and Get Input

if($ENV{'REQUEST_METHOD'} eq "GET") {
$input = $ENV{'QUERY_STRING'};
}
elsif ($ENV{'REQUEST_METHOD'} eq "POST") {
read(STDIN,$input,$ENV{'CONTENT_LENGTH'});
}
else {
print('Request method Unknown');
exit;
}

# Remove URL encoding and place input in two-dimensional associative array.

@input = split (/&/,$input);
   foreach $i (0 .. $#input) {
   $input[$i] =~ s/\+/ /g;
   $input[$i] =~ s/%(..)/pack("c",hex($1))/ge;
   ($name, $value) = split(/=/,$input[$i],2);
   ($item, $variable) = split (/ /,$name);
   $input{$item, $variable} = $value;
}

# Hidden form input variable names have been "pre-pended" with "old"
# To distinguish them from current input. This loop adds previously
# "added" quantities to the currently ordered quantity.

foreach (sort keys %input) {
($item, $variable) = $_ =~ /^(\w+);(\w+)/;
if ($item =~ /old/) {
   $currentitem = $item;
   $currentitem =~ s/old//g;
   unless (defined ($input{$currentitem, add})) {
     $input{$currentitem, add} = "on";
     $input{$currentitem, qty} = 0;
}
   $input{$currentitem, qty} =
      int($input{$currentitem, qty} + $input{$item, qty})
        if ($olditem ne $item);
   $input{$item, add} = "off";
   $olditem = $item;
}
}



# This loop adds items to the order and removes deleted items.
foreach (sort keys %input){
  ($item, $variable) = $_ =~ /^(\w+);(\w+)/;
  $input{$item, add} = "off" if defined $input{$item, del};
  $input{$item, qty} = $empty if defined $input{$item, del};
  $input{$item, add} = "off" if ($input{$item, qty} <= 0) ;
  if ($variable eq "add") {
    push (@orderlist, $item) if ($input{$item, add} eq "on");
  }
}

# Get Product Descriptions
&readproducts;

# Generate HTML and exit if a shopping page has been selected.
&pageone if ($input{button,$empty} eq "Page One");
&pagetwo if ($input{button,$empty} eq "Page Two");

# Mail the Order if the customer has selected that option.
& mailorder(@orderlist) if ($input{button,$empty} eq "Place this Order");

# Print the HTML header for the Cart Contents page.
&cartheader;

# Display the current order, if there is an order.
if ($#orderlist > -1) {

# Table Header
print "<tr><th>Qty.</th><th>Item</th><th>Price</th>";
print "<th>Item Total</th><th>Put<br>Back</th></tr>\n";

# Order Body
foreach (@orderlist){
  ($name, $price) = split (/@/,$iteminfo{$_});
  $input {$_, qty} = int ($input{$_, qty});
  print "<tr><td align=right>$input{$_, qty}</td><td>$name</td>";
  print "<td align=right>\$$price</td>";
  $itemtotal = $price * $input{$_, qty};
  $subtotal = $subtotal + $itemtotal;
  printf "<td align=right>\$%4.2f</td>", $itemtotal;
  print "<td align=center><INPUT TYPE=checkbox name=\"$_ del\"></td>";
  print "</tr>\n";
}

# Display Subtotal, Tax and Grandtotal for the order.
print "<tr><td colspan=3 align=right><font size=+1>Subtotal:</font></td>";
printf "<td align=right><font size=+1>\$%4.2f</font></td>", $subtotal;
print "</tr>";
print "<tr><td colspan=3 align=right><font size=+1>Tax:</font></td>";
$tax = $subtotal * $taxrate;
printf "<td align=right><font size=+1>\$%4.2f</font></td>", $tax;
print "</tr>";
$grandtotal = $subtotal + $tax;
print "<tr><td colspan=3 align=right><font size=+1>Total:</font></td>";
printf "<td align=right><font size=+1>\$%4.2f</font></td>", $grandtotal;
print "</tr>";
}

# If there is no current order display the empty cart.
else{
  print <<EOT;
<tr><td align=center>
<font size=+2>Your Cart is Empty.</font><br>
We have two pages of great products to choose from,<br>
so go fill up your cart.
</td></tr>
EOT
}

# Print the HTML footer for the Cart contents page and exit
& cartfooter;
exit 0;

# *** SUBROUTINES ***

# Read the Product information from a file. Fields are separated by tabs
# Records are separated by newline.
sub readproducts {
open (PRODUCTS, $filename);
while (<PRODUCTS>) {
($code, $name, $price) = split (/\t/);
$iteminfo{$code} = "$name\@$price";
}
}

# Produce the HTML for Page One of Product listings
sub pageone {

@itemlist = ("T", "hat", "keychain", "mousepad");
$gobutton = "Page Two";
&printheader;
&makehidden;
&printbody (@itemlist);
&printfooter ($gobutton);
exit 1;
}

#Produce the HTML for Page Two of product listings.
sub pagetwo {

@itemlist = ("bball", "plate", "coaster", "nitelite");
$gobutton = "Page One";
&printheader;
&makehidden;
&printbody (@itemlist);
&printfooter ($gobutton);
exit 2;
}

# The HTML header for both products page is the same.
sub printheader {

print <<EOT;
<HTML><HEAD>
<TITLE>CGI Unleashed Accessory Depot</TITLE>
</HEAD>

<BODY bgcolor="#FFFFFF">
<CENTER>
<H2>CGI Programming Unleashed<br>Accessory Depot</H2>
</CENTER>

<FORM METHOD=post ACTION=cart.cgi>
<TABLE width=100% border>
<TR><TH>Qty.</TH><TH>+/-<br>Qty.</TH>
<TH>Item</TH><TH>Price</TH><TH>Add<br>to Cart</TH></TR>

EOT
}

# Print the selected product list for the current page.

sub printbody {
  local (@itemlist) = @_;

  foreach(@itemlist) {
  ($name, $price) = split (/@/,$iteminfo{$_});
  print <<EOT;
<TR><TD ALIGN=right> $input{$_, qty}</TD>
<TD><INPUT TYPE="text" size=3 NAME="$_ qty" VALUE=1></TD>
<TD>$name</TD>
<TD ALIGN=right><B>\$$price</B></TD>
<TD ALIGN=center><INPUT TYPE=checkbox NAME="$_ add"</TD>
</TR>
EOT
  }
}

# Print the HTML footer for the product pages
sub printfooter {

local ($gobutton) = @_;
print <<EOT;
</TABLE>
<P>
<CENTER>
<INPUT TYPE=submit VALUE="$gobutton" NAME="button">
<INPUT TYPE=submit VALUE="View Cart" NAME="button">
<INPUT TYPE=reset>
</CENTER>
</FORM>

</BODY>
</HTML>
EOT
}

# Write current state by using hidden form input. Prepend "old" to
# The variable names to make them easy to recognize.

sub makehidden {
@custinfo = ('customer','address','city','state',
    'zipcode','email','payment_method', 'cardno');

foreach (@custinfo) {
  print "<INPUT TYPE=hidden NAME=$_ VALUE=\"$input{$_,$empty}\">"
    if $input{$_,$empty} ne $empty;
}


}

# Print the HTML header for the Cart Contents page

sub cartheader {

print <<EOT;
<HTML><HEAD>
<TITLE>CGI Unleashed Accessory Depot</TITLE>
</HEAD>

<BODY bgcolor="#FFFFFF">
<CENTER>
<H2>Cart Contents</H2>
</CENTER>

<FORM METHOD=post ACTION=cart.cgi>
<TABLE width=100% border>
EOT
}

# Print the HTML footer for the Cart Contents page

sub cartfooter {
&makehidden;

print <<EOT;
</table>

<p>
<center>
<INPUT TYPE=submit name="button" VALUE="Page One">
<INPUT TYPE=submit name="button" VALUE="Page Two">
<INPUT TYPE=submit name="button" VALUE="Delete Selected Items"><p>

<table border>
<tr><td colspan=2 align=center>
<font size=+1>Order Info: Complete Before Placing Order</font></td>
<tr><td>
<b>Name:</b> <INPUT TYPE=text SIZE=20 name="customer"
VALUE="$input{customer,$empty}"></td>
<td><b>Address:</b> <INPUT TYPE=text SIZE=20 name="address"
VALUE="$input{address,$empty}"></td></tr>
<td><b>Email:</b> <INPUT TYPE=text SIZE=20 name="email"
VALUE="$input{email,$empty}"></td>
<td><b>City:</b> <INPUT TYPE=text SIZE=20 name="city"
VALUE="$input{city,$empty}"><br></td></tr>
<tr><td><b>State/Prov.:</b> <INPUT TYPE=text SIZE=15 name="state"
VALUE="$input{state,$empty}"></td>
<td><b>Zip/Postal Code:</b> <INPUT TYPE=text SIZE=7 name="zipcode"
VALUE="$input{zipcode,$empty}"></td></tr> <tr><td colspan=2 align=center>
<b>Payment Method:</b>
Check: <INPUT TYPE=radio name="payment_method" VALUE="check">
"Unleashed" Card: <INPUT TYPE=radio name="payment_method" VALUE="credit">
Money Order: <INPUT TYPE=radio name="payment_method" VALUE="moneyorder">
</td></tr>
<tr><td align=center colspan=2>
<b>"Unleashed" Card # (if applicable)</b>
<INPUT TYPE=text NAME="cardno" size=8
VALUE="$input{cardno,$empty}"></td></tr> <tr><td colspan=2 align=center>
<INPUT TYPE=submit NAME="button" VALUE="Place this Order">
<INPUT TYPE=reset> </td></tr>
</table>
</center>
EOT

print "</form>";
}

# Mail the order information to the person indicted in $mailto

sub mailorder {

local (@orderlist) = @_;
@required=('customer','address','city','zipcode','state',
    'email','payment_method');

if ($input{payment_method,$empty} eq "credit") {
  &printerror if (length($input{cardno,$empty}) != 7);
}

foreach (@required) {
  &printerror unless defined $input{$_,$empty};
}

if ($#orderlist > -1) {
open (MAIL, "|/usr/sbin/sendmail -t");
print MAIL<<EOM;
To: $mailto
From: Shopping Cart CGI
Subject: New Order

Customer Info:
$input{customer,$empty}
$input{address,$empty}
$input{city,$empty}, $input{state,$empty}
$input{zipcode,$empty}
$input{email,$empty}

Paying by: $input{payment_method,$empty}
Number: $input{cardno,$empty}
EOM

foreach (@orderlist){
  ($name, $price) = split (/@/,$iteminfo{$_});
  $input {$_, qty} = int ($input{$_, qty});
  print MAIL "$input{$_, qty} $name @\$$price";
  $itemtotal = $price * $input{$_, qty};
  $subtotal = $subtotal + $itemtotal;
}
  $tax = $subtotal * $taxrate;
  $grandtotal = $subtotal + $tax;
  printf MAIL "Subtotal: \$%4.2f\n", $subtotal;
  printf MAIL "Tax: \$%4.2f\n", $tax;
  printf MAIL "Total: \$%4.2f\n", $grandtotal;

&printthanks
}
else {
&printerror
}
close (MAIL);
exit 3;
}

# Print a Thank You message once the order has been sent
sub printthanks {
print <<EOT;
<HEAD><HTML>
<TITLE>Thank You!</TITLE>
</HEAD>

<BODY bgcolor="#FFFFFF">

<center>
<h2>Thank you for your order, $input{$customer, $empty}</h2>
Please wait 6-8 weeks for delivery.
</center>
</BODY>
</HTML>
EOT

exit 4;
}

# Print an error message if an error is encountered when mailing the order.
sub printerror {

print <<EOT;
<HEAD><HTML>
<TITLE>Ooops!</TITLE>
</HEAD>

<BODY bgcolor="#FFFFFF">
<center>
<h2>The Shopping Cart CGI has Encountered an Error.</h2>
Either your cart was empty or you did not include all<br>
necessary information in the order form.<br>
Please go back and try again.

<p>
<FORM ACTION=cart.cgi METHOD=post>
<INPUT TYPE=submit name="button" VALUE="Go Back and try Again.">
EOT

&makehidden;

print <<EOT;
</center>
</FORM>
</BODY>
</HTML>
EOT

exit 5;
}

The following is an example of the HTML output generated by cart.cgi. This is a product listings page. Notice from the hidden input fields that before this page was generated, the customer had added 100 T-Shirts and 60 keychains to her shopping cart. Obviously a bulk buyer!


Listing 23.5. The HTML output produced by the shopping cart code.
<HTML><HEAD>
<TITLE>CGI Unleashed Accessory Depot</TITLE>
</HEAD>

<BODY bgcolor="#FFFFFF">
<CENTER>
<H2>CGI Programming Unleashed<br>Accessory Depot</H2>
</CENTER>

<FORM METHOD=post ACTION=cart.cgi>
<TABLE width=100% border>
<TR><TH>Qty.</TH><TH>Item</TH><TH>Price</TH><TH>Add</TH></TR>

<INPUT TYPE=hidden NAME="oldT add"
VALUE="on">
<INPUT TYPE=hidden NAME="oldT qty"
VALUE="100">
<INPUT TYPE=hidden NAME="oldkeychain add"
VALUE="on">
<INPUT TYPE=hidden NAME="oldkeychain qty"
VALUE="60">
<TR><TD><INPUT TYPE="text" size=3 NAME="bball qty" VALUE=1></TD>
<TD>"Team CGI" Baseball Jerseys</TD>
<TD ALIGN=right><B>$39.95</B></TD>
<TD ALIGN=center><INPUT TYPE=checkbox NAME="bball add"</TD>
</TR>
<TR><TD><INPUT TYPE="text" size=3 NAME="plate qty" VALUE=1></TD>
<TD>"Unleashed" Commemorative Plates</TD>
<TD ALIGN=right><B>$29.95</B></TD>
<TD ALIGN=center><INPUT TYPE=checkbox NAME="plate add"</TD>
</TR>
<TR><TD><INPUT TYPE="text" size=3 NAME="coaster qty" VALUE=1></TD>
<TD>"Unleashed" Coasters, set of 6</TD>
<TD ALIGN=right><B>$4.95</B></TD>
<TD ALIGN=center><INPUT TYPE=checkbox NAME="coaster add"</TD>
</TR>
<TR><TD><INPUT TYPE="text" size=3 NAME="nitelite qty" VALUE=1></TD>
<TD>"Unleashed" Glow-in-the-Dark Nite-Lite</TD>
<TD ALIGN=right><B>$2.95</B></TD>
<TD ALIGN=center><INPUT TYPE=checkbox NAME="nitelite add"</TD>
</TR>
</TABLE>
<P>
<CENTER>
<INPUT TYPE=submit VALUE="Previous Page" NAME="button">
<INPUT TYPE=submit VALUE="View Cart" NAME="button">
<INPUT TYPE=reset>
</CENTER>
</FORM>

</BODY>
</HTML>

Summary

This chapter outlined a simple but adequate shopping cart for most applications. Without too much effort, you should be able to customize the program and the catalog presented in this chapter to suit your own needs. Using the tools explained in this chapter its possible to build a state-of-the-art shopping cart CGI for any application.

In this chapter you learned how to do the following: