创建购物车程序

更新时间:2024-04-08 20:54:01 阅读量: 综合文库 文档下载

说明:文章内容仅供预览,部分内容可能不全。下载后的文档,内容与下面显示的完全一致。下载之前请确认下面内容是否您想要的,是否完整无缺。

第6章 创建购物车程序

第6章

创建购物车程序

Creating a Shopping Cart

对于许多Web开发者来说,简陋的购物车程序在他们心目中有着特殊的位置。虽然许多Web应用程序都是用PHP和MySQL写成,但很多开发者都是从一个无所不在的购物车程序开始其职业生涯的。如果说Web开发蕴含着禅意,那么也许只有在编写购物车时才能体验到这种意境。

放眼互联网,购物车千姿百态,各有不同。众多购物车形态之间的差异是由各自针对的业务不同而造成的。例如,卖书、CD和DVD的购物车与卖电缆、食物、建材和清洁用品的购物车就截然不同。主要区别在于数量,用户通常一次只买一本书或一张DVD,但餐馆一次性购买10袋面包却是家常便饭。 6.1 项目概述

本项目将创建购物车的核心功能。为使您对项目有个清晰的认识,先来看看下面的例子。 John想为他的汽车咖啡店买些茶包。于是他访问了一个知名的在线商店,商店的主人就是本书的一名读者。John没有该网站的用户账号,但他依然开始了购物。他点击了“饮料”分类,看到了茶包的列表。在点击“购买”链接后,系统跳到了另一个页面,在该页面上可以选择数量。他选择了10箱茶包,并将其放到购物车里。随后,页面刷新,他看到了购物车里的内容。接下来John又买了咖啡,于是购物车再次更新。但John想起他不需要咖啡,便点击了“×”(删除)链接,从购物车中删掉了咖啡。John选完了商品,点击了“去收银台”链接。系统提示他输入自己的地址,输入之后他被带到了付款界面。在那里,他可以选择通过PayPal[7]或支票付款。John点击了PayPal按钮,系统跳到了位于paypal.com的PayPal付款界面,John为自己的订单付了款。

Pauline也想买些茶包。不过她已经有了自己的账户,所以她首先登录。然后把自己想要的东西放到了购物车中,再单击了“去收银台”链接。在地址输入页面中,可以选择输入新地址,或者使用账户中保存的地址。她选择了账户保存的地址后跳转到付款界面。Pauline选择了用支票付款,系统告诉她应当向哪里、向谁发送支票。 Ken是网站的运营者,他想查看当前的所有订单。于是他用自己的管理员用户名和密码登录,登录后看到了一系列的订单。Ken逐一查看每一项商品,然后为订单打包货物,并在包裹上写好地址。最后Ken点击了“确认付款”链接,确认该订单完成。整个订单的处理流程至此结束。

本章中建立的购物车将满足前面例子中讨论的所有特性,但实际上仍有无数功能可继续开发。购物车可以成为巨大而复杂的系统,说句公道话,用一整本书来讨论购物车构建的话题都不为过。本项目只是为您继续开发其他特性打下一个坚实的基础。

6.2 建立数据库

本项目使用的数据库如图6-1所示。

图6-1 所有数据库结构均围绕着主要的orders表

从根本上讲,整个项目都依赖于orders表中存储的各个订单。该表关联着customers表(包含的是注册用户的地址详细信息)和delivery_addresses(包含的是未注册用户的地址和注册用户的其他地址)表。订单中的每件商品(存储于products表中)都保存在orderitems表中。其他的表包括logins(存储着注册用户的登录信息),categories(包含的是产品所属的类别)和admins表(存储着管理员登录信息)。 6.2.1 实现数据库

启动phpMyAdmin,新建一个名为shoppingcart的数据库,然后添加以下各表:

NOTE

始终要记住订单的状态

orders表中有个字段叫做status。该字段的作用是标明在购物过程中用户已经进行到了哪个阶段。该字段有4个可能的值: 0 用户仍在向购物车中添加商品; 1 用户已经输入了地址; 2 用户已经付款;

10 管理员已经确认了交易并发送了商品。

admins表

n id:将其设为TINYINT类型(管理员不会太多)并打开auto_increment。设置该字段为主键。

n username:将其设为VARCHAR类型,长度为10。 n password:将其设为VARCHAR类型,长度为10。 categories表

n id:将其设为TINYINT类型(类别数目不会太多),并打开Extras列中的auto_increment。设置该字段为主键。

n name:将其设为VARCHAR类型,并将长度设置为20。(类别标题一般不会超过20个字符。) customers表

n id:将其设为INT类型(会有很多用户)并打开auto_increment。设置该字段为主键。

n forename:将其设为VARCHAR类型,长度为50。 n surname:将其设为VARCHAR类型,长度为50。 n add1:将其设为VARCHAR类型,长度为50。 n add2:将其设为VARCHAR类型,长度为50。 n add3:将其设为VARCHAR类型,长度为50。 n postcode:将其设为VARCHAR类型,长度为10。 n phone:将其设为VARCHAR类型,长度为20。 n email:将其设为VARCHAR类型,长度为100。

n registered:将其设为TINYINT类型。 delivery_addresses表

n id:将其设为INT类型(用户有很多)并打开auto_increment。设置该字段为主键。 n forename:将其设为VARCHAR类型,长度为50。 n surname:将其设为VARCHAR类型,长度为50。 n add1:将其设为VARCHAR类型,长度为50。 n add2:将其设为VARCHAR类型,长度为50。 n add3:将其设为VARCHAR类型,长度为50。 n postcode:将其设为VARCHAR类型,长度为10。 n phone:将其设为VARCHAR类型,长度为20。 n email:将其设为VARCHAR类型,长度为100。 logins表

n id:将其设为INT类型(用户有很多)并打开auto_increment。设置该字段为主键。 n customer_id:类型为INT。

n username:将其设为VARCHAR类型,长度为10。 n password:将其设为VARCHAR类型,长度为10。 orderitems表

n id:将其设为INT类型(订购商品会有很多)并打开auto_increment。设置该字段为主键。

n order_id:将其设为INT类型。 n product_id:将其设为INT类型。 n quantity:将其设为INT类型。 orders表

n id:将其设为INT类型(订单会有很多)并打开auto_increment。设置该字段为主键。

n customer_id:将其设为INT类型。 n registered:将其设为INT类型。

n delivery_add_id:将其设为INT类型。 n payment_type:将其设为INT类型。 n date:将其设为DATETIME类型。 n status:将其设为TINYINT类型。

n session:将其设为VARCHAR类型,长度为50。 n total:将其设为FLOAT类型。 products表

n id:将其设为INT类型(商品会有很多)并打开auto_increment。设置该字段为主键。

n cat_id:将其设为TINYINT类型。

n name:将其设为VARCHAR类型,设置长度为100。有的产品名会很长。 n description:将其设为TEXT类型。

n image:将其设为VARCHAR类型,长度为30。 n price:将其设为FLOAT类型。

6.2.2 插入样本数据 建好这一整套数据库表之后,接下来应该添加一些样本数据。记住不要填写id列,auto_increment会自动处理它。可以随意添加自己的样本数据,也可以使用下面推荐的信息。 admins表的样本数据 为管理员创建用户名和密码。本例中使用的用户名为jono,密码为bacon。 categories表的样本数据 添加两个类别:beverages和cakes。 customers表的样本数据 按照表6-1添加客户。 表6-1 customers和logins表存储的是注册用户的详细信息 ForenaSurnaAdd1 Add2 Add3 me me Postal Phone Code E-mail Registered 19, ZiggTuckeSmalltoT3 Craig The y r wn TR4 Grove Road 19, JordaOak BootThistowT1 n Streeh n Road FG3 t 012345678hissite.c1 90 om 012340987tastic.co1 65 m craig@ lee@lee Lee logins表的样本数据 为每个客户添加表6-2 所示的登录信息。 表6-2 请确保customer_id字段能匹配customers表中的id字段 Customer_id 1 2 Username Craig Lee Password Tucker Jordan delivery_addresses表的样本数据 保持该表为空。 products表的样本数据 向products表中添加如表6-3所示的商品。 表6-3 如果存在图片,则用image字段保存图片名 Cat_id Name 1 Best Bags Description Image A quality pack of tea bags. 200 bags in each box. Price 2.99

1 Best Orange Juice One gallon of quality squeezed orange juice. bestorange-juice.jpg 0.90 orders表的样本数据 保持该表为空。 orderitems表的样本数据 保持该表为空。

6.3 开始编码

创建购物车程序的一个挑战是需要同时处理注册用户和未注册用户。对于注册用户完全没有问题,因为向表中添加信息时可以使用其ID对用户进行跟踪。而对于未注册用户则遇到了一个 难题。如何跟踪他们?

解决方案是使用会话ID(session ID)。用户加载第一页时,session_start()函数会生成一个特殊的会话ID。该ID对于特定用户来说是唯一的,用它可以跟踪访问网站的用户所使用的会话变量。虽然以前没有使用过会话ID,但在本项目中,我们将广泛使用它。

每当用户访问购物车程序并添加第一件商品时,系统就会将一个订单添加到orders表中。对于注册用户,用户的id会保存到该表的customer_id字段中。对于未注册用户,则在session字段中保存唯一的会话id。将订单添加到orders表中后,系统将使用订单的id创建一个名为SESS_ORDERNUM的会话变量。之后就可通过SESS_ORDERNUM来跟踪用户在整个网站中的购物过程了。

先来创建通用配置文件,它保存的是网站的一般性信息。建立名为shoppingcart的新目录,并将示例6-1的代码保存为config.php。

示例6-1 配置文件与本书其他项目中的配置文件大同小异

$dbhost = \$dbuser = \$dbpassword = \

$dbdatabase = \

$config_basedir = \$config_sitename = \?>

为使得重定向更容易处理,可将有关数据库连接的细节放到名为db.php的文件中,如示例6-2所示。 示例6-2 当需要数据库连接,但由于要重定向而不能包含header.php时,可以包含db.php文件

require(\

$db = mysql_connect($dbhost, $dbuser, $dbpassword); mysql_select_db($dbdatabase, $db); ?>

创建示例6-3所示的header.php。

示例6-3 页头文件负责添加菜单选项、侧边栏以及一些登录/注销链接

session_start();

if(isset($_SESSION['SESS_CHANGEID']) == TRUE) {

session_unset(); session_regenerate_id(); }

require(\

$db = mysql_connect($dbhost, $dbuser, $dbpassword); mysql_select_db($dbdatabase, $db); ?>

<?php echo $config_sitename; ?>

showcart.php\

require(\ echo \

if(isset($_SESSION['SESS_LOGGEDIN']) == TRUE) {

echo \ . \

[

echo \ }

?>

header.php中有些很有意思的地方,让我们来细细品味一下:

n 在文件开头,先检查会话变量SESS_CHANGEID是否存在。如果存在,则unset(删除)该变量,这样就会重新生成会话id。稍后当订单完成后,在需要重置会话系统时,将会创建SESS_CHANGEID变量。 n 检查SESS_LOGGEDIN变量是否存在。如果存在,则说明用户已经登录,于是显示用户名和“注销”链接。用户名存储在SESS_USERNAME中,该变量在稍后即将说明的login.php中创建。 n 本书以前项目中用到的stylesheet.css原封不动地用在这里。

页头文件中还包含了一个名为bar.php的文件。该文件含有示例6-4所示的分类列表。

示例6-4 虽然bar.php中的代码可以直接加到header.php中,但使用独立的文件可以在必要时整洁地添加其他内容

Product Categories

$catsql = \ $catres = mysql_query($catsql);

while($catrow = mysql_fetch_assoc($catres)) {

echo \ . \ . $catrow['name'] . \ } ?>

bar.php中的代码执行了一个SELECT查询以收集所有的分类信息,并将其显示为项目列表。每个分类均链接至即将创建的products.php中。

建立footer.php并添加示例6-5中所示的代码。

示例6-5 在管理员登录后,页脚文件会创建管理(“admin”)链接

echo \ . $config_sitename . \

if($_SESSION['SESS_ADMINLOGGEDIN'] == 1) {

echo \[

. \ } ?>

使用示例6-6中的代码创建网站的主页面index.php。

示例6-6 看到这样小的index.php文件也许您会很吃惊。该文件仅用来显示商店的基本信息

require(\?>

Welcome!!

Welcome to the

website. Click on one of the pages to explore the site. We have a wide range of different products available.

require(\?>

主要的设计完成后,就可以从浏览器中看到类似于图6-2的结果了。

图6-2 主页面为购物车提供了简单、整洁的界面

6.4 管理用户登录

用户是购物车程序中的重要元素,同时跟踪注册用户和未注册用户更是重中之重。网站的大部分脚本都包含两条功能主线,一条为用户已登录的情况,一条为未登录的情况。

登录页面与本书中其他的登录页面很相似。创建名为login.php的文件并添加以下表单:

回到文件开头部分,添加以下代码:

session_start(); require(\

if(isset($_SESSION['SESS_LOGGEDIN']) == TRUE) { header(\ }

检查用户是否已经登录。如果已登录,则没有必要再加载登录页面,所以重定向到网站的首页。 接下来处理表单:

if(isset($_SESSION['SESS_LOGGEDIN']) == TRUE) { header(\ }

if($_POST['submit']) {

$loginsql = \ WHERE username = '\ . \ . \

$loginres = mysql_query($loginsql); $numrows = mysql_num_rows($loginres); if($numrows == 1) {

$loginrow = mysql_fetch_assoc($loginres); session_register(\ session_register(\ session_register(\ $_SESSION['SESS_LOGGEDIN'] = 1;

$_SESSION['SESS_USERNAME'] = $loginrow['username']; $_SESSION['SESS_USERID'] = $loginrow['id']; $ordersql = \

WHERE customer_id = \ . \

$orderres = mysql_query($ordersql);

$orderrow = mysql_fetch_assoc($orderres); session_register(\

$_SESSION['SESS_ORDERNUM'] = $orderrow['id']; header(\ } else {

header(\ . $SCRIPT_NAME . \ } }

这段代码与之前的用户登录使用的方法相同。成功登录后,代码将建立3个会话变量: n SESS_LOGGEDIN:设置为1以表明用户已经登录。 n SESS_USERNAME:存储用户的用户名。 n SESS_USERID:存储用户的id。

在设置这些变量之后,代码执行SELECT语句从orders表中取出符合“customer_id字段匹配当前用户id”条件的记录的id。该查询返回的id值被保存在名为SESS_ORDERNUM的另一个会话变量中。这个过程可能有两种结果:

n 不存在任何订单。如果orders表中没有任何订单,则SESS_ORDERNUM没有值。

n 存在一个订单。如果查询返回了一个id,就把SESS_ORDERNUM设置为该id。这样,如果用户为购物车选择了商品后就注销了,那么当该用户再次登录时,购物车中能保持与上次访问时相同的商品,并且用户可以继续选择商品。该功能提供了重要的可持续性。

表单成功提交并设置好会话变量后,页面将重定向到网站的首页。页面会在侧边栏中显示“Logged in as ”字样

添加其余的代码:

header(\ . $SCRIPT_NAME . \ } } else {

require(\?>

Customer Login

Please enter your username and password to log into the websites. If you do not

have an account, you can get one for free by

if($_GET['error']) {

echo \ } ?>

require(\?>

完成后的结果看起来应该类似图6-3所示的样子。

图6-3 完成后的登录界面 6.4.1 注销用户

注销过程与之前的项目稍有不同。这里不是通过session_destroy()销毁整个会话,而是注销用户的会话变量。因为当管理员同时以管理者和普通用户身份登录时,session_destroy()会销毁管理员的登录会话,所以不能使用它。

创建logout.php并添加以下代码:

session_start(); require(\

session_unregister(\ session_unregister(\ session_unregister(\ header(\?>

6.5 显示商品与选择商品

购物车程序最核心的功能就是将商品展示给用户,并让用户选择商品,添加到虚拟的购物车中。这个功能涉及以下几个不同的过程:

n 用户单击商品类别以查看可购买的商品。单击“Buy”链接可以选择商品。

n 然后,用户选择要购买的商品数量。

n 页面显示购物车的当前内容及总价格。用户也可以通过单击“×”符号以从购物车中删除任何商品并重新计算总价格。

整个过程中第一步是显示某个类别内可供购买的商品。在编写代码之前,请回顾一下第5章的论坛项目中编写过的pf_validate_number()函数。创建名为functions.php的新文件并将代码复制过来:

function pf_validate_number($value, $function, $redirect) { if(isset($value) == TRUE) {

if(is_numeric($value) == FALSE) { $error = 1; }

if($error == 1) {

header(\ } else {

$final = $value; } } else {

if($function == 'redirect') { header(\ }

if($function == \ $final = 0; } }

return $final; } ?>

在本项目中,您还应当创建一些像图6-4那样的商品图片,以在购物车中使用。您至少需要创建一个名为dummy.jpg的图片,在某个商品没有图片时可以加载该图片。如果您用的是本章前面提供的样本数据,那么“Best Orange Juice”(极品橙汁)的图片名是bestorangejuice.jpg。 图6-4

图片大小应当是150 × 150个像素,并保存成JPG格式

建立名为products.php的新文件并添加代码:

require(\

require(\

$validid = pf_validate_number($_GET['id'], \

pf_validate_number()函数检查传递过来的类别id GET变量。如果id GET变量不存在,则重定向到网站的首页。

从数据库中选择商品:

$validid = pf_validate_number($_GET['id'], \ require(\

$prodcatsql = \ cat_id = \

$prodcatres = mysql_query($prodcatsql); $numrows = mysql_num_rows($prodcatres); if($numrows == 0) {

echo \

echo \ }

如果分类中没有商品,则在页面上显示“No products”字样。(通过检查返回的行数是否为0可以判断分类中有无商品。)如果分类中有商品。则通过while()循环结构依次遍历每一行数据:

echo \ } else {

echo \

while($prodrow = mysql_fetch_assoc($prodcatres)) {

echo \

if(empty($prodrow['image'])) { echo \

src='./productimages/dummy.jpg' alt='\ . $prodrow['name'] . \ } else {

echo \ . \

. $prodrow['name'] . \ }

echo \

echo \ echo \ echo \ . sprintf('%.2f', $prodrow['price']) . \ echo \

. $prodrow['id'] . \ echo \ echo \ }

echo \

上述代码显示每件商品的信息,并显示指向addtobasket.php的“Buy”链接。该链接同样将商品的id作为GET变量传递。

最后,添加结束代码: echo \ }

require(\?>

6.5.1 向购物车添加商品

addtobasket.php页面的作用是将选中的商品添加到orderitems表中,然后重定向到一个能显示购物车中商品概况的页面。

addtobasket.php页面是个包含许多嵌套if语句的大型脚本,很难像其他脚本那样分成许多部分逐一进行讲解。为便于理解,请先将以下所有代码添加到文件中,稍后我们再逐步讲解。

创建addtobasket.php并添加代码:

session_start(); require(\ require(\

$validid = pf_validate_number($_GET['id'], \

$prodsql = \ $prodres = mysql_query($prodsql); $numrows = mysql_num_rows($prodres); $prodrow = mysql_fetch_assoc($prodres); if($numrows == 0) {

header(\ } else {

if($_POST['submit']) {

if($_SESSION['SESS_ORDERNUM']) {

$itemsql = \ product_id, quantity) VALUES(\

. $_SESSION['SESS_ORDERNUM'] . \ . $_GET['id'] . \

. $_POST['amountBox'] . \ mysql_query($itemsql);

} else {

if($_SESSION['SESS_LOGGEDIN']) {

$sql = \ registered, date) VALUES(\

. $_SESSION['SESS_USERID'] . \ mysql_query($sql);

session_register(\

$_SESSION['SESS_ORDERNUM'] = mysql_insert_id(); $itemsql = \

orderitems(order_id, product_id, quantity) VALUES(\ . $_SESSION['SESS_ORDERNUM'] . \

. $_POST['amountBox'] . \ mysql_query($itemsql); } else {

$sql = \ date, session) VALUES(\

. \ mysql_query($sql);

session_register(\

$_SESSION['SESS_ORDERNUM'] = mysql_insert_id(); $itemsql = \

orderitems(order_id, product_id, quantity) VALUES(\

. $_SESSION['SESS_ORDERNUM'] . \ . $_POST['amountBox'] . \ mysql_query($itemsql); } }

$totalprice = $prodrow['price'] * $_POST['amountBox'] ; $updsql = \ . $totalprice . \ . $_SESSION['SESS_ORDERNUM'] . \ mysql_query($updres);

header(\ } else {

require(\

echo \

. $_GET['id'] . \ echo \ echo \

if(empty($prodrow['image'])) { echo \

src='./productimages/dummy.jpg' width='50' alt='\ . $prodrow['name'] . \ } else {

echo \ . \ . \ }

echo \

echo \ for($i=1;$i<=100;$i++) {

echo \ }

echo \ echo \ . sprintf('%.2f', $prodrow['price']) . \

echo \ name='submit' value='Add to basket'>\ echo \ echo \ echo \ } }

require(\?>

为了更好地解释这段代码,请仔细研究以下这些内容,看看这段程序到底做了什么。阅读每一条内容时,请对照您在编辑器里输入的代码。准备好了吗?现在开始……

n 在页面开头,查询返回id GET变量对应的商品。如果没有返回任何行,则跳转到网站的首页。 n 显示表单,其中包含一个下拉选择框,并通过for循环生成1到100的选项。除了表单,还显示一些商品信息。

n 当用户提交表单时,页面重新加载并检查SESS_ORDERNUM变量是否存在。如果存在,说明订单已经生成,则通过INSERT语句将产品id和数量插入到orderitems表中,其中的order_id就是SESS_ORDERNUM。

n 如果SESS_ORDERNUM不存在,则必须在orders表中先创建一个订单,否则就没法向orderitems表中添加商品信息。接下来检查SESS_LOGGEDIN变量是否存在。如果存在,说明用户已经登录,于是先创建一个订单,再将商品添加到orderitems表中。如果SESS_LOGGEDIN不存在,说明用户没有登录(可

能访问者没有用户账号)。此时,在orders表中创建一个订单(使用session_id()获取唯一的会话id),然后将商品添加到orderitems表中。

n 更新orders表中的total字段。通过计算单价与数量的乘积即可得出总价格(结果保存在$totalprice中)。

n 最后,页面重定向到购物车概要页面showcart.php。

在单击提交按钮之前,请确保页面是图6-5所示的样子。

图6-5 完整的“添加到购物车”脚本

6.5.2 显示购物车概况

在addtobasket.php程序完成处理后,页面会重定向到showcart.php。这个页面负责显示添加到购物车中的商品的概况。

有时候您需要显示商品描述。为避免代码重复,我们建立一个名为showcart()的函数来显示购物车概况。创建该函数之前,先建立一个名为showcart.php的文件并添加以下这些调用showcart()函数的代码:

session_start(); require(\ require(\

echo \ showcart();

if(isset($_SESSION['SESS_ORDERNUM']) == TRUE) { $sql = \ order_id = \ $result = mysql_query($sql);

$numrows = mysql_num_rows($result); if($numrows >= 1) {

echo \ Go to the checkout\ } }

require(\?>

并不是每个页面都需要结账链接,所以showcart()函数没有包含它。函数调用之后的代码块检查订单号是否存在,若存在,则检查购物车内是否有选好的商品。如果存在一个或多个商品,则显示结账链接。

向functions.php中添加showcart()代码:

function showcart() {

if($_SESSION['SESS_ORDERNUM']) {

if($_SESSION['SESS_LOGGEDIN']) {

$custsql = \ orders WHERE customer_id = \ . $_SESSION['SESS_USERID'] . \

$custres = mysql_query($custsql); $custrow = mysql_fetch_assoc($custres);

函数中,外层的if检查是否存在订单号。如果存在,第二个if将检查用户是否已登录。如果用户已经登录,则用查询从orders表中选出符合“用户id等于当前用户并且status等于0或1”条件的那些行。该查询应当仅返回一行内容。

接下来可以执行主查询,获取已选商品的详细内容。 $custrow = mysql_fetch_assoc($custres);

$itemssql = \ itemid FROM products, orderitems WHERE orderitems.product_id = products.id AND order_id = \ $itemsres = mysql_query($itemssql);

$itemnumrows = mysql_num_rows($itemsres); }

如果用户尚未登录,则执行一个类似的SELECT查询以获取订单号,但这次是通过当前的会话id进行匹配。查询执行后,返回一系列已选择的商品。

$itemnumrows = mysql_num_rows($itemsres); } else {

$custsql = \

WHERE session = '\ $custres = mysql_query($custsql); $custrow = mysql_fetch_assoc($custres); $itemssql = \ orderitems.id AS itemid

FROM products, orderitems WHERE orderitems.product_id = products.id AND order_id = \

$itemsres = mysql_query($itemssql);

$itemnumrows = mysql_num_rows($itemsres);

如果SESS_ORDERNUM变量可用,则设置$itemnumrows变量为0: $itemnumrows = mysql_num_rows($itemsres); } }

else {

$itemnumrows = 0; }

下面这段代码检查$itemnumrows的值。如果值为0,则显示消息说明购物车是空的: $itemnumrows = 0; }

if($itemnumrows == 0) {

echo \ }

如果$itemnumrows变量中有值,则显示已选的商品:

echo \ } else {

echo \ echo \

echo \

echo \ echo \ echo \ echo \ echo \ echo \

while($itemsrow = mysql_fetch_assoc($itemsres)) {

$quantitytotal =

$itemsrow['price'] * $itemsrow['quantity']; echo \

if(empty($itemsrow['image'])) { echo \

src='./productimages/dummy.jpg' width='50' alt='\ . $itemsrow['name'] . \ } else {

echo \ $itemsrow['image'] . \ . $itemsrow['name'] . \ }

echo \ echo \ echo \ . sprintf('%.2f', $itemsrow['price'])

. \

echo \

. sprintf('%.2f', $quantitytotal) . \ echo \ . $config_basedir . \ . $itemsrow['itemid'] . \ echo \

$total = $total + $quantitytotal;

$totalsql = \ . $total . \

. $_SESSION['SESS_ORDERNUM']; $totalres = mysql_query($totalsql); }

echo \ echo \ echo \ echo \ echo \ echo \ . sprintf('%.2f', $total) . \ echo \ echo \ echo \

echo \ } }

6.5.3 删除选中的商品

showcart()函数包含一个指向delete.php的链接,通过该链接可以从购物车中删除商品。单击链接时,系统将从orderitems表中删除指定的商品,并更新orders表中的总价格。

建立delete.php并添加以下代码:

require(\ require(\ require(\

$validid = pf_validate_number($_GET['id'], \

$itemsql = \ . $_GET['id'] . \

$itemres = mysql_query($itemsql); $numrows = mysql_num_rows($itemres); if($numrows == 0) {

header(\ }

$itemrow = mysql_fetch_assoc($itemres);

这段代码中的查询从orderitems表中获取选择的商品,然后检查返回的行数。这种检查可以防止有人通过修改URL,如改成“delete.php?id=73”之类的参数信息而引发问题。如果没有返回任何行,则通过HTTP头重定向技术跳转到showcart.php。如果返回了一行,则继续执行脚本:

$itemrow = mysql_fetch_assoc($itemres); $prodsql = \ WHERE id = \ $prodres = mysql_query($prodsql); $prodrow = mysql_fetch_assoc($prodres);

$sql = \ mysql_query($sql);

在这个代码块中,首先选出商品的价格,然后通过另一个查询从orderitems中删除商品。 使用新的总价格更新orders表: mysql_query($sql);

$totalprice = $prodrow['price'] * $itemrow['quantity'] ; $updsql = \ . $totalprice . \ . $_SESSION['SESS_ORDERNUM'] . \ mysql_query($updres);

header(\?>

完成购物车概要函数和相关的页面后,浏览器上看到的应当与图6-6显示的页面相似:

图6-6 购物车概要显示出当前选中商品的列表,并允许用户加以删除

6.6 结账

用户向购物车中添加完商品之后,就可以开始结账过程了。这个过程需要两个步骤:

n 提示用户输入发货地址。如果用户已经登录,则询问是要使用注册过的地址还是使用其他地址。所有地址都应被验证。

n 提示用户选择付款方式,可以通过PayPal或支票付款。

创建checkout-address.php并添加表单:

require(\

echo \ if(isset($_GET['error']) == TRUE) { echo \ information from the form\ }

echo \ if($_SESSION['SESS_LOGGEDIN']) { ?>


Forename

Surname

House Number, Street

Town/City

County

Postcode

Phone

Email

显示表单之前,if语句先判断error GET变量是否存在。如果存在,则显示错误信息。之后脚本检查用户是否已登录,如果已经登录,则添加两个单选按钮,让用户在注册的地址和其他地址之间选择。

回到文件开头,添加如下代码:

session_start(); require(\

$statussql = \ $_SESSION['SESS_ORDERNUM']; $statusres = mysql_query($statussql); $statusrow = mysql_fetch_assoc($statusres); $status = $statusrow['status'];

第一步检查订单的当前状态。如果用户已经通过了结账过程中的地址选择阶段,则将页面重定向至付款界面。通过在orders表中搜索符合SESS_ORDERNUM的记录可以得到订单的状态,然后设置$status变量为正确的状态值。

NOTE

不要忘记状态的取值有以下几种: 0 用户仍在购物。 1 用户已完成了地址输入。 2 用户已经完成付款。 10 管理员已经确认完定单。

如果状态为1,说明用户已经输入完地址,页面重定向到付款界面。如果状态为2或更高,说明订单已经完成,页面重定向到网站的首页:

$status = $statusrow['status']; if($status == 1) {

header(“Location: “ . $config_basedir . “checkout-pay.php”); }

if($status >= 2) {

header(“Location: “ . $config_basedir);

}

开始处理表单: if($status >= 2) {

header(\ }

if($_POST[?submit?]) {

if($_SESSION[?SESS_LOGGEDIN?]) {

if($_POST[?addselecBox?] == 2) {

if(empty($_POST[?forenameBox?]) || empty($_POST[?surnameBox?]) || empty($_POST[?add1Box?]) || empty($_POST[?add2Box?]) || empty($_POST[?add3Box?]) || empty($_POST[?postcodeBox?]) || empty($_POST[?phoneBox?]) || empty($_POST[?emailBox?])) {

header(“Location: “ . $basedir . “checkout- address.php?error=1”); exit; }

嵌套内的第一个if判断检查用户是否已经登录。然后检查用户是否选择了第二个单选按钮(Use the address below的选项)。这种情况下则检查表单字段是否为空。如果为空,则设置error这个GET变量并刷新页面,以显示错误信息。

如果表单非空,则将地址添加到delivery_addresses表并更新orders表: exit; }

$addsql = \

delivery_addresses(forename, surname, add1, add2, add3, postcode, phone, email) VALUES('\

. strip_tags(addslashes( $_POST['forenameBox'])) . \

. strip_tags(addslashes( $_POST['surnameBox'])) . \

. strip_tags(addslashes( $_POST['add1Box'])) . \

. strip_tags(addslashes( $_POST['add2Box'])) . \

. strip_tags(addslashes( $_POST['add3Box'])) . \

. strip_tags(addslashes( $_POST['postcodeBox'])) . \

. strip_tags(addslashes( $_POST['phoneBox'])) . \

. strip_tags(addslashes( $_POST['emailBox'])) . \ mysql_query($addsql);

$setaddsql = \ delivery_add_id = \ status = 1 WHERE id = \ . $_SESSION['SESS_ORDERNUM']; mysql_query($setaddsql); header(\

. $config_basedir . \ }

delivery_addresses表包含了未注册用户的地址与注册用户选择的非注册地址。将地址信息添加到表中时,strip_tags()函数将删除可能出现的所有HTML标签,addslashes()函数将所有引号进行转义。最后,使用插入到delivery_addresses表中的记录的id值更新orders表,并将status改变为1。完成这些操作后,页面重定向至checkout-pay.php页面。

如果用户已登录,但选择了注册的地址,则仍然要更新orders表: header(\

. $config_basedir . \ } else {

$custsql = \ delivery_add_id = 0, status = 1 WHERE id = \ $_SESSION['SESS_ORDERNUM']; mysql_query($custsql);

header(\ . \ } }

如果用户没有登录,则进行表单验证并将地址添加到数据库: header(\ . \ } } else {

if(empty($_POST['forenameBox']) || empty($_POST['surnameBox']) || empty($_POST['add1Box']) || empty($_POST['add2Box']) ||

empty($_POST['add3Box']) || empty($_POST['postcodeBox']) || empty($_POST['phoneBox']) || empty($_POST['emailBox'])) {

header(\ exit; }

$addsql = \

delivery_addresses(forename, surname, add1, add2, add3, postcode, phone, email) VALUES('\

. $_POST['forenameBox'] . \ . $_POST['surnameBox'] . \ . $_POST['add1Box'] . \ . $_POST['add2Box'] . \ . $_POST['add3Box'] . \ . $_POST['postcodeBox'] . \ . $_POST['phoneBox'] . \ . $_POST['emailBox'] . \ mysql_query($addsql); $setaddsql = \ SET delivery_add_id = \

. mysql_insert_id() . \ . session_id() . \

mysql_query($setaddsql);

header(\ } }

这段代码将地址添加到delivery_addresses表,然后使用delivery_addresses表中记录的id更新orders表并将status设为1。

接下来开始显示表单:

header(\ } } else {

require(\

echo \最后,在表单之后添加以下代码:

require(\?>

完成地址相关的代码之后,在用户已经登录的情况下,浏览器显示的页面应该类似于图6-7。

图6-7 如果用户已经登录,单选按钮选项将提示用户选择哪个地址 6.6.1 付款

结账过程的最后一部分是付款。在网站上可以通过多种途径来付款:PayPal,NOCHEX,Worldpay,等等。本项目提供两种付款方式:PayPal和支票。这两种方法演示了如何处理自动购买(PayPal)和手动购买(支票)过程。

创建名为checkout-pays.php的新文件,添加以下表单:

Select a payment method

这个简单的表单中仅有两个提交按钮——一个是通过PayPal付款,另一个是通过支票付款。处理该表单需要两个部分——一部分处理PayPal的情况,另一部分处理支票的情况。

在页面顶部添加以下代码:

session_start(); require(\ require(\

用户单击PayPal按钮时,按以下的方法处理订单: require(\ if($_POST['paypalsubmit']) {

$upsql = \type = 1 WHERE id = \ $upres = mysql_query($upsql);

$itemssql = \ id = \ $itemsres = mysql_query($itemssql); $row = mysql_fetch_assoc($itemsres);

更新orders表以反映订单的完成。status字段更新到2,payment_type字段设置为1(PayPal)。然后执行查询,获取订单的总价,用于稍后组合成PayPal的付款链接。

重置订单会话:

$row = mysql_fetch_assoc($itemsres); if($_SESSION['SESS_LOGGEDIN']) {

unset($_SESSION['SESS_ORDERNUM']); } else {

session_register(\ $_SESSION['SESS_CHANGEID'] = 1; }

如果用户已经登录,则通过unset()函数删除SESS_ORDERNUM会话变量。如果用户未登录,则创建名为SESS_CHANGEID的新会话变量。下次再加载header.php时,header.php顶部的代码将重新生成新的会话和会话id。

重定向到www.paypal.com,同时传递付款的细节。 $_SESSION['SESS_CHANGEID'] = 1; }

header(\cgi-bin/webscr?cmd=_xclick&business= you@youraddress.com&item_name=\ . urlencode($config_sitename) . \ .\ $row['total'])) . \ submit.x=41&submit.y=15\ } 这行代码通过一系列GET变量向PayPal网站传递数据。这些GET变量是一些保留字,PayPal通过它们来处理订单。表6-4解释了每个变量的作用。 表6-4 PayPal变量详解 PayPal变量 business item_name item_number 设置 %urlencode($config_sitename) . “+Order” \amount no_note urlencode(sprintf('%.2f', $row['total'])) 1 描述 运行该网站的业务名称 订单名称,本例中为“ Order” 商品代码。此处在“PROD”后面接上订单编号作为商品代码(如“PROD12”)。 订单数量 no_note变量指定客户是否需要为付款指定留言。设置为1表示不需要留言。 交易的货币种类。 交易的区域。 currency_code GBP lc GB 重要的一点是,任何通过GET变量传递的文本信息都必须通过urlencode()对非标准的字符进行转义。 现在开始编写处理支票支付方式的代码。代码与PayPal的代码很相似。 header(\cgi-bin/webscr?cmd=_xclick&business=you@ youraddress.com&item_name=\ . urlencode($config_sitename) . \ .\ $row['total'])) . \ _code=GBP&lc=GB&submit.x=41&submit.y=15\ } else if($_POST['chequesubmit']) { $upsql = \ payment_type = 2 WHERE id = \ . $_SESSION['SESS_ORDERNUM'];

$upres = mysql_query($upsql);

这里再次更新orders表,但这次设置payment_type为2而不是1。 跟刚才一样,重置该订单: $upres = mysql_query($upsql); if($_SESSION['SESS_LOGGEDIN']) {

unset($_SESSION['SESS_ORDERNUM']); } else {

session_register(\ $_SESSION['SESS_CHANGEID'] = 1; }

最后,显示接收支票的详细地址:

$_SESSION['SESS_CHANGEID'] = 1; }

require(\?>

Paying by cheque

Please make your cheque payable to

.

Send the cheque to:


22, This Place,
This town,
This county,
FG43 F3D.

至此,处理就结束了。

打开代码块以显示表单。但在显示表单之前,首先调用showcart()函数以显示当前购物车的概况:

require(\ echo \ showcart(); ?>

Select a payment method

require(\?>

您崭新的、土生土长的付款界面应当类似于图6-8。

图6-8 完成后的付款界面

6.7 管理员页面

购物车的管理功能十分简单。管理的主要功能就是查看并确认已完成的订单。订单确认后就表明管理员已经成功地发货了。

首先需要提供一个管理员登录功能。创建名为adminlogin.php的新文件并添加以下代码:

session_start(); require(\

if(isset($_SESSION['SESS_ADMINLOGGEDIN']) == TRUE) { header(\\$config_basedir); }

if($_POST['submit']) {

$loginsql = \* FROM admins WHERE username = '\$_POST['userBox'] . \AND password = '\$_POST['passBox'] . \

$loginres = mysql_query($loginsql);

$numrows = mysql_num_rows($loginres); if($numrows == 1) {

$loginrow = mysql_fetch_assoc($loginres); session_register(\ $_SESSION['SESS_ADMINLOGGEDIN'] = 1;

header(\\$config_basedir . \ } else {

header(\\ . $config_basedir

. \ } } else {

require(\

echo \Login\ if($_GET['error'] == 1) {

echo %username/password!\ } ?>

require(\

?>

绝大部分代码您都应该很熟悉。管理员成功登录后,代码会创建会话变量SESS_ ADMINLOGGEDIN。 6.7.1 管理员注销

创建名为adminlogout.php的新文件并添加以下代码,以注销管理员:

session_start(); require(\

session_unregister(\ header(\\$config_basedir); ?>

和普通用户注销一样,这里仅仅注销变量,而不是销毁整个会话。这样当管理员以管理者和普通用户两种身份同时登录时,可以避免注销所有身份。 6.7.2 管理已完成的订单

管理员主页显示的是已完成订单的列表。该页面的用途是允许管理员查看哪个订单的商品需要邮寄。这样管理员就可以打包商品,并在发货之后确认订单。

本页面十分简洁明了,只是从一些表中取出数据并输出。脚本有两个主要状态:显示订单,或者确认订单。默认的页面是显示订单。如果给页面传递“func=conf”GET变量与订单号,则对应于该订单号的订单将被确认。

创建名为adminorders.php的新文件,开始添加以下代码:

session_start(); require(\ require(\ require(\

if(isset($_SESSION['SESS_ADMINLOGGEDIN']) == FALSE) { header(\\$config_basedir); }

在这段通用代码之后,检查名为func的GET变量是否存在: if(isset($_GET['func']) == TRUE) { if($_GET['func'] != \{

header(\\$config_basedir); }

$validid = pf_validate_number($_GET['id'], \$config_basedir);

$funcsql = \orders SET status = 10 WHERE id = \$_GET['id']; mysql_query($funcsql);

header(\\$config_basedir . \ }

如果func GET变量存在,则当该变量的值不等于“conf”时重定向页面以避免SQL注射攻击。接下来,验证id GET变量是否有效。最后通过更新orders表并设置status字段的值为10来确认订单。最后重定向到订单概况页面。

如果func GET变量不存在,则让该页面显示已完成的订单一览: else {

require(\

echo \orders\

$orderssql = \* FROM orders WHERE status = 2\ $ordersres = mysql_query($orderssql);

$numrows = mysql_num_rows($ordersres); if($numrows == 0) {

echo \orders\ } else {

echo \cellspacing=10>\

while($row = mysql_fetch_assoc($ordersres)) {

echo \

echo \

href='adminorderdetails.php?id=\$row['id'] . \ echo \

. date(\jS F Y g.iA\strtotime($row['date']))

. \

echo \

if($row['registered'] == 1) {

echo \Customer\ } else

echo \Customer\ }

echo \

echo \sprintf('%.2f', $row['total']) . \ echo \

if($row['payment_type'] == 1) {

echo \ } else {

echo \ }

echo \ echo \

href='adminorders.php?func=conf&id=\$row['id'] . \Payment\ echo \ }

echo \ } }

require(\?>

如果一切正常,完成后的订单摘要应当类似于图6-9所示的页面。

图6-9 出色的订单页面能使您轻松查看需要发货的订单 6.7.3 查看指定的订单

为了知道某个订单的邮寄地址,管理员需要查看该订单的详细信息。下一个页面应当列出订单的信息(订单号、地址、购买的商品、付款方式,等等)。

建立名为adminorderdetails.php的文件并添加以下代码:

session_start();

require(\ require(\

if(isset($_SESSION['SESS_ADMINLOGGEDIN']) == FALSE) { header(\\$basedir); }

$validid = pf_validate_number($_GET['id'], \$config_basedir . \ require(\

echo \Details\

echo \href='adminorders.php'><— go back to the main orders screen\

$ordsql = \* from orders WHERE id = \$validid; $ordres = mysql_query($ordsql);

$ordrow = mysql_fetch_assoc($ordres); echo \cellpadding=10>\

echo \Number \$ordrow['id'] . \

echo \of order

\date('D jS F Y g.iA', strtotime($ordrow['date'])) . \

echo \Type\ if($ordrow['payment_type'] == 1) {

echo \ } else {

echo \ }

echo \ echo \

if($ordrow['delivery_add_id'] == 0) {

$addsql = \* FROM customers WHERE id = \$ordrow['customer_id']; $addres = mysql_query($addsql); } else {

$addsql = \* FROM delivery_addresses WHERE id = \$ordrow['delivery_add_id']; $addres = mysql_query($addsql); }

$addrow = mysql_fetch_assoc($addres); echo \cellpadding=10>\ echo \

echo \ echo \$addrow['forename'] . \\ . $addrow['surname'] . \ echo $addrow['add1'] . \ echo $addrow['add2'] . \ echo $addrow['add3'] . \ echo $addrow['postcode'] . \ echo \

if($ordrow['delivery_add_id'] == 0) {

echo \from member account\ } else {

echo \delivery address\ }

echo \

echo \

. $addrow['phone'] . \

echo \

$itemssql = \products.*, orderitems.*, orderitems.id AS itemid FROM products, orderitems WHERE orderitems.product_id = products.id AND order _id = \$validid;

$itemsres = mysql_query($itemssql);

$itemnumrows = mysql_num_rows($itemsres); echo \Purchased\ echo \cellpadding=10>\ echo \

echo \ echo \ echo \ echo \

while($itemsrow = mysql_fetch_assoc($itemsres)) {

$quantitytotal = $itemsrow['price'] * $itemsrow['quantity']; echo \

if(empty($itemsrow['image'])) { echo \

src='./productimages/dummy.jpg' width='50' alt='\ . $itemsrow['name'] . \ }

else {

echo \src='./productimages/\ . $itemsrow['image'] . \width='50' alt='\ . $itemsrow['name'] . \ }

echo \$itemsrow['name'] . \

echo \$itemsrow['quantity'] . \x \ echo \sprintf('%.2f', $itemsrow['price']) . \

echo \sprintf('%.2f', $quantitytotal) . \ echo \ }

echo \

echo \ echo \

echo \

echo \

echo \sprintf('%.2f', $total) . \ echo \ echo \ require(\?>

这段代码您应该很熟悉了,它简单地显示orders、orderitems和delivery_addresses表中的一些详细内容。

完成后的页面看上去与图6-10相似。

图6-10 管理界面中的订单概要

6.8 总结

这个项目中,将若干不同的技巧结合在一起来建立一个完整的产品。虽然只是触及了购物车系统众多可能功能的一点皮毛,但您已经开发出了核心的功能,仍有广阔的空间让您自由发挥,如: n 当订单完成时向用户和管理员发送确认邮件。

n 在首页上的某个区域中显示随机商品,这样可以通过显示商品的图片来吸引用户。 n 建立评级系统,让用户可以评价商品的优劣。

n 创建评论和评价系统,让用户写出商品带给他们的深刻印象。

n 创建销售报表。

使用本书中已经涉及到的技巧,您就能开发所有这些附加功能。只需静下心来考虑一下如何实现功能,然后就大胆地放手去做吧!

本文来源:https://www.bwwdw.com/article/clgr.html

Top