Implementing a Web Shopping Cart
Online transactions in Perl
Chris Baron and Bob Weil
Chris Baron is director of programming and Bob Weil is a partner at The Hypertising Network, creating next-generation, marketing-driven websites. Bob can be reached at [email protected], Chris at [email protected].
As the World Wide Web makes the transition from an academic communication channel to a commercial medium, the demand for new ways to conduct transactions increases. The rich graphics and hypertext environment of the Web make the presentation of products and services easy and efficient. Coupled with built-in support for fill-in forms and platform-independent client-server communications, you'd think the Web would be the ideal commerce channel. However, the nature of the Web and HTTP is not well suited to the type of transactions required for online shopping, particularly the virtual "shopping cart" metaphor often seen on Web sites today. In this article, we'll examine the components of an online catalog, focusing on a virtual shopping-cart system and some ways around the shortcomings of HTTP.
What's in the Cart?
The shopping-cart interface is only one part of an online catalog and ordering system. In general terms, an online catalog consists of the following:
1. A database of text and graphics describing the items in the catalog.
2. An efficient mechanism to display the items on demand.
3. A process to collect shipping and payment information.
4. A method of tracking items the user wishes to purchase.
5. A way to identify the user, and a way to group a series of HTTP requests into a single "session."
The Web and HTTP are very good at the first two points. Most items can be sufficiently displayed with HTML, embedded graphics, and hypertext links. For a large number of items, a database of text and graphics should be created and the pages generated programmatically on-the-fly to reduce storage and maintenance requirements. The collection of shipping and payment data is also easily implemented over the Web by using <FORM> and related tags.
Points four and five are not well supported by the HTTP protocol due to its stateless and connectionless nature (see "Hypertext Transfer Protocol-HTTP/1.0 Specification," Internet-Draft 05, Internet Engineering Task Force (IETF), February 19, 1996). A Web page consisting of HTML-formatted ASCII text and several graphics is transferred as a series of discrete request/response transactions between the browser and the HTTP server rather than as a continuous connection. However, neither the client nor server maintain any knowledge of the state of the previous or other connections. This provides an efficient method for servers to handle hundreds or thousands of simultaneous requests. When there is a need to maintain state information over extended periods (as in the current case), additional data must be stored somehow: either in the browser, server, or both. Several methods for transmitting state data over the Web have been developed. The three we'll discuss are of the following:
- The type=hidden attribute of HTML forms.
- The Netscape "cookie" method (in which a "cookie" containing state data is passed along in the HTTP request-response dialog).
- Using a database to store the current state on the server.
Listing One is an online-catalog system called "Minishop" implemented in Perl. For maximum compatibility, we used Perl 4 and tried to make the code clear and accessible by avoiding opaque Perl-isms. The source code for Minishop is available electronically from DDJ and from our site at http://www.egrafx.com/ minishop/.
A single script generates all the HTML pages for the shopping system. When called with no parameters, as in an initial call, it produces a catalog index page listing all of the items in the database (Figure 1). The catalog data is stored in a Perl associative array with the part number as the key. The data is a colon-separated string containing the price and other data about the item. A production system would also include descriptive text about each item and references to one or more graphic images or sound files.
Selecting an item name displays the detailed catalog page for the item using the &show_item_page subroutine. Buttons are provided to show the current contents of the shopping cart, empty it, or go to the checkout page. Figure 2 shows a detailed catalog page. In a production system, large numbers of similar pages could be created in real time using a template. During the real-time generation of the page, the item name and other descriptive data are inserted into the template. Several of the systems listed at the end of the article use this method to generate their catalog pages, thus eliminating the need for many similar but slightly different HTML pages.
The current contents of the shopping cart are displayed by calling the &show_cart subroutine, which generates the display in Figure 3.
Finally, when users are ready to purchase the selected items, they are shown the page generated by the &check_out subroutine. This page allows users to review the items selected, and calculates the total bill along with tax and shipping charges. Address and payment information are also collected on this form. In many current systems, the specifics of the user's location and desired shipping mode would be collected first to allow proper calculation of sales tax and shipping charges. In our simple system, we've combined these steps into the single page in Figure 4.
Basic systems simply collect the data from the form and send the order information to the sales department via e-mail or fax without validation, perhaps encrypted for security. Advanced systems minimize the man-hours spent on each order by automatically validating credit-card data, initiating production activities, and updating inventory databases.
I'm Hidden (But You Can Still See Me)
The first of three methods we'll use to store the contents of the shopping cart is a type=hidden attribute of an HTML <INPUT> field. Example 1 shows how to store data in this way. The $cart_out string is created by the &maintain_state subroutine and contains the contents of the shopping cart as colon-delimited fields consisting of the part number and the quantity. When the script is called, it reads any data passed in the cartdata field. To pass the data between pages, we use a hypertext link generated for each catalog entry containing the catalog data. Both methods are necessary on this particular page because the script may be called by clicking either the catalog hypertext links or one of the submit buttons on the form. While either method could have been used alone, we preferred GUI buttons rendered by the HTML form routines rather than text or graphic hyperlinks. This routine also reveals a little trick for automatically submitting the form when one of several buttons is clicked. Multiple buttons of type=SUBMIT are created with name="action". To find out which button was pressed, we simply read the value field of the button in our script.
The advantages of the hidden-field method are ease of implementation and minimal server requirements. Only the static product databases and the single CGI script need to be stored on the server. While easy to implement, this method has some problems. The back and forward buttons on most browsers will retrieve a cached page, but will not reinvoke the script. For example, if a user adds an item to the shopping cart and then returns to the main catalog page using the browser's back button, the newly added item will be lost because the previous page did not include the new data and the form was not submitted to pass the data back to the script. Additionally, if users jump to an external page and then return via a hypertext link or bookmark, all their data will be lost because the state information is stored in the form, not in the URL. Therefore, this method is most suitable for simple applications or for passing short-term data between pages.
Gimme Cookie!
Netscape has implemented an elegant method of passing and storing state information called HTTP "cookies." The official cookie spec (see "Persistent Client State HTTP Cookies," Preliminary Specification, Netscape Communications Corporation, 1996) describes it as follows:
Cookies are a general mechanism which server side connections (such as CGI scripts) can use to both store and retrieve information on the client side of the connection. The addition of a simple, persistent, client-side state significantly extends the capabilities of Web-based client/server applications.
This method has been adopted by the Internet Engineering Task Force (IETF) as a draft specification for maintaining HTTP state information. Cookies work by passing data in the HTTP request and response headers. The client stores cookies in a special file and sends them to the server with HTTP requests that match certain criteria. Netscape Navigator, Microsoft Internet Explorer, Netcom Netcruiser, and several other web browsers currently support cookies. (For more information, see http:// www.research.digital.com/nsl/formtest/ stats-by-test/NetscapeCookie.html.) Example 2 is a typical cookie dialog.
Thus, CGI scripts can generate the cookies and embed them into the HTTP header. When the browser requests a page from the CGI URL, the browser passes along any cookies it previously received from that server. These are stored in the environment variable $HTTP_COOKIE and can be read by the script just like other CGI parameters passed by the Web server. Listing Two shows the modifications necessary to our shopping-cart system to use cookies to store and pass the cart data. You should substitute the routines in Listing Two for their equivalents in Listing One. Also delete the line <INPUTNAME="cartdata" TYPE="HIDDEN" VALUE="$cart_out"> from the show_cart, show_item_ page, show_ cart, and check_ out routines.
Cookies overcome nearly all the shortcomings of the hidden-form field method and are just as easy to implement. They provide superior security because cookies are only sent to URLs matching the cookie's criteria. This reduces the chances of spoofing and inadvertent transmission of sensitive data. Users do not normally see the cookies, and listing the source of an HTML form will not reveal their contents. Persistence is improved over the hidden-field method since the cookies are stored in a file on the client machine. They will survive any amount of Web surfing and even shutting down the client. The expiration-date parameter provides fine-grain control over how long the cookie is valid and provides automatic deletion when it expires. This can significantly reduce the effort required to track and expire old data, which is often needed with a system that uses databases to store the state. A minor shortcoming of cookies is the limited amount of data they can contain: specified as 300 total cookies, 20 cookies per domain, and 4 KB total per cookie (name+data). A more serious shortcoming is the limited set of browsers that support the specification. Although the proportion of users with a supporting browser is over 80 percent, shopping-cart systems that must support all browsers will have to use a different method until the specification is more universally supported.
Databases: Tried and True
When considering how to store state information for a shopping cart or other application, some kind of database is what first comes to mind. As we've discussed, HTTP does not provide the continuous connection most developers are used to. Simply using a database to store the state information is not enough. We still need to be able to link the HTTP request with the data stored in the database. Example 3 and Listing Three show the modifications needed to run our system using a database to store the state. To modify Minishop2.cgi to use a DBM database, add Example 3 near the top of the file, then substitute the routines in Listing Three for their equivalents. This version of Minishop is a hybrid, using a cookie to store a unique UserId identifier as a key to access the data stored in the database. We use standard DBM files because of their simple setup, and Perl's built-in support. Perl accesses DBM files as an associative array, providing a very simple interface to the database. We have two such databases in Minishop. %id_base holds the information for generating the next UserId cookie. In our case, this is a simple integer that increments for each new user. In a more complex system, we could use more-complicated methods of generating user ids and store more data about the user (perhaps in encrypted form). The %cart_db database holds the actual shopping-cart contents in the same colon-delimited form we used for the form data and the cookie data.
With a bit of extra work to pass the data needed to identify the user, database-management systems can be used very effectively to store data in Web applications. The disadvantage is the sometimes considerable overhead and server load of a database system, which may be overkill for a simple application.
Advanced Topics
A number of additional issues should be considered when implementing an online shopping system. Security is always a concern, and the architecture of your system should be designed with security in mind. The browser-to-server transactions can be encrypted with the Secure Socket Layer (SSL) system used by Netscape servers and others. Data stored on the server should be encrypted using systems such as PGP, and orders transmitted via e-mail or FTP should also be encrypted. The Web is designed to be open and accessible to all, so locations of files, permissions, and the data stored or transmitted across the Web should be carefully considered.
After several years of anticipation, methods of conducting secure financial transactions are starting to emerge. Online validation of credit-card charges, digital cash, and automatic-checking debits are a reality.
Web sales systems should be integrated with existing methods (such as telephone and mail-ordering) to link with sales, inventory management, and manufacturing systems.
Emerging technologies, such as Java, may supersede much of the functionality discussed in this article. However, at this point, only a single commercial browser supports Java. The current need for reliable, platform-independent, client-server applications demands the use of universally supported protocols such as HTTP and CGI.
Example 1: Storing data.
sub show_cat { &print_type; # the content-type: line &maintain_state; # this generates the state information print &HtmlTop("The Minishop CD-ROM Catalog"); print "<P>This catalog uses the type=HIDDEN form method of \n"; print "maintaining state information between pages."; print "<P>Please click on an item to view more detail or to order it.\n"; print "<P><CENTER><TABLE BORDER>\n"; print "<TR><TH>Current CDs In Stock</TH></TR>\n"; # sort for display foreach $item (sort keys(%catalog)) { ($name, $price, $num_cd, $wt) = split(':',$catalog{$item}); print qq(<TR><TD>); print qq(<A HREF="$self?action=showitem&page=$item&cartdata=$cart_out">); print "$name</A></TD></TR>\n"; } print "</TABLE></CENTER>\n"; print qq(<P><CENTER><FORM ACTION="$self" METHOD="POST">); print qq(<INPUT NAME="action" TYPE="SUBMIT" VALUE="Show Cart" ALIGN=left>\n); print qq(<INPUT NAME="action" TYPE="SUBMIT" VALUE="Empty Cart" ALIGN=center>\n); print qq(<INPUT NAME="action" TYPE="SUBMIT" VALUE="Check Out" ALIGN=right>\n); print qq(<INPUT NAME="cartdata" TYPE="HIDDEN" VALUE="$cart_out">); # print &PrintVariables(%form); ######### debugging print "</FORM></CENTER></BODY></HTML>\n"; }
Example 2: Typical cookie dialog. (a) What the server sends; (b) whenever the client requests a URL from this server, it sends in the HTTP request.
(a) Content-Type: text/html Set-Cookie: CUSTOMER=DR_DOBBS; expires=Wednesday, 09-Nov-99 23:12:40 GMT >blank line to indicate end of header< <HTML> (b) Cookie: CUSTOMER=DR_DOBBS; ...more cookies...
Example 3: Modifying Minishop2.cgi.
# utility arrays for dates ('Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'); (Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec);
Figure 1: Minishop catalog display.
Figure 2: Minishop catalog page.
Figure 3: Minishop shopping-cart display.
Figure 4: Minishop checkout page.
Listing One
#!/usr/bin/perl
#
# minishop1.cgi
# A simple shopping cart CGI script written by Chris Baron
# for Dr. Dobb's Journal. This code may be freely used and modified. If
# you use Minishop as a starting point for anything please reference where
# you got it. I'd appreciate a copy of the script.
# Comments or questions? Send me email at [email protected]
# This script uses the HIDDEN attribute for a text field to store the data in
# the shopping cart. This data is updated each time the user
# executes the script.
# This script uses the cgi-lib.pl v2.4 utility module
# by Steven Brenner ([email protected])
# http://www.bio.cam.ac.uk/cgi-lib/
# tells Perl to use the cgi-lib.pl module
require 'cgi-lib.pl'; # in same directory as the script or in @INC
# our script name
$self = $ENV{'SCRIPT_NAME'};
# initialize the catalog. For a real system you would want to store in
# a database for ease of maintenance and flexibility to add and delete
# new products
#
# the format of the catalog is --
# part#, title:price:num-disks:weight
%catalog = (
'cd001', 'Algorithms and Data Structures:59.95:1:0.1',
'cd002', 'WWW Toolkit:29.95:1:.1',
'cd003', 'Graphics Programming Books:69.95:1:0.1',
'cd004', q/Dr. Dobb's on CD-ROM:79.95:2:0.2/,
'cd005', '386BSD Reference:99.00:1:0.1',
'cd006', 'Alternative Languages:49.95:1:0.1');
# MAIN routine
MAIN:
{
# Read in all the variables set by the form
if (&ReadParse(*form)) { # if true there were parameters
$to_do = $form{'action'}; # fetch the command
}
else { # called with no parameters i.e. initial call
$to_do = 'display'; #just show the catalog
}
# load the cart data into an array for use if any
&get_state;
# the Perl equivalent of a switch statement to display the proper page based
# on which submit button was pressed or action parameter was passed
switch: {
if (($to_do eq 'display') || ($to_do eq 'Return')) {
&show_cat; last switch; }
if ($to_do eq 'showitem') { &show_item_page($form{'page'}); last switch; }
if ($to_do eq 'Show Cart') { &show_cart; last switch; }
if ($to_do eq 'Buy/Update') { &add_item($form{'item'}, $form{'num'});
&show_cart; last switch; }
if ($to_do eq 'Check Out') { &check_out; last switch; }
if ($to_do eq 'Empty Cart') { &empty_cart; &show_cat; last switch; }
&CgiError('Boo Boo', 'Bad Command none matched', &PrintVariables(%form));
}
} # end of MAIN
##################### State Subroutines #######################
# get_state. here is where we fetch the state data from the hidden field
# and load it into an associative array for use
sub get_state {
%cart_data = split(":", $form{'cartdata'});
}
# maintain_state. Here is where the hidden cart data is stored into the form
# the hidden type creates a string for use in an input field with a
# tag like <INPUT TYPE="HIDDEN" NAME="cartdata" VALUE="$cart_out">
sub maintain_state {
if (%cart_data) {
$cart_out = join(":", %cart_data);
}
}
# add_item. add an item to the shopping cart the state consists of an array
# of part numbers with a non-zero quantity a quantity of zero means delete it
sub add_item {
local ($item, $num) = @_;
if ($num == 0) { delete $cart_data{$item} }
else { $cart_data{$item} = $num; } #this does a simple overwrite
}
# empty_cart. empty out the cart by simply undefing the %cart_data array
sub empty_cart {
undef %cart_data;
}
# print_type. Prints out the magic "Content-Type: text/html" line
sub print_type {
print "Content-Type: text/html\n\n";
}
##################### Display Subroutines ######################
# show_cat. show the catalog index page
sub show_cat {
&print_type; # the content-type: line
&maintain_state; # this generates the state information
print &HtmlTop("Minishop On-Line CD-ROM Catalog");
print "<P>This catalog uses the type=HIDDEN form method of \n";
print "maintaining state information between pages.";
print "<P>Please click on an item to view more detail or to order it.\n";
print "<P><CENTER><TABLE BORDER>\n";
print "<TR><TH>Current CDs In Stock</TH></TR>\n";
# sort for display foreach $item (sort keys(%catalog)) {
($name, $price, $num_cd, $wt) = split(':',$catalog{$item});
print qq(<TR><TD>);
print qq(<A HREF="$self?action=showitem&page=$item&cartdata= $cart_out">);
print "$name</A></TD></TR>\n";
}
print "</TABLE></CENTER>\n";
print qq(<P><CENTER><FORM ACTION="$self" METHOD="POST">);
print qq(<INPUT NAME="action" TYPE="SUBMIT"
VALUE="Show Cart" ALIGN=left>\n);
print qq(<INPUT NAME="action" TYPE="SUBMIT"
VALUE="Empty Cart" ALIGN=center>\n);
print qq(<INPUT NAME="action" TYPE="SUBMIT"
VALUE="Check Out" ALIGN=right>\n);
print qq(<INPUT NAME="cartdata" TYPE="HIDDEN" VALUE="$cart_out">);
# print &PrintVariables(%form); ######### debugging
print "</FORM></CENTER></BODY></HTML>\n";
}
# show_item_page. show the page for an individual item
sub show_item_page {
local($item) = pop(@_);
($name, $price, $num_cd, $wt) = split(':',$catalog{$item});
&print_type; # the content-type:.line
&maintain_state; # this generates the state information
print &HtmlTop("Catalog Page for $name");
print "<P>Here is where you would place the picture and ";
print "description of this item";
print "you would want to store the description in a database ";
print "along with a reference to the graphic image etc.\n";
print qq(<P><FORM ACTION="$self" METHOD="POST">);
print qq(<CENTER><TABLE BORDER WIDTH="100%">\n);
print "<TR><TH></TH><TH>Item</TH>";
print "<TH>No. CDs</TH><TH>Price</TH></TR>\n";
$val = ($cart_data{$item}) || 1;
print qq(<TR><TD><INPUT NAME="num" TYPE="TEXT" VALUE="$val");
print qq( COLS=2 SIZE="5"></TD>\n);
print "<TD>$name</TD><TD>$num_cd</TD><TD>$price</TD>";
print "</TR></TABLE>\n";
print qq(<P><TABLE BORDER=0 WIDTH="100%"><TR>\n);
print qq(<TD><INPUT NAME="action" TYPE="SUBMIT"
VALUE="Buy/Update"></TD>\n);
print qq(<TD><INPUT NAME="action" TYPE="SUBMIT"
VALUE="Show Cart" ALIGN=left></TD>\n);
print qq(<TD><INPUT NAME="action" TYPE="SUBMIT"
VALUE="Empty Cart" ALIGN=left></TD>\n);
print qq(<TD><INPUT NAME="action" TYPE="SUBMIT"
VALUE="Check Out" ALIGN=left></TD>\n);
print "</TR></TABLE>\n";
print "</CENTER>";
print qq(<INPUT NAME="cartdata" TYPE="HIDDEN" VALUE="$cart_out">);
# print &PrintVariables(%form); ######### debugging
print qq(<INPUT NAME="item" TYPE="HIDDEN" VALUE="$item">);
print "</FORM>\n";
print "</BODY></HTML>\n";
}
# show_cart. display the entire cartfull of stuff
sub show_cart {
&print_type;
&maintain_state; # this generates the state information
print &HtmlTop("Your Shopping Cart");
if (! keys %cart_data) { # shortcut if nothing in cart
print "<P>Is empty.";
}
else {
print "<P>Listed below is your current shopping cart\n";
print "<UL>\n";
print "<LI>Review the contents of your cart.\n";
print "<LI>To delete an item ";
print "click on the name and change the quantity to zero\n";
print "<LI>To change the quantity click the item name\n";
print "<LI>To complete your order click the 'Check Out' button.\n";
print "</UL>\n";
print "<P>\n";
print qq(<CENTER><TABLE BORDER=1 WIDTH="100%" ALIGN=CENTER>\n);
print "<TR><TH>Quantity</TH><TH>Item</TH>";
print "<TH>Price</TH>\n";
# we use the part number to relate the cart_data and catalog entries
foreach $item (sort keys(%cart_data)) { # sort for display
$num = $cart_data{$item};
($name, $price, $num_cd, $wt) = split(':',$catalog{$item});
print "<TR>";
print "<TD>$num</TD>\n";
print qq(<TD>);
print qq(<A HREF="$self?action=showitem&page=$item&cartdata= $cart_out">);
print "$name</A></TD>\n";
print "<TD>$price</TD>\n";
print "</TR>\n";
}
print "</TABLE></CENTER>\n";
} # end of else
print qq(<P><FORM ACTION="$self" METHOD="POST">);
print qq(<P><TABLE BORDER=0 WIDTH="100%"><TR>\n);
print qq(<TD><INPUT NAME="action" TYPE="SUBMIT" VALUE="Return"></TD>\n);
print qq(<TD><INPUT NAME="action" TYPE="SUBMIT"
VALUE="Empty Cart" ALIGN=left></TD>\n);
print qq(<TD><INPUT NAME="action" TYPE="SUBMIT"
VALUE="Check Out" ALIGN=left></TD>\n);
print "</TR></TABLE>\n";
print qq(<INPUT NAME="cartdata" TYPE="HIDDEN" VALUE="$cart_out">);
# print &PrintVariables(%form); ######### debugging
print "</FORM>", &HtmlBot;
}
# check_out. generate the order page
sub check_out {
&print_type;
&maintain_state; # this generates the state information
print &HtmlTop("Your Order");
print "<P>Here is your order<P>Please review it. If everything is OK\n";
print "Then fill in the information below and click the 'Order' button\n";
print "Your order will be on it's way in no time (literally).\n";
print "<CENTER><TABLE BORDER>\n";
print "<TR><TH>Item</TH><TH>Quantity</TH>";
print "<TH>Price</TH><TH>Total</TH></TR>\n";
foreach $item (sort keys(%cart_data)) { # sort for display
$num = $cart_data{$item};
($name, $price, $num_cd, $wt) = split(':', $catalog{$item});
$sub_tot = $num * $price;
$weight += $num * $wt;
$grand_tot += $sub_tot;
print "<TR><TD>$name</TD><TD>$num</TD><TD>$price</TD>";
print "<TD>", sprintf("%7.2f",$sub_tot), "</TD></TR>\n";
}
print "<TR><TD></TD><TD></TD>";
print "<TH>Sub Total:</TH><TH>";
print sprintf("%7.2f",$grand_tot), "</TH></TR>\n";
$shipping = $weight * 3;
print "<TR><TD></TD><TD></TD>";
print "<TH>Shipping</TH><TH>", sprintf("%7.2f",$shipping), "</TH></TR>\n";
$tax = $grand_tot*0.085;
print "<TR><TD></TD><TD></TD>";
print "<TH>Tax:</TH><TH>", sprintf("%7.2f",$tax), "</TH></TR>\n";
print "<TR><TD></TD><TD></TD>";
print "<TH>Grand Total:</TH><TH>";
print sprintf("%7.2f",$grand_tot+$tax+$shipping);
print "</TH></TR>\n";
print "</CENTER></TABLE>";
# An ugly shipping information form. In a production system you could read
# this data from a registered user database and not require users to input
# shipping and payment data each time. This also increases security.
print <<EndofForm
<FORM ACTION="$self" METHOD="POST">
<INPUT NAME="action" TYPE="SUBMIT" VALUE="Return">
<INPUT NAME="action" TYPE="SUBMIT" VALUE="Empty Cart">
<HR>
Your Name: <INPUT NAME="name" TYPE="TEXT" COLS=30 SIZE="50"><BR>
Street Address: <INPUT NAME="addr" TYPE="TEXT" COLS=50 SIZE="60"><BR>
City: <INPUT NAME="city" TYPE="TEXT" COLS=15 SIZE="60"><BR>
State: <INPUT NAME="state" TYPE="TEXT" COLS=2 SIZE="5">
Zip: <INPUT NAME="zip" TYPE="TEXT" COLS=12 SIZE="12">
Country: <INPUT NAME="country" TYPE="TEXT" COLS=12 SIZE="12">
<CENTER><HR WIDTH="50%"></CENTER>
Method of Payment: Visa <INPUT NAME="pmt" TYPE="RADIO" VALUE="visa">
Master Card <INPUT NAME="pmt" TYPE="RADIO" VALUE="nc">
Discover <INPUT NAME="pmt" TYPE="RADIO" VALUE="dis"><BR>
Card #: <INPUT NAME="card" TYPE="TEXT" COLS=15 SIZE="20">
Expiration: <INPUT NAME="exp" TYPE="TEXT" COLS=9 SIZE="20"><BR>
<CENTER><HR WIDTH="50%"></CENTER>
Shipping Method: <SELECT NAME="ship" VALUE="FedEx" SIZE= 1>
<OPTION>U.S. Mail
<OPTION>UPS Ground
<OPTION>UPS Air
<OPTION>FedEx
</SELECT>
<P><INPUT NAME="action" TYPE="SUBMIT" VALUE="Send Order">
<INPUT TYPE="RESET">
<INPUT NAME="cartdata" TYPE="HIDDEN" VALUE="$cart_out">
</FORM>
EndofForm
}
#eof Minishop.cgi shopping cart demo