Chapter 28

Fully Integrated Shopping Environment


CONTENTS


Chapter 27, "Multipage Shopping Environment," describes in detail a shopping cart system that can be used with any browser. That system is rather complex-each page is responsible for managing the unique ID assigned to users when they first enter the shopping area.

To use a conventional HTML page in the shopping area, you must make sure all outgoing links call page.cgi and pass along $orderID. Server-side image maps need special handling.

In short, maintaining pages in the shopping area is more work than maintaining static HTML files. As pointed out in Chapter 2, "Reducing Site Maintenance Costs Through Testing and Validation," when time and money are spent in maintenance, less is available for improving the content of the site and increasing traffic.

In an ideal world, you could intersperse conventional HTML pages with shopping pages without having to remember the ID. When the shopping cart needed to know who the client was, it would be able to ask the client. The good news is that for more than 90 percent of the users to most sites, the world is ideal-their browsers support cookies based on the Netscape cookie extension.

The Pros and Cons of Cookies

Netscape cookies are introduced in Chapter 9, "Making a User's Life Simpler with Multipart Forms." This section reviews the benefits and shortcomings of Netscape cookies.

Advantages

Recall that SetCookie allows a CGI script to store information on the client's hard drive. Chapter 24, "User Profiles and Tracking," describes a use for this mechanism that takes advantage of the long-term storage possible with cookies.

Another use of this mechanism is to store a unique ID that is only valid during the session and expires when the user quits the browser. This application lends itself nicely to the shopping application.

One way to use Netscape cookies to implement a shopping cart is to set a new cookie when users first arrive in the shopping area. Users can now enter and leave the shopping area as they see fit-their unique ID is safe on their hard drive. When they shop, review their order, or check out, the script simply does a GetCookie and uses their ID to look up their cart on the server's hard drive.

Disadvantages

The down side of Netscape cookies is implied by their name-the mechanism was invented by Netscape (although at least 12 other browsers are now reported to handle cookies correctly). If the CGI script sends SetCookie to the client and the client does not handle it, the Webmaster is faced with the unpleasant possibility of sending the shopper away or possibly switching to a conventional shopping cart script. Depending on the site, perhaps 10 percent of the users are running browsers that do not support Netscape cookies.

The Integrated Shopping Environment

Remember from Chapter 1, "How to Make a Good Site Look Great," that content is king. For most sites, it is appropriate to provide large amounts of information or entertainment and let the sales aspects of the site be more subtle.

The system described in Chapter 27, "Multipage Shopping Environment," is fairly obtrusive-users can't help but notice that each page has "funny information" tagged onto the end of the URL and that otherwise conventional pages are put up by a roundabout mechanism through a CGI script.

Users also notice that they are discouraged from using their browser's Back button too freely, and must be careful not to leave the site before completing their order.

Compare that environment with the fully integrated environments described below, in which the user is scarcely aware of entering or leaving the shopping area.

Online Examples

Although there are many examples of shopping cart scripts on the Web, the number of sites that have implemented a fully integrated shopping environment is relatively small for these reasons:

Hyperfuzzy

Figure 28.1 shows the welcoming sequence at Hyperfuzzy (http://www.hyperfuzzy.com/). Note that the server has inserted a long number into the path. This number is the only indication that the user has been assigned a unique ID. As these figures show, users can wander quite a while before finding anything for sale.

Figure 28.1 : Hyperfuzzy is a subtle sales environment.

Macmillan USA Information SuperLibrary

The Macmillan USA Information SuperLibrary is an integrated shopping environment of a very different sort. Located at http://www.mcp.com, it contains detailed information on the full catalog from the world's leading computer book publisher.

Like the Hyperfuzzy site, the overall look and feel of the Macmillan site doesn't suggest shopping. The only cue users have that they are being tracked is the unique ID embedded in their URL path. Unlike the Hyperfuzzy site, it is easy for users to find something to buy at the Macmillan site. Figures 28.2 through 28.6 show how easily users can find their way to the shopping area, even though most pages on the site are not about shopping.

Figure 28.2 : By the time users find something for sale, they are already "part of the family."

Design

Both the Hyperfuzzy and the Macmillan site use a modified server rather than Netscape cookies to assign a unique ID to the user.

The advantages of modifying a server include:

The disadvantages of this technique are

Figure 28.3 : Almost without realizing it, users slip into the shopping area.

The alternative technology, Netscape cookies, stores information on the client's hard drive. In the design shown in this chapter, only the ID is stored in the cookie. Other designs might store the entire shopping cart in the cookie and even set the expiration date for the cookie far in the future.

Such a design would allow users to start shopping, leave the site, even shut down their browser, and come back to the site hours or days later to resume shopping. Although this design is technically feasible, it is not representative of the shopping needs of most users and has more in common with the subject of Chapter 24, "User Profiles and Tracking."

Modifying servers is an expensive technique whose popularity is waning. The remainder of this chapter concentrates on the integrated shopping environment based on Netscape cookies.

Figure 28.4 : Macmillan USA Information SuperLibrary integrates content about computer books with an online bookstore.

Areas of Pure Content

Pages that do not need to know the order ID can be presented unmodified. Users can even leave the site, visit other sites, and return to continue shopping. It is customary with applications like this one to leave the expiration date on the Netscape cookie unset. In this way the cookie expires as soon as the user exits the browser.

If users are connected by a dial-up line and lose the connection, they can reconnect at any time before the shopping cart is deleted and complete their order. If you want to let users actually shut down and then return, the expiration date should be set a few hours into the future. If you do this, the final exchange with the user (when the cart is deleted) should also send a SetCookie that immediately expires the cookie.

Figure 28.5 : It's easy for users to find the bookstore.

Tip
Many time computations get bogged down rolling minutes over into hours, hours into days, and so forth. In UNIX, it's easy to do time computations when the time is in its internal format-seconds after the epoch. (The UNIX epoch started January 1, 1970 at midnight.) For example, to convert a time to a new time six hours in the future, just add 21,600 seconds. For use with cookies, the time must be converted to a specific format. Details of that format are given in Chapter 24, "User Profiles and Tracking."

Figure 28.6 : The bookstore environment emulates what users are used to.

The Shopping Area

When you use this design, the shopping area looks much like the multipage shopping environment described in Chapter 27, "Multipage Shopping Environment." The system no longer needs a single starting point. Instead, every page in the shopping area checks to see if the user already has an order ID. If the user does, that order ID is used. If the user does not, one is issued and sent to the user's browser with SetCookie.

The various cart update processes (putting a new item into the cart, presenting the cart for review, modifying the quantity of an item in the cart, and checking out) are essentially unchanged.

The new versions of these scripts get the order ID from the Netscape cookie rather than from the URL. No special provisions have to be made for image maps.

Moving Between Areas

When users enter the shopping area, they are issued an order ID that is good until they exit their browser. They can move anywhere on the Web without losing their cookie. They can return to the shopping area, put some items in their cart, leave again, and return as often as they like. When they are finally ready to review their order or check out, the shopping area pages retrieve their order ID from the cookie and use it to find their cart.

Code Specifics

This section shows the modifications made to the multipage shopping environment in Chapter 27, "Multipage Shopping Environment," to take advantage of Netscape cookies.

html.cgi

The major modification is in html.cgi. Recall that html.cgi is a very generic set of routines that put up HTML headers and trailers (including a simple error exit mechanism called die). When you modify html.cgi in the shopping area, every script in the system immediately has access to the Netscape cookie. Listing 28.1 shows the modified html.cgi


.

Listing 28.1  html.cgi-Modified to Read and Set the Netscape Cookie

#!/usr/bin/perl
#@(#)html.cgi    1.2
# Copyright 1995-1996 DSE, Inc.
# All Rights Reserved

# ===============================================================
# This subroutine takes a single input parameter and uses it as
# the <TITLE> and the first-level header. In addition, it reads
# and, if necessary sets, the orderID cookie. On exit, $orderID
# contains the visitor's current order ID.
# ===============================================================

sub html_header
{
  $document_title =$_[0];

  require "counter.cgi";

  $theCookie = $ENV{'HTTP_COOKIE'};
  if ($theCookie =~ /orderID/)
  {
    @cookies = split (/; /, $theCookie);
    foreach (@cookies)
    {
     ($name, $value) = split(/=/, $_);
     last if ($name eq 'orderID');
    }
    $orderID = $value;
    print "Content-type: text/html\n\n";
  }
  else
  {
    # modify the domain name to meet local requirements
    $theDomainName = ".dse.com";
    $orderID = &counter;
    print "Content-type: text/html\n";
    print "Set-Cookie: orderID=$orderID\; Domain=$theDomainName\;\n\n";
  }
  print "<HTML>\n";
  print "<HEAD>\n";
  print "<TITLE>$document_title</TITLE>\n";
  print "</HEAD>\n";
  print "<BODY BGCOLOR=\"#FFFFFF\" TEXT=\"#000000\" LINK=\"#CC0000\"
  VLINK=\"#663366\" ALINK=\"#333366\">\n";
  print "<H2>$document_title</H2>\n";
  print "<P>\n";

}

sub html_trailer
{
  print "</BODY>\n";
  print "</HTML>\n";
}
 
sub die
{
  print "Content-type: text/html\n\n";
  print "<HTML>\n";
  print "<HEAD>\n";
  print "<TITLE>Error</TITLE>\n";
  print "</HEAD>\n";
  print "<BODY BGCOLOR=\"#FFFFFF\" TEXT=\"#000000\" LINK=\"#CC0000\"
  VLINK=\"#663366\"> ALINK=\"#333366\">\n";
  print "<H2>An Error has occured</H2>\n";
  print @_;
  print "\n";
  print "</BODY>\n";
  print "</HTML>\n";

  die;
} 
1;

Tip
When installing this script, be sure to set the domain name to the local domain in subroutine html_header.

Before examining modifications in the other scripts, look at how html.cgi works. Use the tiny CGI script shown in Listing 28.2 to trace html_header's actions.


Listing 28.2  List282.pl-Demonstrates the Modified html.cgi

#!/usr/bin/perl

require "html.cgi";

&html_header("Test");
print "Now processing order $orderID.\n";
&html_trailer;
exit;

The script immediately calls html_header, which examines the HTTP_COOKIE environment variable. Recall that this environment variable holds a string delimited by ';' with every name/value pair that match this domain and this path, and that have not yet expired. (The tail-matching and path-matching mechanisms prevent a site from reading the cookies of another site.)

If the order ID string appears in our cookie, the script parses out the order ID number, sets it into the $orderID variable, and then proceeds as usual.

If the script does not find an order ID, it calls &counter to make one, then writes it out in the cookie and puts it into the $orderID variable.

When control returns to the calling program (such as the one in Listing 28.2), $orderID is defined and can be used just as it was in the multipage shopping environment in Chapter 27.

Scripts to Manage the User ID

Most of the ID management functions disappear into the browser. Those that remain are unchanged except for html.cgi.

catalog.cgi

Since catalog.cgi is designed to issue the transaction ID, it goes away completely in this version.

counter.cgi

counter.cgi is unchanged in this version. Instead of being called from catalog.cgi, it is called from html.cgi.

Caution
If the shopping area of the site is large and goes in several directories, point all of them to a single copy of counter.cgi and a single counter.txt file. Failure to do so can lead to more than one copy of the same order ID.

Handling Product Information

In the multipage shopping environment in Chapter 27, "Multipage Shopping Environment," the HTML shown below serves to put an item in the cart.

Use this code with GET to put one instance of the item in the cart:

<P>Here is a description of the product. It is a fine product.
</P>
<A HREF="/cgi-bin/ShoppingCart/update.cgi?$orderID+1+Item1+Fine%20Item+19.95">
Put Item 1 in my cart.</A>

Use this version with PUT when the shopper must be able to choose a style, size, or quantity:

<P>Here is a description of the product. It is a fine product.
</P>
<FORM METHOD=POST ACTION="/cgi-bin/ShoppingCart/update.cgi">
<INPUT TYPE=Hidden NAME=orderID VALUE=$orderID>
<INPUT TYPE=Text NAME=NewQuantity VALUE=1>Quantity<BR>
<INPUT TYPE=Hidden NAME=NewItemNumber VALUE=Item1>
<INPUT TYPE=Hidden NAME=NewItemDescription VALUE="Fine Item">
<INPUT TYPE=Radio NAME=NewItemStyle VALUE=Blue CHECKED>Blue<BR>
<INPUT TYPE=Radio NAME=NewItemStyle VALUE=Green>Green<BR>
<INPUT TYPE=Radio NAME=NewItemStyle VALUE=Red>Red<BR>
<INPUT TYPE=Radio NAME=NewItemSize VALUE=Small>Small<BR>
<INPUT TYPE=Radio NAME=NewItemSize VALUE=Medium CHECKED>Medium<BR>
<INPUT TYPE=Radio NAME=NewItemSize VALUE=Large>Large<BR>
<INPUT TYPE=Hidden NAME=NewPrice VALUE="19.95">
<INPUT TYPE=Submit VALUE=Order>
</FORM>

To put an item in the cart when Netscape cookies are in use, just omit the order ID.

Product pages no longer need be put up using page.cgi. The product page can be a static page (which still invokes update.cgi by GET or POST).

Listing 28.3 shows the modifications to update.cgi.

In Listings 28.3, 28.4, and 28.5, bold has been used to show additions to the Chapter 27 (Multipage Shopping Environment) version of the script, and strikethrough has been used to show deletions.


Listing 28.3  update.cgi-Two Simple Modifications to update.cgi Allow It to Function with Netscape Cookies

#!/usr/local/bin/perl
#@(#)update.cgi     1.6
# Copyright 1995-1996 DSE, Inc.
# All Rights Reserved

# html.cgi contains routines that allow us to speak HTTP
# AND handles the order ID
require "html.cgi";

$FORM{orderID} = $orderID;

# kart.cgi contains routines to open and close the shopping cart
require "kart.cgi";
require "install.inc";

# make sure arguments are passed using the POST method
if ($ENV{'REQUEST_METHOD'} eq 'POST' )
{
   read (STDIN, $buffer, $ENV{'CONTENT_LENGTH'});
   
   # Split the name-value pairs on '&'
   @pairs = split(/&/, $buffer);
   foreach $pair (@pairs)
   {
     ($name, $value) = split(/=/, $pair);
     $value =~ tr/+/ /;
     $value =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg;
     $FORM{$name} = $value;
   }
}
elsif ($ENV{'REQUEST_METHOD'} eq 'GET' )
{
  # Get the query in phrase1+phrase2 format from the QUERY_STRING
  # environment variable.
  $query = $ENV{'QUERY_STRING'};

  #if the query string is null, then there is no data appended to the URL.

  if ($query !~ /\w/)
  {
    # No argument
    &die('Error: No product data found.');
   }
   else
   {
        $query =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg;
        ($FORM{'orderID'}, $FORM{'NewQuantity'}, $FORM{'NewItemNumber'}, 
          $FORM{'NewItemDescription'}, $FORM{'NewPrice'}, $FORM{'nextPage'}) =
               split('\+',$query);

     # the GET version doesn't use Style and Size
     $FORM{'NewItemStyle'} = "None";
     $FORM{'NewItemSize'} = "None";
    }
}
else
{
  &die "Not started via POST or GET; REQUEST_METHOD is $REQUEST_METHOD \n";
}

# and here is where we do the work
if ($success == &openCart ($FORM{'orderID'}))
{
  workingItemNumber = $FORM{'NewItemNumber'} ."\t". $FORM{'NewItemStyle'} ."\t". 
     $FORM{'NewItemSize'};
     
  # do the update
  $cart{$workingItemNumber} = 
          ($FORM{'NewItemStyle'} . "\t" . $FORM{'NewItemSize'} . "\t" . 
          $FORM{'NewQuantity'} . "\t" . $FORM{'NewItemDescription'} . "\t" . 
          $FORM{'NewPrice'} );

  # and write it out
  $result = &closeCart($FORM{'orderID'});
  if ($success == $result)
  {
     if ($FORM{'nextPage'} !~ /^$/)
          {
       print "Location: $upcgipath?$FORM{'orderID'}+$FORM{'nextPage'}\n\n";
          }
     else
     {
       print "Location: $ENV{HTTP_REFERER}\n\n";
     }
   }
   else
   {
     &html_header('Error');
          print "Could not close cart file.\n";
     &html_trailer;
    }
}
else
{
     &html_header('Error');
        print "Could not access cart file.\n";
     &html_trailer;
}

Listing 28.4 shows the change needed to make orderForm.cgi work in the integrated environment with Netscape cookies. Instead of reading the order ID from $arg[0], the script now allows it to flow out of html_header.


Listing 28.4  orderForm.cgi-With a Single Deleted Line, orderForm.cgi Is Ready to Work with Cookies

#!/usr/local/bin/perl
#@(#)orderForm.cgi     1.8
# Copyright 1995, 1996 DSE, Inc. 
# All rights reserved

#html.cgi contains routines that allow us to speak HTML.
require "html.cgi";
require "kart.cgi";
require "install.inc";

$orderID = $ARGV[0];
 
if ($ENV{'REQUEST_METHOD'} eq 'POST')
{
  # Using POST, so data is on standard in
  read (STDIN, $buffer, $ENV{'CONTENT_LENGTH'});
  
  # Split the fields using '&'
  @pairs = split(/&/, $buffer);
  
  # Now split the pairs into an associative array
  foreach $pair (@pairs)
  {
    ($name, $value) = split(/=/, $pair);
    $value =~ tr/+/ /;
    $value =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg;
    $FORM{$name} = $value;
  }
  
  # Check for illegal metacharacters.
  if ($FORM{email} !~ /^$/ && $FORM{email} !~ /^[a-zA-Z0-9_\-+ \t\/@%.]+$/)
  {
    &html_header("Illegal Characters");
    print "Your e-mail address contains illegal \n";
    print "characters. Please notify \n";
    print "the webmaster: \n";
    print "<A HREF=\"mailto:$webmaster\">$webmaster</A>\n";
    &html_trailer;
  }
  else
  {
   # make sure required data is entered
   if (($FORM{name} !~ /^$/) && 
      (($FORM{address1} !~ /^$/) ||($FORM{address2} !~ /^$/)) &&
      ($FORM{city} !~ /^$/) &&
      ($FORM{zip} !~ /^$/) && ($FORM{country} !~ /^$/) &&
      ($FORM{state} !~ /^$/) && 
      (($FORM{workphone} !~ /^$/) ||($FORM{homephone} !~ /^$/)) && 
      ($FORM{payment} !~ /^$/) )
   {
     # start the HTML stream back to the server
     &html_header("Order Form");
     print "<FORM METHOD=\"POST\" ACTION=\"/cgi-bin/ShoppingCart/
     checkout.cgi?$orderID\">\n";
     print "Name: $FORM{name}<BR>\n\n";
     print "Address1: $FORM{address1}<BR>\n";
     print "Address2: $FORM{address2}<BR>\n";
     print "City: $FORM{city}<BR>\n";
     print "State: $FORM{state}<BR>\n";
     print "Zip: $FORM{zip}<BR>\n";
     print "Country: $FORM{country}<BR>\n";
     print "Work Phone: $FORM{workphone}<BR>\n";
     print "Home Phone: $FORM{homephone}<BR>\n";
     print "Payment Method: $FORM{payment}<BR>\n";
     print "<INPUT TYPE=Hidden NAME=name VALUE=\"$FORM{name}\">";
     print "<INPUT TYPE=Hidden NAME=address1 VALUE=\"$FORM{address1}\">";
     print "<INPUT TYPE=Hidden NAME=address2 VALUE=\"$FORM{address2}\">";
     print "<INPUT TYPE=Hidden NAME=city VALUE=\"$FORM{city}\">";
     print "<INPUT TYPE=Hidden NAME=state VALUE=\"$FORM{state}\">";
     print "<INPUT TYPE=Hidden NAME=zip VALUE=\"$FORM{zip}\">";
     print "<INPUT TYPE=Hidden NAME=country VALUE=\"$FORM{country}\">";
     print "<INPUT TYPE=Hidden NAME=workphone VALUE=\"$FORM{workphone}\">";
     print "<INPUT TYPE=Hidden NAME=homephone VALUE=\"$FORM{homephone}\">";
     $theTypeOfPaymentChosen = $FORM{payment};
     print "<INPUT TYPE=Hidden NAME=payment VALUE=\"$theTypeOfPaymentChosen\">\n";
     print "<INPUT Type=Hidden Name=ups Value=\"$UPS\">\n";
    
     if ($success == &openCart ($orderID))
     {  
       if (%cart == 0)
       {
         print "<HR><STRONG>But your cart is empty!</STRONG><BR>\n";
         print "Go back to the <A HREF=\"$catalog?$orderID\">main page</A>.<HR>\n";
       }
       print "<PRE>\n";
       print "Qty    Item    Description   Style      Size      Each\n";
       $subtotal = 0;
       while (($workingItemID, $rest) = each(%cart))
       { 
         ($itemID, $junk) = split('\t', $workingItemID);
         ($style, $size, $quantity, $itemDescription, $price) = split('\t', $rest);
         $lineTotal = sprintf ("\$%4.2f", $quantity * $price);
         $cquantity = &commas($quantity);
         $fprice = sprintf("\$%4.2f", $price);
         $cprice = &commas($fprice);
         $clineTotal = &commas($lineTotal);
         printf (" %-5s %-7s %-10s %-10s %5s %10s<BR>\n",
         $quantity, $itemID, $itemDescription, $style, $size, $cprice, $clineTotal);
         $subtotal += $price * $quantity;
       } 
        
     # put any standard text here
     # for example
       $fsubtotal = sprintf("\$%4.2f", $subtotal);
       $csubtotal = &commas($fsubtotal);
       print "Subtotal is $csubtotal<BR>\n";
       $ShippingAndHandling = &ShippingAndHandling;
       printf ("Shipping and Handling: \$%5.2f<BR>\n",$ShippingAndHandling);
       $Insurance = &Insurance;
       printf ("Insurance: \$%5.2f<BR>\n", $Insurance);
       $UPS = $FORM{ups};
if ($UPS eq 'ups') 
       { 
         $Rush = &Rush;
         printf ("Rush: \$%5.2f<BR>\n", $Rush);
       }
       else
       {
         $Rush = 0;
       }
       if ($theTypeOfPaymentChosen eq 'COD')
       {
         $COD = &COD;
         printf ("COD: \$%5.2f<BR>\n", $COD);
       }
       else
       {
         $COD = 0;
       }
       $Gtotal = $subtotal + $Insurance + $ShippingAndHandling + $Rush + $COD;
       $ftotal = sprintf("\$%5.2f", $Gtotal);
       $State = $FORM{state};
       $State =~ tr/a-z/A-Z/;
       if ($State eq $TaxStateShort)
       {
         print "<B>Total before Tax is $ftotal</B>\n";
         $tax = $subtotal * $SalesTax;
         $ftax = sprintf("\$%5.2f", $tax);
         $totalWithTax = $tax + $Gtotal;
         $ftotalWithTax = sprintf("\$%5.2f", $totalWithTax);
         $fSalesTax = sprintf ("%0.2f", 100 * $SalesTax);
         print "The state of $TaxStateLong, adds $ftax
         $LocalNameOfTax.<BR>(Tax rate is $fSalesTax percent.)<BR>\n";
         print "<STRONG>Your total with tax is $ftotalWithTax</STRONG>\n";
       }
       else
       {
         print "<B>Total is $ftotal</B>\n";
       }  
      
       # modify the following as payment options change.
       if ($theTypeOfPaymentChosen eq 'Mail')
       {
         print "Please print off this order form and mail it with your
                       payment.<BR>\n";
         print "<INPUT TYPE=submit VALUE=\"Tell Us Your Order Is Coming\">\n"; 
         print "<INPUT TYPE=reset VALUE=Clear><BR>\n";
       }
       elsif ($theTypeOfPaymentChosen eq 'Phone')
       {
         print "Please print off order form and have it handy when
         you phone in your order.<BR>\n";
         print "<INPUT TYPE=submit VALUE=\"Tell Us Your Order Is Coming\">\n"; 
         print "<INPUT TYPE=reset VALUE=Clear><BR>\n";
       }
       elsif ($theTypeOfPaymentChosen eq 'COD')
       {
         print "Please print off order form and keep it for your record.<BR>\n";
         print "<INPUT TYPE=submit VALUE=\"Send Order\">\n"; 
         print "<INPUT TYPE=reset VALUE=Clear><BR>\n";
       }
       elsif ($theTypeOfPaymentChosen eq 'CreditCard')
       {
         print "<INPUT Type=Radio Name=card Value=MC CHECKED>Master Card";
         print "<INPUT Type=Radio Name=card Value=visa>Visa";
         print "<INPUT Type=Radio Name=card Value=discover>Discover<BR>\n";
         print "Card Number:<BR><input Type=Text name=number size=44><BR>\n";
         print "Expiration Date:<BR><input Type=Text name=expiration size=44><BR>\n";
         print "<INPUT TYPE=submit VALUE=\"Send Order\">\n"; 
         print "<INPUT TYPE=reset VALUE=Clear><BR>\n";
       }
       else
       {
         print "Unknown payment method.
         Please notify webmaster:<A HREF=\"mailto: $webmaster\">
           $webmaster";
         print "<INPUT TYPE=submit VALUE=\"Send Order\">\n"; 
         print "<INPUT TYPE=reset VALUE=Clear><BR>\n";
       }
    }
    else
    {
       &die("System error: cannot open cart.\n");
    }
  &html_trailer;
  }
  else
  {
    &html_header("Required data missing.");
    print "One or more of the required fields is blank.
    Please back up and complete the form.\n";
    &html_trailer;
  }
 }
} # end if 'if METHOD==POST'
else
{
  &html_header("Error");
  print "Not started via POST\n";
  &html_trailer;
}

sub commas
{
  local($_) = @_;
  1 while s/(.*\d)(\d\d\d)/$1,$2/;
  $_;
}

sub ShippingAndHandling
{
  # modify this subroutine to satisfy local S&H requirements
if ($subtotal < 125)
  {
     $sh = 7.95;
  }
  elsif ($subtotal < 225)
  {
     $sh = 8.95;
  }
  else
  {
     $sh = 9.95;
  }
$sh;
} 

sub Insurance
{
  # modify this subroutine to satisfy local insurance requirements
  $subtotalInCents = $subtotal * 100;
  $wholeHundreds = $subtotalInCents - ($subtotalInCents % 10000);
  $fractionalHundreds = $subtotalInCents % 10000;
  if ($fractionalHundreds != 0)
  {
     $wholeHundreds += 10000;
  }
  $insurance = (.0050 * $wholeHundreds)/100;
$insurance;
}

sub Rush
{
  # modify this subroutine to satisfy local rush shipping requirements
  if ($UPS eq 'ups')
  {
    $Rush = 4.00;
  }
$Rush;
}
        
sub COD
{
  # modify this subroutine to satisfy local COD shipping requirements
  if ($theTypeOfPaymentChosen eq 'COD')
  {
    $COD = 4.75;
  }
$COD;
}

Building the Cart on Disk

The kart.cgi routines work exactly as they did in the multipage shopping environment in Chapter 27.

inkart.cgi needs more extensive modification but it is all simplification. Listing 28.5 shows the modified inkart.cgi.


Listing 28.5  inkart.cgi-Simplified to Take Advantage of Netscape Cookies

#!/usr/local/bin/perl
#@(#)inKart.cgi     1.4
# Copyright 1995, 1996 DSE, Inc. 
# All rights reserved

require "install.inc";

# orderForm.cgi is used for Insurance, S&H, and other special charges
require "orderForm.cgi";

# html.cgi contains routines that allow us to speak HTML
require "html.cgi";

# kart.cgi contains routines to open and close the shopping cart
require "kart.cgi";

# make sure arguments are passed using the GET method
if ($ENV{'REQUEST_METHOD'} eq 'GET' )
{
  # Get the query in phrase1+phrase2 format from the QUERY_STRING
  # environment variable.

  $query = $ENV{'QUERY_STRING'};

  #if the query string is null, then there is no search phrase
  # appended to the URL.

  if ($query !~ /\w/)
  {
    # No argument
    &html_header('Empty OrderID string.');
    &html_trailer;
   }
   else
   {
     $query =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg;
     ($orderID)  = split(/\+/, $query);

     # here is where we do the work
     if ($success == &openCart ($orderID))
     {
        $count = @cart;
        &html_header('Your order');

print "Now processing order number $orderID\n";
        print ".<BR>\n";

       # find the length of the cart array
       $numberOfItems = %cart;

       if ($numberOfItems == 0)
       {
         print "There are no items in your shopping cart.\n";
       }
       else
       {
          print "<PRE>\n";
                     print "Qty Item       Description
                     Style      Size           Each      Total\n";
         print "<FORM ACTION=\"multiupdate.cgi?$orderID\" METHOD=\"POST\"><BR>\n";
         $subtotal = 0;
         while (($workingItemID, $rest) = each(%cart))
         {
           ($itemID, $junk) = split('\t', $workingItemID);
           ($style, $size, $quantity, $itemDescription, $price) = split('\t', $rest);
           $lineTotal = sprintf ("\$%4.2f", $quantity * $price);
           $cquantity = &commas($quantity);
           $fprice = sprintf("\$%4.2f", $price);
           $cprice = &commas($fprice);
           $clineTotal = &commas($lineTotal);
        print "<INPUT TYPE=text NAME=\"$workingItemID\" VALUE=$cquantity SIZE=2>";      
        printf (" %-10s %-30s %-10s %-10s %10s %10s<BR>\n",
           $itemID, $itemDescription, $style, $size, $cprice, $clineTotal);
           $subtotal += $price * $quantity;
         }


          # put any standard text here
          # for example
          $fsubtotal = sprintf("\$%4.2f", $subtotal);
          $csubtotal = &commas($fsubtotal);
          
          print "Subtotal is $csubtotal<BR>\n";
          $ShippingAndHandling = &ShippingAndHandling;
          printf ("Shipping and Handling: \$%5.2f<BR>\n",$ShippingAndHandling);
          $Insurance = &Insurance;
          printf ("Insurance: \$%5.2f<BR>\n", $Insurance);
          $Gtotal = $subtotal + $Insurance + $ShippingAndHandling;
          $ftotal = sprintf("\$%5.2f", $Gtotal);
          print "<B>Total without Tax is $ftotal</B>\n";
          print "<P>\n";
          $tax = $subtotal * $SalesTax;
          $ftax = sprintf("\$%5.2f", $tax);
          $fSalesTax = sprintf ("%0.2f", 100 * $SalesTax);
          print "If you live in the state of $TaxStateLong, we will add
          $ftax  $LocalNameOfTax.<BR>(Tax rate is $fSalesTax
          percent.)<BR>\n";
          print "</P>\n";
          print "</PRE>\n";
          print "<H3>Please Note</H3>\n";
          print "<P>If payment is by C.O.D there will be a charge of
          \$4.75 added to your bill.</P>\n";
          print "<P>Two day UPS Rush delivery is available at a cost of \$4.00.</    P>\n";
          print "</P>\n";

          # put up the buttons
          print "<INPUT TYPE=\"submit\" VALUE=\"Update\">\n";
          print "<INPUT TYPE=\"reset\" VALUE=\"Clear Form\">\n";
          print "</FORM>\n";
          print "<A HREF=$catalog?$orderID>Return to Catalog</A> |\n";
          print "<A HREF=$outputPage?$orderID
          +$pageDirectory/orderform.html>Check out</A><BR>\n";
       print "Use your browser\'s BACK button to return to the previous page.<BR>\n";
        }

        print "<HR>\n";
        &html_trailer;
      }
   }
}
else
{
  &html_header('Error');
  print "Not started via GET\n";
  &html_trailer;
}

multiupdate.cgi and checkout.cgi are modified by removing the single line

$orderID = $ARGV[0];

just as for orderform.cgi.

Online shopping is one of the fastest growth applications on the Web. Integrated shopping environments combine the tangible returns of shopping with the traffic-drawing benefits of high content. As Netscape cookies are adopted by more and more browsers, the ability to set up a fully integrated shopping environment becomes affordable for even modest-sized sites.

Not only must the shopping site be designed, but your clients must also decide how they will accept payment. Up to a point, the more options the user has, the higher the return will be on the site. Chapter 25, "Getting Paid: Taking Orders over the Internet," shows how to arrange for the money to transfer from the user to your client.