PHP MySQL Shopping Cart Tutorial by asurappule

VIEWS: 3,655 PAGES: 87

More Info
									         PHP MySQL Shopping Cart Tutorial

Yes, this is a another shopping cart tutorial. I am planning to make this tutorial to cover a more
sophisticated shopping cart solution but for now it only explains a basic shopping cart. I will
improve it in time so stay tuned.

Here is what we have in this site :

   1. Introduction
      This page explains the big picture. What kind of shopping cart software we will create.
      The shopping cart flow, file organizations, requirements and configuration.

   2. Database Design
      Here you will see what tables do we need and the relation between tables. An Entity
      Relationship (ER) diagram is also included so you can see the database relation better.

   3. Admin, Control Panel
      Where all shopping cart administration work takes place. This page explains the basic
      structure of the admin pages and brief explanation on each admin submodules.

   4. Admin, Login
      We have to create this one first of course. Making an administrator page without one is
      simply a crazy move :)

   5. Admin, View Category
      List all available categories and it's child categories.

   6. Admin, Add Category
      You can't build the online store if you don't have product categories, right ?

   7. Admin, Edit Category
      Modify existing categories, to change the name, description or maybe the image

   8. Admin, Delete Category
      Unused categories can be deleted here.

   9. Admin, View Products
      List all products in our online shop, search and view by category is also available

   10. Admin, Add Product
       After finish working with the category pages. We start writing the product scripts.

   11. Admin, Edit Product
       Modify every aspect of the product
12. Admin, Delete Product
    Remove product from the database.

13. Admin, Order Management
    Let's see how many orders we have, how many completed orders and abandoned ones.

14. Admin, Shop Configuration
    Here we can set several aspects in our shop. For now we can only set the shop
    information ( name, address, etc ), the currency and shipping cost.

15. Admin, User Management
    This page covers adding a user, modify password and delete user

16. Shop, Main page
    This is where the customers will go. They will browse around and view the products and
    hopefully if they're interested enough they'll put some products in the cart.

17. Shop, Browse Categories
    Just to add some navigation so the customer can browse around

18. Shop, View Product List
    Display all product in a category

19. Shop, View Product Detail
    Display detailed information about a product.

20. Shop, Add to Cart
    All interesting goodies goes here

21. View Shopping Cart
    Customer can view what's in her shoping cart. Probably modifying the product quantity
    or dump some products ( or even abandon the whole cart ).

22. Shop, Checkout
    Our real target, getting the sale.

23. Resources
    Some useful online resources like a study on shopping cart usability, shopping cart
    security issues, alternative shopping cart solutions, etc

24. Source code
    The source code files for this shopping cart tutorial.

25. Demo site
    Have a look on our completed shop. It's not much but it works.

26. Question and Answers
    Some questions that i received regarding this tutorial and the answers. Not much here for
       now but i'll try adding more. I need to dig my inbox to find all those questions and upload
       them here.

   27. Link Exchange
       Got a website? Would you exchange link with me? Just browse around the categories and
       put your site in the one you think fit the best.

   28. Book Store
       Just some books about e-commerce in general

By the way, what exactly your ideal shopping cart solution look like. If it's not too much trouble
could you let me know about it. What features do you want from a shopping cart software.Please
drop me an email and tell me what's on your mind
            Shopping Cart Tutorial Introduction
The online shop we're makin here is a basic one without any sophisticated features and stuff. The
shop has admin pages ( where the shop admin can create categories, add products, etc ) and the
shopper pages ( a.k.a the shop itself ) where all the shopping process takes place. You will learn
more about them in subsequent pages.

By the way, i did call this tutorial as a shopping cart tutorial but actually we're building an online
shop ( a really simple one ). The shopping cart is just part of the shop. But because the term
'shopping cart' is already common to define an online shop solution i just use it instead of naming
this site a 'PHP MySQL Online Shop Tutorial'.

I have to assume that you already know about PHP and MySQL so i won't explain every code in
detail. The codes are not too complicated though, i'm sure you can understand it. I suggest you
download the code first so you can run it on your computer. That way it's easier for you to
understand this tutorial

If you don't want to download the code that's fine but make sure you take a look at the demo site,
this is what our shopping cart willl look like.

After you browse around you will see that the basic flow of our shop is :

   1.   A customer visit the site
   2.   She browse the pages, clicking her way between categories
   3.   View the product details that she found interesting
   4.   Add products to shopping cart
   5.   Checkout ( entering the shipping address, payment info )
   6.   Leave ( hopefully to return another time )

Nothing complex here. The customer doesn't need to register for an account. She just buy then leave.



Features

Okay, here are the shop features ( or maybe i should call this restrictions )

   1. Flat shipping cost.
      No complex shipping calculation for this shop right now and i don't have any plan to change this in
      near future.

   2. Payment options including COD ( cash on delivery ) and Paypal
      For now this shop can only handle COD and payment through Paypal IPN. The reason i pick paypal is
      because they provide excellent resource for developers so i can test the payment process easily.

   3. Configurable image and thumbnail size
       You can restrict the product image width from the config file. You can also set the thumbnail width
       you want for all product images that you upload.




File Organization

The files in our shop will be organized like this :




The plaincart/library directory contain :

      config.php : this is the main configuration file for our shop
      category-functions.php :functions required for fetching the categories
      product-functions.php : contain product related functions
      cart-functions.php : shopping cart specific functions
      checkout-functions.php : checkout processes are in here
      common.php : common functions required for the shop and admin pages
      database.php : contain the database abstraction functions

The plaincart/include contain :

      header.php
       The shop common header.

      top.php
       You can place your shop banner here.

      footer.php
       Common footer, display the shop address, phone number and email. You can add more information
       here when needed.

      shop.css
       Style sheet file for our shop

      leftNav.php
       The left navigation you see on the shop

      categoryList.php
       Show the top categories we have

      productList.php
       Show the products in certain category

      productDetail.php
       You know what this is for, right ?

      miniCart.php
       Shown on the right portion of the shop pages, it shows the products in the shoping cart.

      shippingAndPaymentInfo.php
       The form to enter shipping and payment info ( step 1 of checkout )

      checkoutConfirmation.php
       Show the order items, shipping & payment info ( step 2 of checkout )

The plaincart/include/paypal directory contain :

      paypal.inc.php
       The configuration file for the paypal payment module

      ipn.php
       The script that process payment verification

      payment.php
       Contain the form that submit the payment information from this website to paypal website

The plaincart/admin folder will contain all the admin files.You can see that admin folder also
contain include and library folder. These will contain specific library files for the admin pages

All images required in our shop will be put in plaincart/images directory. The category and product
images are put in the category and product sub-folder respectively.



The Requirements
For this tutorial i'm using these :

       Apache 2
       PHP 4.3.10 with GD ( graphics library ) support ( you can also use lower version but >= 4.3.7 )
       MySQL 4

If you don't have these ready check out this tutorial to install them : Installing Apache PHP &
MySQL



What Configurations You Will Need
Database Configuration

When you install the shopping cart script you will need to modify library/config.php. You need to
change the database connection info ( database host, username, password and name ) according to
your own configurations.

Enabling GD Support

The next thing you may need to do is to enable the GD support. This is usually enabled by default by
web hosting company but in case you test it on your own computer you may need to enable it
manually.

First, open the php.ini file using a text editor ( notepad is okay ) and search for this line of code :

extension=php_gd2.dll

If you see that code preceded by a semicolon ( ; ) that means GD library is not enabled yet. Remove
the semicolon to enable GD and then restart the web server ( apache ) for the changes to take effect.



I really hope this shopping cart tutorial is useful for you. Now to take the first step, let's start with the
database design
                Shopping Cart Database Design
The database design for our shopping cart is quite simple. Below is the summary of what tables we
need for this shopping cart plus the short description of each table. You can see the complete SQL
needed to build the database here

                         Table Name                       Description

                   tbl_category             Storing all product categories

                   tbl_product              The products ( what else )

                                            When the shopper decided to put an
                   tbl_cart                 item into the shopping cart we'll add
                                            the item here

                   tbl_order                This is where we save all orders

                   tbl_order_item           The items ordered

                   tbl_user                 Store all shop admin user account

                                            Contain the shop configuration like
                   tbl_shop_config          name, address, phone number, email,
                                            etc




The ER ( Entity Relationship ) diagram is shown below.
Now, let's take a better look at each table

tbl_category

This table store the product categories. From the ER diagram you can see that our current database
design enables a category to have a child category and for the child category to have another child
category and so on. But for this tutorial we make a restriction that the category will only two level
deep like this "Top Category > Manga > Naruto". The reason is to reduce the number of clicks
required by a visitor when browsing a category.
Another rule is that a product can only be added on the second level category. For example if we
have this category structure :

Top Category > Manga > Naruto

then we can only add a product in "Naruto", not in "Manga". The top level categories will not
contain any products and a product can only belong to one category.



tbl_product

In this table we store the product's name, category id, description, image and thumbnail. For now a
product can only have one image. It may not be enough if you want to show a picture of you product
from multiple angles so i plan to improve this on future version.

When adding a product image in the admin page we don't need to upload the thumbnail too. The
script will generate the thumbnail from the main image. The thumbnail size is defined in
library/config.php ( THUMBNAIL_WIDTH ) and currently it is set to 75 pixels.



tbl_cart

This table will store all items currently put by the customer. Here we have ct_session_id to save the
id of a shopping session. We will explore this further when adding a product to shopping cart



tbl_order

Finally when the customer finally place the order, we add the new order in this table. The shipping
and payment information that the customer provided during checkout are alos saved in this table
including the shipping cost.

For the order id i decided to use an auto increment number starting from 1001.

Why start at 1001 ?

Because an order id looks ugly ( at least for me ^^ ) if it' s too short like 1, 2 or 3 so starting the order
id from 1001 seems to be a good idea for me.

To make the order id start from 1001 we use the following sql :

CREATE TABLE tbl_order (
  id int(10) unsigned NOT NULL auto_increment,
  date datetime default NULL,
  last_update datetime NOT NULL default '0000-00-00 00:00:00',
  status enum('New', 'Paid', 'Shipped','Completed','Cancelled') NOT NULL default 'New',
  memo varchar(255) NOT NULL default '',
  shipping_first_name varchar(50) NOT NULL default '',
  shipping_last_name varchar(50) NOT NULL default '',
  shipping_address1 varchar(100) NOT NULL default '',
  shipping_address2 varchar(100) NOT NULL default '',
  shipping_phone varchar(32) NOT NULL default '',
  shipping_city varchar(100) NOT NULL default '',
  shipping_state varchar(32) NOT NULL default '',
  shipping_postal_code varchar(10) NOT NULL default '',
  shipping_cost decimal(5,2) default '0.00',
  payment_first_name varchar(50) NOT NULL default '',
  payment_last_name varchar(50) NOT NULL default '',
  payment_address1 varchar(100) NOT NULL default '',
  payment_address2 varchar(100) NOT NULL default '',
  payment_phone varchar(32) NOT NULL default '',
  payment_city varchar(100) NOT NULL default '',
  payment_state varchar(32) NOT NULL default '',
  payment_postal_code varchar(10) NOT NULL default '',
  PRIMARY KEY ( id)
) TYPE=MyISAM AUTO_INCREMENT=1001 ;

You see, we just need to add AUTO_INCREMENT = 1001 right after the create definition.



tbl_order_item

All ordered items are put here. We simply copy the items from the cart table when the customer
place the order.



tbl_shop_config

This table store the shop information. For now it only have the shop name, address, phone number,
contact email address, shipping cost, the currency used in the shop and a flag whether we want to
receive an email whenever a customer place an order.



tbl_user
This table save all the user or admin account. Currently all user is an admin and all can do
everything to the shop. I'm planning to add permission level so one admin can do everything, while
the other user can only add / update product, manage orders, etc.

By the way, we will be using indexes on the tables to speed up queries. As a matter of fact whatever
application you make using indexes is a good idea because it can improve the database query
performance.



Okay, next we talk about the database abstraction. It's not a difficult stuff so you can skim read it if
you like.
                             Database Abstraction
For our shopping cart we will use a simple database abstraction library. Although this tutorial only
use MySQL but using an abstraction layer is still a good thing. We do this just in sow we can easily
migrate to another database.

In case you don't know, a database abstraction is simply making our own interfaces ( functions ) to
call the database function provided by PHP. This way if we change to another database or if the PHP
function itself change we just need to modify one file ( database.php ) instead of modifying all our
source code.

The database functions is stored in library/database.php. You can see the content below


<?php
require_once 'config.php';

$dbConn = mysql_connect ($dbHost, $dbUser, $dbPass) or die ('MySQL connect failed. ' .
mysql_error());
mysql_select_db($dbName) or die('Cannot select database. ' . mysql_error());

function dbQuery($sql)
{
  return mysql_query($sql) or die('Query failed. ' . mysql_error());
}

function dbAffectedRows()
{
  global $dbConn;

    return mysql_affected_rows($dbConn);
}

function dbFetchArray($result, $resultType = MYSQL_NUM)
{
  return mysql_fetch_array($result, $resultType);
}

function dbFetchAssoc($result)
{
  return mysql_fetch_assoc($result);
}

function dbFetchRow($result)
{
    return mysql_fetch_row($result);
}

function dbFreeResult($result)
{
  return mysql_free_result($result);
}

function dbNumRows($result)
{
  return mysql_num_rows($result);
}

function dbSelect($dbName)
{
  return mysql_select_db($dbName);
}
?>



How to use it ?

Below you can see an example on how to use this database abstration library. The first one is using
PHP database function directly :

<?php
require_once 'config.php';

$conn = mysql_connect($dbHost, $dbUser, $dbPass);
mysql_select_db($dbName);

$sql = "SELECT *
FROM tbl_product
ORDER BY pd_name";

$result = mysql_query($sql);

$products = array();
while ($row = mysql_fetch_assoc($result)) {
$products[] = $row;
}
?>

And the second one is using our database abstraction. We don't have to initiate the connection this
time because it is done from database.php :

<?php
require_once 'config.php';
require_once 'database.php';

$sql = "SELECT *
FROM tbl_product
ORDER BY pd_name";

$result = dbQuery($sql);

$products = array();
while ($row = dbFetchAssoc(&$result)) {
$products[] = $row;
}
?>

As you can see, using database abstraction isn't so hard at all.

Now that we have taken care the database issues we start building the admin pages.
                            Admin Control Panel
Our shopping cart admin page consist of the following :

      Category
          o Add Category
             Add a new category.

           o   View Category
               List all the category we have. We can also see all the child categories and show many
               products in each category

           o   Modify Category
               Update a category information, the name, description and image

           o   Delete Category
               Remove a category. All products in it will be set to have cat_id = 0.

      Product
          o Add Product
              Insert an item into our store. We also need to supply the product image and we'll
              create a thumbnail automatically from this image

           o   View Product
               View all the products we have. Since our online shop can have many products we can
               view the products grouped by category.

           o   Modify Product
               Modify product information. We can also remove the product image from this page

           o   Delete Product
               Remove a product from the shop

      Order
           o   View Orders
               Here we can see all the orders we have and their status. When you click the "Order"
               link on the left navigation you will go straight to the "Paid" orders. The reason is so
               you can respond immediately upon your customers that already paid for their
               purchase.

           o   Modify Orders
               Sometimes a customer might contact us saying that she made the wrong order like
               specifying the wrong product quantity or simply want her order cancelled so she can
               repeat the buying process again. This page enables the admin to do such a thing.

      Shop Configuration
       This is where we can set and change our online shop appearance, behaviour and information
        ( like the shop name, main url, etc ).

Below is what admin main page ( admin/index.php ) look like.




By the way the shopping cart name is PlainCart :-)

Each sub-module (category, product, etc) will have similar file structure. They are :

       index.php
       list.php
       add.php
       modify.php
       process<sub-module name>.php

The admin/index.php only serves as a simple display when the admin enters the administrator
section. On this page (and all other pages in the admin sections ) we check if the one requesting the
file is already logged in or not. This way we can be sure that anyone who plays around with the
admin pages are those who have the required permission.

All admin pages will be using the same template so they will all look alike. Basically each admin file
will set the page title, what javascript to include and the main content. If you want to customize the
look of the admin pages you only need to modify the template and the css file ( admin.css ) . Here is
the code for template.php

Source code : admin/include/template.php

<?php
if (!defined('WEB_ROOT')) {
   exit;
}

$self = WEB_ROOT . 'admin/index.php';
?>
<html>
<head>
<title><?php echo $pageTitle; ?></title>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
<link href="<?php echo WEB_ROOT;?>admin/include/admin.css" rel="stylesheet" type="text/css">
<script language="JavaScript" type="text/javascript" src="<?php echo
WEB_ROOT;?>library/common.js"></script>
<?php
$n = count($script);
for ($i = 0; $i < $n; $i++) {
if ($script[$i] != '') {
echo '<script language="JavaScript" type="text/javascript" src="' . WEB_ROOT. 'admin/library/' .
$script[$i]. '"></script>';
}
}
?>
</head>
<body>
<table width="750" border="0" align="center" cellpadding="0" cellspacing="1" class="graybox">
<tr>
<td colspan="2"><img src="<?php echo WEB_ROOT; ?>admin/include/banner-top.gif"
width="750" height="75"></td>
</tr>
<tr>
<td width="150" valign="top" class="navArea"><p>&nbsp;</p>
<a href="<?php echo WEB_ROOT; ?>admin/" class="leftnav">Home</a>
<a href="<?php echo WEB_ROOT; ?>admin/category/" class="leftnav">Category</a>
<a href="<?php echo WEB_ROOT; ?>admin/product/" class="leftnav">Product</a>
<a href="<?php echo WEB_ROOT; ?>admin/order/?status=New" class="leftnav">Order</a>
<a href="<?php echo WEB_ROOT; ?>admin/config/" class="leftnav">Shop Config</a>
<a href="<?php echo WEB_ROOT; ?>admin/user/" class="leftnav">User</a>
<a href="<?php echo $self; ?>?logout" class="leftnav">Logout</a>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p></td>
<td width="600" valign="top" class="contentArea"><table width="100%" border="0"
cellspacing="0" cellpadding="20">
<tr>
<td>
<?php
require_once $content;
?>
</td>
</tr>
</table></td>
</tr>
</table>
<p>&nbsp;</p>
<p align="center">Copyright &copy; 2005 - <?php echo date('Y'); ?> <a
href="http://www.phpwebcommerce.com"> www.phpwebcommerce.com</a></p>
</body>
</html>

You see the bolded section on the top? All file that is meant to be included from another file will
have this right at the beginning of the code. So if we request the file directly like this :
plaincart/admin/include/template.php it won't display anything.

But first, we have to postpone making the admin pages and create the login page first. Because a
shop admin certainly must login before doing anything to the shop.

NOTE : When you play with the admin pages demo you will see that any changes you make doesn't
have any effect at all. This is because i've commented some of the code in the demo. I do this just to
make sure all the settings, products, and categories stay the same. The code for download, however,
are not commented. So when you install it on your server you can make any changes as you wish
                                       Admin Login
All user account is saved in tbl_user. For simplicity the table will only contain the bare necessities
such as user id and password. You can add more column if you want to.

This is how the login works

   1.   The admin enter it's username and password
   2.   The script check whether that username and password combination do exist in the database
   3.   If it is set the session then go the admin main page
   4.   If it's not then show an error message

Below is the login page screenshot :




The default user name and password are "admin" ( without the quotes )


The login function is called doLogin() and it's located in admin/library/functions.php

Source code : admin/library/functions.php

function doLogin()
{
  // if we found an error save the error message in this variable
  $errorMessage = '';

 $userName = $_POST['txtUserName'];
 $password = $_POST['txtPassword'];

 // first, make sure the username & password are not empty
 if ($userName == '') {
    $errorMessage = 'You must enter your username';
 } else if ($password == '') {
    $errorMessage = 'You must enter the password';
 } else {
        // check the database and see if the username and
        // password combo do match
        $sql = "SELECT user_id
                FROM tbl_user
                WHERE user_name = '$userName' AND
                        user_password = PASSWORD('$password')";
        $result = dbQuery($sql);

        if (dbNumRows($result) == 1) {
             $row = dbFetchAssoc($result);
             $_SESSION['plaincart_user_id'] = $row['user_id'];

           // log the time when the user last login
           $sql = "UPDATE tbl_user
                    SET user_last_login = NOW()
                    WHERE user_id = '{$row['user_id']}'";
           dbQuery($sql);

            // now that the user is verified we move on to the next page
            // if the user had been in the admin pages before we move to
            // the last page visited
            if (isset($_SESSION['login_return_url'])) {
              header('Location: ' . $_SESSION['login_return_url']);
              exit;
            } else {
              header('Location: index.php');
              exit;
            }
        } else {
            $errorMessage = 'Wrong username or password';
        }

    }

    return $errorMessage;
}

If the login is successful this function will set the session variable $_SESSION['plaincart_user_id'].
All admin pages will check for this session id using the checkUser() function. If the session id is not
found then the function will set a redirection to the login page.

The checkUser() function look like this :

Source code : admin/library/functions.php

function checkUser()
{
    if (!isset($_SESSION['plaincart_user_id'])) {
       header('Location: ' . WEB_ROOT . 'admin/login.php');
    }

    if (isset($_GET['logout'])) {
       doLogout();
    }
}

You see that if $_SESSION['plaincart_user_id'] is not set we just redirect to the login page. Very
simple right?

Another thing that this function check is if there's a 'logout' in the query string. If it is then we call
the doLogout() function which will remove the session id.

Source code : admin/library/functions.php

function doLogout()
{
  if (isset($_SESSION['plaincart_user_id'])) {
     unset($_SESSION['plaincart_user_id']);
     session_unregister('plaincart_user_id');
  }

    header('Location: login.php');
}

Next we start making the category pages
                        Admin - View Category
All the product categories for the online shop are listed here. The sql query for it is pretty simple.
We just select category id, parent id and name and using a while loop we show the category one
by one.

Below is the screenshot for the category list page. You ca see that on each row there's a Modify
link and Delete link. Clicking on the Modify link will take you to the category modification page
( admin/category/modify.php ) where you can update the category name, description and image.
Clicking on the Delete link will pop a javascript confirmation box asking whether you are sure to
delete the category. Using a confirmation is a must when you want to delete something. This will
prevent stupid accident where you unknowingly click on the delete link and suddenly the
category disappear before you even realize what's going on.




Take a look at the code snippet below :

Source code : admin/category/list.php

<?php
// ...
$catId = (isset($_GET['catId']) && (int)$_GET['catId'] >= 0) ?
(int)$_GET['catId'] : 0;
//...
?>



When the page loads we check for the existence of catId ( category id ) in the query string. This
category id is then used as the paramater for the javascript function addCategory() . When you
click on the 'Add Category' button the parent id will be sent to category/add.php.
Go look at the source code and scroll to the bottom you will see this code :

Source code : admin/category/list.php

<input name="btnAddCategory" type="button" id="btnAddCategory"
value="Add Category" class="box"
onClick="addCategory(<?php echo $catId; ?>)">




The addCategory() function is defined in admin/library/category.js. It simply perform a redirect
to show the add category page. More detail on adding a category can be found on the next page.
                           Admin - Add Category
Here the admin can add new product category for the online shop. The information we need are the
category name, description, image. Both the name and description are mandatory but the image is
not. If we don't have the category image we can leave the field blank. Of course it's not
recommended because when the customer come to the shop she will see the default image.

The category description here will not be shown anywhere on the shop. It's only purpose is to let the
shop owner / admin to know what the category is all about.

Take a look at the snapshot below, nothing fancy right?




Take a look at the form source code. The form has a hidden variable called hidParentId. The value is
set from category/list.php as explained on the previous page. Go look at the source code and scroll to
the bottom you will see this code :

<input name="btnAddCategory" type="button" id="btnAddCategory"
value="Add Category" class="box"
onClick="addCategory(<?php echo $catId; ?>)">


When you submit the form the process then handed to processCategory.php. All kind of category
processing ( add, modify, delete ) are done in this file. On top of the script there's a simple switch to
call the appropriate function based on the action required.

$action = isset($_GET['action']) ? $_GET['action'] : '';

switch ($action) {
    case 'addCategory' :
        addCategory();
        break;

     case 'modifyCategory' :
          modifyCategory();
          break;

     case 'deleteCategory' :
         deleteCategory();
         break;

     case 'deleteImage' :
         deleteImage();
         break;

     default :
         // if action is not defined or unknown
         // move to main category page
         header('Location: index.php');
}




On the add category form the form action is set as processCategory.php?action=addCategory so if
you look at the code above the script will call addCategory();. If no action is defined we just redirect
to category main page.

When saving the product image there is a possibility of name conflict. It may seem weird for two
categories to have the same image name, but in some cases it can happen. To avoid such conflict we
will generate a new name for each category image we upload using the combination of rand(), time()
and md5() functions like this :

// get the image extension
$ext = substr(strrchr($image['name'], "."), 1);

// generate a random new file name to avoid name conflict
$imagePath = md5(rand() * time()) . ".$ext";

The image name wil then become something like 6c444ed816ce251d610c25154dc28462.jpg. Now
it's almost impossible for us to ever hit the name conflict problem. We will use the same name
generation for the product image and thumbnail.

How does it work ?

The time() function will return the number of seconds elapsed since the beginning of ( computer )
time which is January 1, 1970. Using rand() function we get a random value less or equal to the
number of seconds. We need to use rand() because this shopping cart can have more than one admin.
If two admins happen to submit the form at the same second the result of time() will be the same.

As the final step md5() use the random value and return the hash ( a string with 32 characters ). If
you feel that using 32 characters for a filename is too much you can use substr() function to cut it
like this :

// get the image extension
$ext = substr(strrchr($image['name'], "."), 1);

// generate a random new file name to avoid name conflict
$imagePath = substr(md5(rand() * time()), 0, 10) . ".$ext";

The code above will use only the first ten characters as the file name.

Next is the modify category page.
                           Admin - Edit Category
This page is where you can modify a category information. You can see that the form is just a lame
copy from add.php. The difference is that in this page we need to fetch the category information first
so we can show it in the input boxes.




Another difference is that in this form we also display the category image. If you change the
category image then the old image will be deleted from the server and the new image is uploaded.

Take a look a the code below. To get the category info from database we need the category id from
the query string. If $_GET['catId'] is not present or empty we just redirect to index.php. If it's present
and not empty we fetch the category info.


Source code : admin/category/modify.php

<?php
if (!defined('WEB_ROOT')) {
   exit;
}

// make sure a category id exists
if (isset($_GET['catId']) && (int)$_GET['catId'] > 0) {
   $catId = (int)$_GET['catId'];
} else {
   header('Location:index.php');
}
$sql = "SELECT cat_id, cat_name, cat_description, cat_image
     FROM tbl_category
     WHERE cat_id = $catId";
$result =& dbQuery($sql);
$row =& dbFetchAssoc(&$result);
extract($row);

// ... put the form down here
?>

On the screenshot you can see that we next to the category image we have a delete link. Clicking on
the link will call the javascript function deleteImage(). This function will pop a confirmation box and
if you confirm the deletion the function will redirect you to processCategory.php where all category
related process is taken care of.

Below is the code that perform the image deletion

Source code : admin/category/processCategory.php

function deleteImage()
{
  if (isset($_GET['catId']) && (int)$_GET['catId'] > 0) {
     $catId = (int)$_GET['catId'];
  } else {
     header('Location: index.php');
  }

    _deleteImage($catId);

    // update the image name in the database
    $sql = "UPDATE tbl_category
         SET cat_image = ''
         WHERE cat_id = $catId";
    dbQuery($sql);

    header("Location: index.php?view=modify&catId=$catId");
}

To delete the image from the server the deleteImage() function calls _deleteImage(). Please excuse
this lame function naming. I just cant' find other name that fit perfectly for this function.

After deleting the image we update the category information in database. We only need to set
cat_image to an empty string and we're done. The final thing that deleteImage() do is redirect back to
the category modification page. We don't redirect to category listing page because the admin may
still want to modify the category further.
                       Admin - Delete Category
When you decide that a category is no longer needed you can remove it from the database. To
delete a category go to the category listing page and click on the 'delete' link on the category you
wish to delete

Deleting a category will delete all product in that category and in all it's children. For example
if you delete the "Manga" category than alll product in "Naruto" and "HunterxHunter" will also
be deleted. The function used to delete category is called deleteCategory() and it's located in
admin/category/processCategory.php

The deletion process is like this :

   1. Update the cat_id for all products in that category to zero
   2. Remove the category image ( if exist )
   3. Delete the category from database

Pretty easy huh? I don't think we need to dive into the source code here because it is quite
simple. Just take a look at the code and you will understand

Okay, now the category stuff is done we start playing with the next sub modul, the product
pages.
                           Admin - Add Product
Adding a product for the online shop is a straightforward process. Just enter the product
information and hit the 'Add Product' button. When adding a product we will require these
information :

      Category
      Product name
      Description
      Price
      Quantity in stock
      Image

All is mandatory except for the image. We can add the product image later.The add product form
look like this :




Not much difference from the add category form. We just have more input box. On top of the
form you can see the category combo box. We build this so that you can only select the second
level category. This is to ensure that all product are added on the second level category and not
put in the top level category by mistake.
The one responsible to build the list is the buildCategoryOptions() in admin/library/function.php.
Below is the snippet for the code that build the combo box. Just in case you will need the ability
to add some product to the first level category you can replace the buildCategoryOptions() with
the commented function code written below it.

If you happen to click the "Add Product" button while viewing the product list in a category you
can see that the category list is pre-selected to the right category. When the function is building
the list options it always check if the current category id is the same as the category id in the
function parameter.

Source code : admin/library/functions.php

<?php
// ... some code to fetch the categories from database

// build combo box options
$list = '';
foreach ($categories as $key => $value) {
   $name = $value['name'];
   $children = $value['children'];

    $list .= "<optgroup label=\"$name\">";

    foreach ($children as $child) {
      $list .= "<option value=\"{$child['id']}\"";
      if ($child['id'] == $catId) {
         $list.= " selected";
      }

        $list .= ">{$child['name']}</option>\r\n";
    }

    $list .= "</optgroup>";
}

// ... more code here

?>

The product quantity is limited to 65,535 since in the table definition we only use smallint(5) . I
find this number adequate for most online shops. But if do carry more than that amount for an
item just change the data type to something bigger like mediumint(8).

The product image you need to supply is the large size product image which will be shown in the
product detail page. The script will generate a thumbnail for it to be shown in the product
browsing page. We define the maximum image size and the thumbnail size in config php.
We need to restrict the image size so it won't destroy the site layout. Imagine if the image is 1000
pixels wide and 2000 pixels high. It will make the product detail page look awful. Image resizing
can be turned on or off. If you set LIMIT_PRODUCT_WIDTH to false on config.php the script
will just upload the image without worying about it's size. It's not recommended though.

Please note that the image resizing function can only handle jpeg and gif image. That function
was actually taken from the php documentation and i don't know how to improve it yet.
                         Admin - View Product
This page list all the products we have. We can see all products or just products from certain
category. From this page we can see the product detail , add new product, modify and delete.

Below is what the page look like. The table shows the product name, thumbnail, category, and
modify and delete link. We show the product image if it exist. In case we haven't supply the
image the default image is shown instead.




There's a javascript function called viewProduct() attached to the combo box on the top right
portion of the page. If you select a category then the page will show product list only from that
category. For example if you choose "Naruto" the this page will look like this :
If you wish to view all products again just choose "All Category" from the combo box

The code for this page is in admin/product/list.php. It's a very simple page really. It just perform
a simple SELECT query and loop through the result to print the contents. If the products returned
from the query is more than five we print the paging links to navigate from one result page to
another. The code snippet below shows how the paging is done.

Source code : admin/product/list.php

<?php
if (!defined('WEB_ROOT')) {
   exit;
}


if (isset($_GET['catId']) && (int)$_GET['catId'] > 0) {
   $catId = (int)$_GET['catId'];
   $sql2 = " AND p.cat_id = $catId";
   $queryString = "catId=$catId";
} else {
   $catId = 0;
   $sql2 = '';
   $queryString = '';
}
// for paging
// how many rows to show per page
$rowsPerPage = 5;

$sql = "SELECT pd_id, c.cat_id, cat_name, pd_name, pd_thumbnail
        FROM tbl_product p, tbl_category c
        WHERE p.cat_id = c.cat_id $sql2
        ORDER BY pd_name";
$result = dbQuery(getPagingQuery($sql, $rowsPerPage));
$pagingLink = getPagingLink($sql, $rowsPerPage, $queryString);

$categoryList = buildCategoryOptions($catId);

?>

Instead of executing the query directly like this dbQuery($query) we add some paging code first
to the sql query by feeding it to getPagingQuery() along with how many results that we want to
show on each page. The getPagingQuery() function is located in library/common.php. Here is the
code :

Source code : library/common.php

function getPagingQuery($sql, $itemPerPage = 10)
{
  if (isset($_GET['page']) && (int)$_GET['page'] > 0) {
     $page = (int)$_GET['page'];
  } else {
     $page = 1;
  }

    // start fetching from this row number
    $offset = ($page - 1) * $itemPerPage;

    return $sql . " LIMIT $offset, $itemPerPage";
}

This function first check the page number. When you click on a paging link there's a page
variable embedded in the query string. If the function can't find any page variable on the query
string then it just assume that the first page is wanted.

Paging on MySQL is done using the LIMIT keyword. The offset is the index where we want to
start fetching the result. We also supply how many result that we want .

Now after we get the paging query and execute it the next thing we must do is making the page
links. This is done by getPagingLink() function. This function is the one responsible for printing
the paging link you see on the bottom of the product list. The function only does the followings :
   1.   Find out how many total results returned by a query
   2.   Calculate how many pages the results should be split into
   3.   Determine the first and last page
   4.   Print page link from first to last.

Somehow i don't feel like explaining the paging process in detail here. If you're interested you
can read the full tutorian on pagination here : paging tutorial on www.php-mysql-tutorial.com

Next we discuss about adding a new product. When you click on the "Add Product" button you
will be taken to the add product screen.
                         Admin - Edit Product
I don't think i need to explain this because it's the same as modifying a category. We show the
form where we can change the product information. Display the image thumbnail if the product
has an image and put a delete link right next to it.

Here is the form snapshot :




The process of updating the product information is also the same as the category. So we better
move on and working on the process of deleting a product.
                         Admin - Delete Product
This part also don't need much explanation. The process is simple. First we delete any references
to this product from tbl_cart and tbl_order_item to maintain data integrity. Then remove the
product images and thumbnail and finally remove the product from database.

The delete process is like this :

    1.   Delete all references to this product from tbl_order_item
    2.   Delete all references from tbl_cart
    3.   Delete the product image and thumbnail
    4.   Delete the product from database

And here is the code for deleteProduct() function that responsible for this process :

Source code : admin/product/processProduct.php

function deleteProduct()
{
  if (isset($_GET['productId']) && (int)$_GET['productId'] > 0) {
     $productId = (int)$_GET['productId'];
  } else {
     header('Location: index.php');
  }

  // remove any references to this product from
  // tbl_order_item and tbl_cart
  $sql = "DELETE FROM tbl_order_item
        WHERE pd_id = $productId";
  dbQuery($sql);

  $sql = "DELETE FROM tbl_cart
       WHERE pd_id = $productId";
  dbQuery($sql);

  // get the image name and thumbnail
  $sql = "SELECT pd_image, pd_thumbnail
        FROM tbl_product
        WHERE pd_id = $productId";

  $result =& dbQuery($sql);
  $row =& dbFetchAssoc($result);

  // remove the product image and thumbnail
  if ($row['pd_image']) {
        unlink(SRV_ROOT . 'images/product/' . $row['pd_image']);
        unlink(SRV_ROOT . 'images/product/' . $row['pd_thumbnail']);
    }

    // remove the product from database;
    $sql = "DELETE FROM tbl_product
          WHERE pd_id = $productId";
    dbQuery($sql);

    header('Location: index.php');
}
                    Admin - Order Management
All customer order will be shown here. You can see the orders and take appropriate action. For
example all orders initially have the status "New". When you pack the product and ship it to the
customer you can then change the order status to "Shipped".

The order status are :

       New
       Paid
       Shipped
       Completed
       Cancelled

Let's take a better look at each status

New

All orders initially have this status

Paid

An order's status is changed from "New" to "Paid" by the IPN script after completing the
payment process.

Shipped

After we pack the ordered items and ship it we can change the order status to "Shipped"

Completed

We got the payment, the customer receveived the goods that mean the order is completed

Cancelled

In case you see a suspicious order and you feel that it's a fraud you can set the status to
"Cancelled". Or in some case a customer calls you and for some reason she ask you to cancel her
order.

Here is what the order page look like :
Using the combo box on the top right corner we can view the orders with a certain status.
Clicking on an order number will take you to the order detail page. On this page we can also
modify the order status
                             Site Configuration
Here we can set some settings which are used sitewide. They are

      Online shop information
           o Shop Name
           o Real world address
           o Phone number
           o Contact email
      Shipping cost
      Currency
      A flag whether to send a notification email to admin when a customer place an order

The shop information is shown on the footer ( include/footer.php ). You really shouldn't leave
any field blank. Having these information can assure the customers that your online shop is a
real business. This can improve customer trust and also improve your credibility and without
those two you really can't expect anyone to buy.

The currency chooser is simply a drop down box where you can choose between Dollar, Euro,
Poundsterling or Yen.

Why only four currencies ?

Because i'm making the shop using Dreamweaver and the only special characters for currency
that i can find are €, £, and ¥. Plus the dollar sign ( $ ) that makes four currencies.
Under the currency list is the shipping cost. Since this shop use flat shipping cost a simple text
box is sufficient. If you plan to offer different shipping cost like depending on the product weight
or shipping courier then you really have to change it to suit your need.

The last setting is for sending email whenever a new order is placed.The notification email is
sent to the address you specify as the contact email ( in the screenshot above it's
franky@tomsworkers.com ). If you run a super busy shop it would be better to turn this off
otherwise you will be flooded with emails and your mail server might think someone is
spamming you.
                    Admin - User Management

A user of the shop is actually the shop admin itself. Currently all user are granted the permission
to do all administration task. In future version i'll modify this so one user can be assigned to
specific task such as managing the products or managing the orders, etc.

View User List

This display just display all user on the shop




Add User

The only information needed are the user name and password.




Here is the code for adding a user.

Source code : admin/user/processUser.php

function addUser()
{
  $userName = $_POST['txtUserName'];
  $password = $_POST['txtPassword'];

 // check if the username is taken
 $sql = "SELECT user_name
      FROM tbl_user
        WHERE user_name = '$userName'";
    $result =& dbQuery($sql);

    if (dbNumRows($result) == 1) {
       header('Location: index.php?view=add&error=' .
         urlencode('Username already taken. Choose another one'));
    } else {
       $sql = "INSERT INTO tbl_user (user_name, user_password,
                        user_regdate)
            VALUES ('$userName', PASSWORD('$password'), NOW())";

        dbQuery($sql);
        header('Location: index.php');
    }
}

Before adding the new user info we need to check if the username is taken or not. We just query
the database looking for that username. If a row is found that mean the username is already used
and we set a redirect to go back to the 'add user' page and send an error message.

Since the error message is put in the query string we need to use urlencode(). This function is
commonly used when passing a string to a url. Using the above example the url will look like
this :

index.php?view=add&error=Username+already+taken.+Choose+another+one

If the username is still available we just insert the new user info to database. You see that in the
insert query we use PASSWORD() function. This is a MySQL function that will encrypt the
given password.



Modify User Password

For simplicity the user name cannot be changed. Only the password can be changed. The
function used for modifying the password simply perform an UPDATE query to update the
password
Delete User

The deleteUser() function just remove a user from database. No extra steps needed.
                                Shop - Main Page
This page is usually what the visitor see for the first time when she visit our shop. It consist of five
parts. On the left is the category browser. The shopper can click here way through categories to find
the product. The right side is where we put the mini shopping cart. If the visitor add a product, this
mini cart will show the item. At the very top and bottom are the common header an footer. The top
area is usually where we put our store logo. For this tutorial the bottom area is used for displaying
the store information (address, email, phone )




The center part is the main area. Here we show the product categories and products. The visitor will
( hopefully ) find her way through the shop, find the item she want, put it into the shopping cart and
then buy.

What we show in the main area depends on the visitor action. When she first arrive to main page she
get the category list. If she click on one of the category then we show the product list for that
category. And if she click on a product from the list we show the product detail.


Take a look at the code below, it's the code for index.php, the main page

Source code : index.php

<?php
require_once 'library/config.php';
require_once 'library/category-functions.php';
require_once 'library/product-functions.php';
require_once 'library/cart-functions.php';

$_SESSION['shop_return_url'] = $_SERVER['REQUEST_URI'];

$catId = (isset($_GET['c']) && $_GET['c'] != '1') ? $_GET['c'] : 0;
$pdId = (isset($_GET['p']) && $_GET['p'] != '') ? $_GET['p'] : 0;

// ... more code here
?>

First we load the required libraries. The config.php file contain all kind of initialization like setting
the database connection, defining constants, and it also load the common libraries. I won't explain
the detail, just take a look at the file and i'm sure you will understand what it does.

Next is category-functions.php. This file contain the several functions, they are :

        formatCategories()
         This function is used by include/leftNav.php. It's job is to generate the category list where the
         currently selected category will expand and show the children categories ( if it have any ).

        getCategoryList()
         Get a list of first level categories ( i.e. the parent id is zero )

        getChildCategories()
         Get all children categories

        fetchCategories()
         Get all categories ( first and second level )

After finish loading the libraries we set a session variable $_SESSION['shop_return_url']. The value
for this session is the url of the page that we currently see. This variable is used by the shopping cart.
When you click on the 'Continue Shopping' button it will redirect to the url pointed by this session
variable.

Next step is checking the existence of $_GET['c'] ( category id ) and $_GET['p'] ( product id ). If
none exist in the query string we show the category list ( categoryList.php ). If only the category id
exist then we show all product in that category ( productList.php ). And if the product id also exist
we show that product information ( productDetail.php ).

Source code : index.php

<?php
// ... previous code

include 'header.php';
?>
<table width="780" border="1" align="center" cellpadding="0" cellspacing="0">
<tr>
<td colspan="3">
<?php include 'top.php'; ?>
</td>
</tr>
<tr valign="top">
<td width="150" height="400" id="leftnav">
<?php
include 'leftNav.php';
?>
</td>
<td>
<?php
if ($pdId) {
   include 'productDetail.php';
} else if ($catId) {
   include 'productList.php';
} else {
   include 'categoryList.php';
}
?>
</td>
<td width="130" align="center"><?php include 'miniCart.php'; ?></td>
</tr>
</table>
<?php
include 'footer.php';
?>

As you can see from the above code, the main page doesn't do much. It "out sourced" almost
everything. The header, navigation, content, mini cart and the footer. Of course this is a good thing
because if we need to modify some part ( like the left navigation ) we can do it wit less risk of
messing with the other part.

Now, lets take a deeper look at the header file. One important usage of this file is to set the page
title. You may have noticed it that when you're browsing the categories and products the page title
change accordingly. Setting up a descriptive page title is especially important when you consider
SEO ( search engine optimization ). You see, search engine put heavy emphasize on the page title so
we better get this one thing right.

Here how it works. By default we set the page title as "My Online Shop" but if a product id is
detected in the query string we search the database to find the name of the product. Then we use the
product's name as the page title.

If we can't find any product id in the query string we do another test to see if there's a category id in
there. If we found one we fetch the category name from the database and use it as the page title.

Here is the code :

Source : include/header.php

<?php
if (!defined('WEB_ROOT')) {
   exit;
}

// set the default page title
$pageTitle = 'My Online Shop';

if (isset($_GET['p']) && (int)$_GET['p'] > 0) {
   $pdId = (int)$_GET['p'];
   $sql = "SELECT pd_name
        FROM tbl_product
        WHERE pd_id = $pdId";

    $result = dbQuery($sql);
    $row    = dbFetchAssoc($result);
    $pageTitle = $row['pd_name'];

} else if (isset($_GET['c']) && (int)$_GET['c'] > 0) {
  $catId = (int)$_GET['c'];
  $sql = "SELECT cat_name
        FROM tbl_category
        WHERE cat_id = $catId";

    $result = dbQuery($sql);
    $row    = dbFetchAssoc($result);
    $pageTitle = $row['cat_name'];
}

?>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title><?php echo $pageTitle; ?></title>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
<link href="include/shop.css" rel="stylesheet" type="text/css">
<script language="JavaScript" type="text/javascript" src="library/common.js"></script>
</head>
<body>

Next we explore further about the category browsing ( that navigation links on the left section of the
page ).
                       Shop - Browse Category

On the left side of the shop pages we show the category list. When the customer clicks on a
category on the left side she can see all products in that category. The shop main page include
this navigation from include/leftNav.php and as mentioned before, the one responsible to get the
list of categories for this navigation is the formatCategories() function in library/category-
functions.php

In summary here is what formatCategories() do :

   1. Get all the children categories
   2. Get the parent category and other categories on the same level as the parent
   3. Keep looping to get the parent's parent until we reach the top level category

This function is actually a bit too complex for handling two level deep categories. However in
the future i want this shop to handle categories more than two level deep so i made this function
so it can cope with that.

Okay, let's not talk about this function too much and move on with the code for leftNav.php

Source code : include/leftNav.php

<?php
if (!defined('WEB_ROOT')) {
   exit;
}

// get all categories
$categories = fetchCategories();

// format the categories for display
$categories = formatCategories($categories, $catId);
?>
<ul>
<li><a href="<?php echo $_SERVER['PHP_SELF']; ?>">All Category</a></li>
<?php
foreach ($categories as $category) {
   extract($category);
   // now we have $cat_id, $cat_parent_id, $cat_name

 $level = ($cat_parent_id == 0) ? 1 : 2;
 $url = $_SERVER['PHP_SELF'] . "?c=$cat_id";
 // for second level categories we print extra spaces to give
 // indentation look
 if ($level == 2) {
    $cat_name = '&nbsp; &nbsp; &raquo;&nbsp;' . $cat_name;
 }

  // assign id="current" for the currently selected category
  // this will highlight the category name
  $listId = '';
  if ($cat_id == $catId) {
     $listId = ' id="current"';
  }
?>
<li<?php echo $listId; ?>><a href="<?php echo $url; ?>"><?php echo $cat_name; ?></a></li>
<?php
}
?>
</ul>



The categories are printed as unordered lis ( <ul> ). I guess you already know that unordered list
usually shows up like this :

      list one
      list two
           o      child 1
           o      child 2

But thanks to CSS ( Cascading Style Sheet ) we can alter how the browser display the list and
give them than boxy look. I must admit that i'm a beginner to css and i learn this from
projectseven.com, so if you want to know how the css works you can visit the site later.

Next up is the product browsing.
                           Shop - View Product List

When you click one of the category from the left navigation the main content area will show all
product contained in that category. For example if you click on Manga then all four products in this
category will be shown. Actually "Manga" doesn't have any product. Those product are belong to it's
children categories ( Naruto and HunterXHunter).

If you checkout the source code for productList.php you can see that it first call getChildCategories()
function so if you browse "Manga" productList.php will show all products in "Manga" ( zero
products ) and it's children "Naruto" and "HunterXHunter" ( four products ).

Source code : include/productList.php

<?php
if (!defined('WEB_ROOT')) {
exit;
}

$productsPerRow = 2;
$productsPerPage = 4;

$children = array_merge(array($catId),
getChildCategories(NULL, $catId));
$children = ' (' . implode(', ', $children) . ')';

$sql = "SELECT pd_id, pd_name, pd_price, pd_thumbnail, pd_qty, c.cat_id
FROM tbl_product pd, tbl_category c
WHERE pd.cat_id = c.cat_id AND pd.cat_id IN $children
ORDER BY pd_name";
$result = dbQuery(getPagingQuery($sql, $productsPerPage));
$pagingLink = getPagingLink($sql, $productsPerPage, "c=$catId");

// ... we got more codes here
?>


The rest of this page code should be pretty clear to you. There's a SELECT query and since we also
want to use paging we feed the query to getPagingQuery(). If you don't know what
getPagingQuery() does you should go back and read the page about product list in admin section.

Below is a snapshot of the product listing
On productList.php the products are displayed so one row display three products like shown above.
The number of product in one row can be changed. You just need to modify the value of
$productsPerRow. For example modifying this value into 2 give this look :




And here is the code that make this happen
Source code : include/productList.php

<?php

// ... previous code

$numProduct = dbNumRows($result);

$columnWidth = (int)(100 / $productsPerRow);
?>
<table width="100%" border="0" cellspacing="0" cellpadding="20">
<?php
if ($numProduct > 0 ) {

$i = 0;
while ($row = dbFetchAssoc($result)) {

extract($row);
if ($pd_thumbnail) {
$pd_thumbnail = WEB_ROOT . 'images/product/' . $pd_thumbnail;
} else {
$pd_thumbnail = WEB_ROOT . 'images/no-image-small.png';
}

if ($i % $productsPerRow == 0) {
echo '<tr>';
}

// format how we display the price
$pd_price = displayAmount($pd_price);

echo "<td width=\"$columnWidth%\" align=\"center\"><a href=\"" . $_SERVER['PHP_SELF'] .
"?c=$catId&p=$pd_id" . "\"><img src=\"$pd_thumbnail\"
border=\"0\"><br>$pd_name</a><br>Price : $pd_price";

// if the product is no longer in stock, tell the customer
if ($pd_qty <= 0) {
echo "<br>Out Of Stock";
}

echo "</td>\r\n";

if ($i % $productsPerRow == $productsPerRow - 1) {
echo '</tr>';
}
$i += 1;
}

if ($i % $productsPerRow > 0) {
echo '<td colspan="' . ($productsPerRow - ($i % $productsPerRow)) . '">&nbsp;</td>';
}

} else {
?>
<tr><td width="100%" align="center" valign="center">No products in this category</td></tr>
<?php
}
?>

// .. more code down here
?>




Before printing the product we first check we actually have any product at all. If
dbNumRows($result) returns zero that means we have no product in current category. So we show a
message saying that there are no products in that category.

If we do have some products to show we display them in a table where each column's width depend
on how many products to show in a row. Using the above example the column width is 100 / 3 =
33%. Next thing we do is loop through the product list and print each product.

If the value of ($i % 3) == 0 we start a new row by printing <tr> and if ($i % 3) == 2 end that row
using </tr>.

When printing the product info we use displayAmount() function to format the look of the product
price. This function is located in library/common.php. Here is what the function look like :

Source code : library/common.php

function displayAmount($amount)
{
  global $shopConfig;
  return $shopConfig['currency'] . number_format($amount);
}

What this function does is concat the currency symbol with the formattted amount. So if the amount
is 123456 it will be displayed as $123,456. If the product price contains fraction ( like cents ) you
need to change this function to this :

function displayAmount($amount)
{
  global $shopConfig;
  return $shopConfig['currency'] . number_format($amount, 2);
}

Notice that we add extra parameter to number_format(). This extra parameter will make the function
display the amount with two decimal point such as $19.95

Back to productList.php. After printing the product price we check if we already run out of that
product in inventory. If so we print additional message saying that the product is no longer in stock.

Since there is always a possibility that the last row we print does not contain three products we check
the value of $i % 3 after the loop . If it's greater than zero that means the last row does not have three
products in it so we need to print the empty column like shown on the first snapshot.

In case the picture isn't clear enough here is the same snapshot but the table border is set to 1. You
can see that on the last row we have 2 empty ( merged ) columns
                    Shop - View Product Detail
When you click on one of the product from the product list. The product detail page will show
up. For each product we display the full-sized image, name, price and 'Add to cart' button. When
you want to customize this page remember NOT to change the button to 'Buy Now' because the
visitor may not ready to buy yet so don't scare her away.

Here is the snapshot of the product detail page. We have full size image on top left corner, the
description at the bottom, then the product name, price and an 'Add To Cart' button on the right.




No weird stuff here. This kind of layout is actually very common to see on shopping sites across
the internet. And by following the common stuff we can be sure that the visitor won't be
confused with this layout.

There's one more important thing about the 'Add To Cart' button. We only show this if we still
have this product in stock. After we run out of this product we just display 'Out Of Stock' . Here
is the code snippet from include/productDetail.php

Source code : include/productDetail.php

<?php
// if we still have this product in stock
// show the 'Add to cart' button
if ($pd_qty > 0) {
?>
<input type="button" name="btnAddToCart" value="Add To Cart"
onClick="window.location.href='<?php echo $cart_url; ?>';" class="box">
<?php
} else {
   echo 'Out Of Stock';
}
?>
                                 Shop - Add To Cart
If you click on the 'Add To Cart' button on the product detail page you will be redirected to the
shopping cart page ( cart.php ). For example if you want to buy Naruto Volume 2 you will go to :
cart.php?action=add&p=19.

The code for cart.php is like this :

Source code : cart.php

<?php
require_once 'config.php';
require_once 'cart-functions.php';

$action = (isset($_GET['action']) && $_GET['action'] != '') ? $_GET['action'] : 'view';

switch ($action) {
  case 'add' :
    addToCart();
    break;
  case 'update' :
    updateCart();
    break;
  case 'delete' :
    deleteFromCart();
    break;
  case 'view' :
}

// ... more code here to display the cart content

?>


Since we have action=add in the query string the addToCart() function will be called. In short the
function will do these :

     1.   Check if the product exist in database
     2.   Check if we still have this product in stock ( quantity > 0 )
     3.   If the product is already in cart increase the quantity
     4.   If not add the product to cart

The addToCart() function is located in library/cart-functions.php and the content can be seen below.
Source code : library/cart-functions.php

function addToCart()
{
  // make sure the product id exist
  if (isset($_GET['p']) && (int)$_GET['p'] > 0) {
     $productId = (int)$_GET['p'];
  } else {
     header('Location: index.php');
  }

 // does the product exist ?
 $sql = "SELECT pd_id, pd_qty
       FROM tbl_product
      WHERE pd_id = $productId";
 $result = dbQuery($sql);

 if (dbNumRows($result) != 1) {
    // the product doesn't exist
    header('Location: cart.php');
 } else {
    // how many of this product we
    // have in stock
    $row = dbFetchAssoc($result);
    $currentStock = $row['pd_qty'];

     if ($currentStock == 0) {
        // we no longer have this product in stock
        // show the error message
        setError('The product you requested is no longer in stock');
        header('Location: cart.php');
        exit;
     }
 }

 // current session id
 $sid = session_id();

 // check if the product is already
 // in cart table for this session
 $sql = "SELECT pd_id
       FROM tbl_cart
       WHERE pd_id = $productId AND ct_session_id = '$sid'";
 $result = dbQuery($sql);

 if (dbNumRows($result) == 0) {
      // put the product in cart table
      $sql = "INSERT INTO tbl_cart (pd_id, ct_qty, ct_session_id, ct_date)
            VALUES ($productId, 1, '$sid', NOW())";
      $result = dbQuery($sql);
    } else {
      // update product quantity in cart table
      $sql = "UPDATE tbl_cart
            SET ct_qty = ct_qty + 1
            WHERE ct_session_id = '$sid' AND pd_id = $productId";

        $result = dbQuery($sql);
    }

    deleteAbandonedCart();

    header('Location: ' . $_SESSION['shop_return_url']);
}

After finish placing an item into the cart this function do something else. It calls
deleteAbandonedCart(). I know it's kind of weird to call this function here but currently it is the best
place to call it. We actually have at least three options on how and when to delete abandoned:

        1. Call deleteAbandonedCart() when adding a product into cart like shown above
        2. Make a new submodul on admin page where we have a button saying 'Delete All Abandoned
           Cart'
        3. Using cron job to remove the abandoned cart periodically.

The first option is what i see as the best option right now, because adding an admin submodul
requires extra work, and using cron means installing the shop will become a hassle. Before i forget
here is what deleteAbandonedCart() look like :

Source code : library/cart-functions.php

function deleteAbandonedCart()
{
  $yesterday = date('Y-m-d H:i:s',
          mktime(0,0,0, date('m'), date('d') - 1, date('Y')));
  $sql = "DELETE FROM tbl_cart
       WHERE ct_date < '$yesterday'";
  dbQuery($sql);
}

We consider a cart is abandoned if it's older than one day. So first we find out what date yesterday
was and send a query to database to remove any cart entry where the cart date is less than yesterday
date.
Okay, after adding the product and removing all abandoned carts we don't send the customer to the
shopping cart page. She will stay on the product detail page. But this time the mini cart on the right
side will show the product in her shopping cart.

The mini cart is included from include/miniCart.php. It displays all product currently in shopping
cart. It also show the total amount after the shipping cost.

This behaviour ( sending the customer to product page after adding a product ) is actually depend on
what kind of shop we're running. If it's a shop where people usually buy more than one kind of
product then it's better if we skip showing the shopping cart page and display the product page. But
if people on this kind of shop usually only buy one product in a shopping session then it's best if we
display the shopping cart page. You just need to modify the redirection to

header('Location: cart.php?action=view');

If you're still not sure where to send the shopper after add to cart there's a discussion thread in
Webmasterworld forum discussing this issue, check it out.

Next we start working on the shopping cart page.
                    Shop - View Shopping Cart
The shopping cart interface is made more simple than the shop pages. Studies have shown that
removing any distraction from the shopping cart page ( and from the checkout pages too ) can
lead to higher conversion rates. So for this page the left navigation is removed, the mini cart
display is also gone.

If the visitor come to this page and the shopping cart is still empty there is a chance that she go
there by accident or feeling confused. So now we present her with simple instruction on how to
buy stuff in our shop .

                                  "Your shopping cart is empty"

"If you find you are unable to add anything to your cart, please ensure that your internet browser
has cookies enabled and that any other security software is not blocking your shopping session."



Now if there are already items in the cart we present it to the customer like shown below. Each
row shows the product thumbnail name, unit price, quantity and sub total. On each row we have
a delete button so the customer can easily remove the item. If you plan to customize the shopping
cart interface do not remove the delete button. It will make the delete process difficult for the
customer and it certainly not a good thing.




You may have seen it on another shopping cart solution that to remove an item the customer
must set the quantity to zero then click the 'Update Cart' button. That is the wrong way to do it
because it makes a very simple action difficult.
                               Shop - Checkout
There are three steps to complete the checkout

   1. Fill out the shipping and payment info
   2. Confirm the ordered items, shipping and payment info and enter the payment method.
   3. Save the order information to the database. If the payment method is COD ( cash on
      delivery ) go straight to the thank you page. If the customer choose to pay with paypal
      submit the payment info to paypal server.

Below is the code for checkout.php

Source code : checkout.php

<?php
require_once 'library/config.php';
require_once 'library/cart-functions.php';
require_once 'library/checkout-functions.php';

if (isCartEmpty()) {
   // the shopping cart is still empty
   // so checkout is not allowed
   header('Location: cart.php');
} else if (isset($_GET['step'])
         && (int)$_GET['step'] > 0
         && (int)$_GET['step'] <= 3) {
   $step = (int)$_GET['step'];

 $includeFile = '';
 if ($step == 1) {
    $includeFile = 'shippingAndPaymentInfo.php';
    $pageTitle = 'Checkout - Step 1 of 2';
 } else if ($step == 2) {
    $includeFile = 'checkoutConfirmation.php';
    $pageTitle = 'Checkout - Step 2 of 2';
 } else if ($step == 3) {
    $orderId = saveOrder();
    $orderAmount = getOrderAmount($orderId);

   $_SESSION['orderId'] = $orderId;

   // our next action depends on the payment method
   // if the payment method is COD then show the
   // success page but when paypal is selected
   // send the order details to paypal
   if ($_POST['hidPaymentMethod'] == 'cod') {
      header('Location: success.php');
      exit;
   } else {
      $includeFile = 'paypal/payment.php';
   }
  }
} else {
  // missing or invalid step number, just redirect
  header('Location: index.php');
}

require_once 'include/header.php';
?>
<script language="JavaScript" type="text/javascript" src="library/checkout.js"></script>
<?php
require_once "include/$includeFile";
require_once 'include/footer.php';
?>

On top of this file we check if the shoppping cart is empty. If it is empty the customer is
redirected to the cart page. Just to let her know that her shopping cart is still empty and so she
cannot checkout.

Just like the main page ( index.php ) the checkout page "out sourced" almost everything to other
pages. The main part of this file is the switch to load appropriate file depending on which
checkout step the customer is on.

And now, let's take a better look at these checkout processes one step at a time.
                   Shipping And Payment Info
If the cart is not empty then we show the shipping and payment form like shown below. You can
see the code for this page here. Nothing really interesting in that code because it simply displays
a form where the customer can fill in here shipping and payment information.
In case the shipping and payment info are the same, the customer can tick the checkbox that says
'Same as shipping information' and using javascript the payment info fields will have the same
value as the shipping info. It's just added for convenience so the customer don't have to repeat all
those typings.

And lastly she must choose the payment method. The shop can handle payment using Paypal but
if the customer is not comfortable with it she can just choose Cash on Delivery.

When the form is submitted we don't save the info to database yet. They are just passed to the
next page ( the order confirmation page ) as hidden input. Showing the confirmation page is
important so that the customer can recheck their info and make sure everything is okay.
                       Checkout Confirmation
On the order confirmation page we display the ordered items with the shipping and payment
information provided earlier. You can't see it in the screenshot but you can be sure that the
shipping and payment info is there as hidden inputs. If you don' believe just checkout the source
code
If the customer want to modify the shipping/payment info she can click on the "Modify
Shipping/Payment Info" and if everything is okay she click on the 'Confirm Order' button and so
we begin saving the order to database.

First, we insert the shipping and payment info to tbl_order and the ordered items into
tbl_order_item. We also modify the product quantity in stock. If you play with the demo you can
see that this is disabled so the product quantity is always the same no matter how many items
you ordered.

Next thing we do is remove the ordered items from cart table because they are no longer needed.



If the payment method is COD the customer will go immediately to the thankyou page. But
when paypal is chosen the process is a bit longer. Next we'll start playing with the Paypal stuff
                Payment Processing With Paypal
For now plaincart only accept online payment using Paypal. The reason is that they provide pretty
extensive resource for developers. Paypal provide the developers with a "sandbox" that allow
developers to test their payment module without any actual money transferred.

Paypal provide many ways to accept money but the one used for this tutorial is the Instant Payment
Notification
( IPN ). The reason i choose IPN is it seem to be the easiest way to integrate paypal to this shopping
cart. The details on setting up the business account and the developer account are described at the
end of this checkout discussion. If you want to read it first just click here

There are three files needed for using Paypal IPN and they are put in include/paypal/. The files are :

      paypal.inc.php : containing the configuration variables and paypal specific functions
      payment.php : this is the form that contain the hidden inputs to send to paypal server
      ipn.php : checks the POST from paypal and update the database accordingly

Before going straight to the code please take a look at the picture below. It displays the checkout flow
starting from the checkout confirmation page up to the thankyou page. The ones with blue background
takes place on the merchant site ( plaincart.com ) and the ones with white background takes place in Paypal
website.




Here is what happen on each stage :

1. Checkout Confirmation

After rechecking the shipping and payment information on this page the customer click on the
"Confim Order" button.
2. Submit Payment Info

An HTML form is generated containing the payment information ( order id, amount, etc ). You will
probaly only see a message like this : "Processing Transaction ... ". But if you take a look at the
source you can see the HTML code containing the form and some hidden inputs. This form is
submitted to paypal site during page load so if you have a fast connection you may only see it for a
split second.

The file that generate these hidden inputs is include/paypal/payment.php which is included from
checkout.php. You can see that payment.php is not doing anything complex. It just prints the hidden
inputs with values coming from the paypal configuration in paypal.inc.php plus the order id, order
amount and the item name ( which is hardcoded to "Plaincart Purchase" ).


3. Paypal Login Page

Once on the Paypal site you will be asked to login or create a new account. I already made a test
account for this so you don't need to signup with paypal just to test this. You can use use this
information to login :
Email Address: testme@phpwebcommerce.com
PayPal Password: phpwebco

4. Payment Detail

After login you will see the payment detail page. It look something like this :
There's an optional message box that the customer ( you ) can fill in. Paypal will pass this message
along with other payment information to merchat server ( this site ). When you go to the order page
in the admin section you can see the message on the order detail page. To continue with the payment
process just click on the "Pay" button.

5. Payment Confirmation

The next screen you will see is the payment confirmation screen from paypal :
6. Thank You Page

Once you click the "Continue" button Paypal will redirect the customer back to the merchant site (
this site ). Since i'm not using a secure connection ( no https:// stuff ) there will be a warning
message about you're being redirected to unsecure site. You can ignore it and just click on
"Continue" and you will see the thankyou page. Here is the screenshot :




The thankyou page can only be accessed when an order id is found in the session. The order id is put
in the session variable after you click the "Confirm" button on the checkout confirmation page. If no
order id is found then the customer is sent directly to the shop main page.

On this last step of checkout an email is sent to the shop admin notifying about the new order. This
behaviour can be changed from the admin page on the shop configuration section.

Here is the code for the thankyou page

Source : success.php

<?php
require_once 'library/config.php';

// if no order id defined in the session
// redirect to main page
if (!isset($_SESSION['orderId'])) {
   header('Location: ' . WEB_ROOT);
   exit;
}

$pageTitle = 'Checkout Completed Successfully';
require_once 'include/header.php';

// send notification email
if ($shopConfig['sendOrderEmail'] == 'y') {
   $subject = "[New Order] " . $_SESSION['orderId'];
   $email = $shopConfig['email'];
   $message = "You have a new order. Check the order detail here \n
          http://" . $_SERVER['HTTP_HOST'] . WEB_ROOT .
          'admin/order/index.php?view=detail&oid=' .
           $_SESSION['orderId'] ;
    mail($email, $subject, $message, "From: $email\r\nReturn-path: $email");
}

unset($_SESSION['orderId']);
?>
<p>&nbsp;</p><table width="500" border="0" align="center" cellpadding="1" cellspacing="0">
<tr>
<td align="left" valign="top" bgcolor="#333333"> <table width="100%" border="0"
cellspacing="0" cellpadding="0">
<tr>
<td align="center" bgcolor="#EEEEEE"> <p>&nbsp;</p>
<p>Thank you for shopping with us! We will send the purchased
item(s) immediately. To continue shopping please <a href="index.php">click
here</a></p>
<p>&nbsp;</p></td>
</tr>
</table></td>
</tr>
</table>
<br>
<br>
<?php
require_once 'include/footer.php';
?>

This is really an oversimplified thank you page. Normally an online shop also provide a link where
the customer can check her order online or a notice that an order confirmation email has been sent
,etc. However i haven't made any of those so for now this is sufficient.



There's a "hidden step" between step 4 and 5. The customer can't see it because it happen between
Paypal server and the merchant server. On this "hidden step" these things happen :

     1. Paypal send a POST request to merchat server to notify about the payment.
     2. Then our server send a reply POST to let Paypal know that we have receive it.
     3. Paypal send a reply notifying the payment status whether it's verified or not

We will take a deeper look at these on the next page
              Handling Paypal Instant Payment
                     Notification (IPN)
When you reach the payment detail page on paypal site and click the "Pay" button paypal will send a
POST message to our server. Our script then send a confirmation back to paypal to verify this POST.

Here's that flow diagram again.




Paypal Configuration File

The script that process the notification is located in include/paypal/ipn.php. But before talking about
that file let's see the paypal configuration first.

Source code : paypal.inc.php

<?php

$paypal = array();

$paypal['business'] = "armanpi@phpwebcommerce.com";
$paypal['site_url'] = "http://www.phpwebcommerce.com/plaincart/";
$paypal['image_url'] = "";
$paypal['success_url'] = "success.php";
$paypal['cancel_url'] = "error.php";
$paypal['notify_url'] = "include/paypal/ipn.php";
$paypal['return_method'] = "2"; //1=GET 2=POST
$paypal['currency_code'] = "USD";
$paypal['lc'] = "US";
//$paypal['url'] = "https://www.paypal.com/cgi-bin/webscr";
$paypal['url'] = "https://www.sandbox.paypal.com/cgi-bin/webscr";
$paypal['post_method'] = "fso"; //fso=fsockopen();
$paypal['curl_location'] = "/usr/local/bin/curl";



// ... other "not so important" settings down here

?>

Here is the description for each configuration :

     1. $paypal['business']
        The email address that shown as the recipient when you pay

     2. $paypal['site_url']
        The store's main url. It is NOT ALWAYS the same as the site's homepage url

     3. $paypal['image_url']
        I'm not using any images to display on paypal site during the checkout process so i just left this blank

     4. $paypal['success_url']
        When paypal can verify the customer's credit card and everything gone smoothly the customer will
        be sent back to this url

     5. $paypal['cancel_url']
        And if something went wrong the customer are taken to this one

     6. $paypal['notify_url']
        This is the main script that check the verification message sent by paypal. It also update the
        database if the payment is successful

     7. $paypal['return_method']
        It sets the http method used by paypal to send the verification message

     8. $paypal['currency_code']
        What currency will the customer be paying. Other currencies supported are GBP,JPY,CAD, and EUR

     9. $paypal['lc']
        The language ( locale )

     10. $paypal['url']
         We send all payment information to this url. For testing we use the sandbox url :
         https://www.sandbox.paypal.com/cgi-bin/webscr
         But when the store goes live we'll use the real paypal url : https://www.paypal.com/cgi-bin/webscr
   11. $paypal['post_method']
       The function fsockPost($url,$data) can use several method to send transaction data. They are fso (
       fsockopen() ), curl ( curl command line), and libCurl ( php compiled with libCurl support ). We're
       using fso because a hosting company may not provide PHP with curl support

   12. $paypal['curl_location']
       If you're using curl as the post method you must supply the path to the curl command on your
       server

Submitting The Payment Information

From the second step on the flowchart above we post some form values to paypal server so they can
process the payment. Below you can see the variables sent from payment.php and their description

   1. business
      The merchant's email address. The payment will be sent to the paypal account identified by this
      email address. The value for this variable is taken from the configuration file ( $paypal['business'] )

   2. amount
      The amount of payment that the customer must pay.

   3. invoice
      The order id. We need this so we can know which order is being paid

   4. item_name
      Since the customer can buy multiple items i just hard code this one to "Plaincart purchase".

   5. return
      The url where you want to send the customer to after the payment is complete (
      $paypal['success_url'] )

   6. cancel_return
      When the payment fails the customer is redirected here ( $paypal['cancel_url'] )

   7. notify_url
      The url of the script that checks the IPN notification from paypal and send back a confirmation to
      paypal ( $paypal['notify_url'] )

   8. rm
      The return method. The available values are 1 ( GET ) and 2 ( POST ). Since we use POST in this
      example the value of rm is 2.

   9. currency_code
      In what currency do you want to be paid in ( $paypal['currency_code'] ) .

   10. lc
       The language ( $paypal['lc'] )
   11. cmd
       The value is _xclick which is hardcode. Paypal provides several kinds of payment processing services
       so we need to tell them what kind of service we're using.

   12. no_shipping
       Since we already ask the shipping address during checkout. We don't need to ask it again on the
       paypal site. So we set this value to 1. Use 0 ( zero ) if you want paypal to ask the shipping address
       instead.




Processing The Payment

Here is the code for the IPN script :

Source code : include/paypal/ipn.php

<?php

if (strpos($_SERVER['REMOTE_ADDR'], '66.135.197.') === false) {
  exit;
}

require_once './paypal.inc.php';

// repost the variables we get to paypal site
// for validation purpose
$result = fsockPost($paypal['url'], $_POST);

//check the ipn result received back from paypal
if (eregi("VERIFIED", $result)) {

 require_once '../../library/config.php';

 // check that the invoice has not been previously processed
 $sql = "SELECT od_status
         FROM tbl_order
         WHERE od_id = {$_POST['invoice']}";

 $result = dbQuery($sql);

 // if no invoice with such number is found, exit
 if (dbNumRows($result) == 0) {
    exit;
 } else {
     $row = dbFetchAssoc($result);

     // process this order only if the status is still 'New'
     if ($row['od_status'] !== 'New') {
        exit;
     } else {

         // check that the buyer sent the right amount of money
         $sql = "SELECT SUM(pd_price * od_qty) AS subtotal
         FROM tbl_order_item oi, tbl_product p
         WHERE oi.od_id = {$_POST['invoice']} AND oi.pd_id = p.pd_id
         GROUP by oi.od_id";
         $result = dbQuery($sql);
         $row = dbFetchAssoc($result);

         $subTotal = $row['subtotal'];
         $total = $subTotal + $shopConfig['shippingCost'];

         if ($_POST['payment_gross'] != $total) {
            exit;
         } else {

             $invoice = $_POST['invoice'];
             $memo = $_POST['memo'];
             if (!get_magic_quotes_gpc()) {
                $memo = addslashes($memo);
             }


             $sql = "UPDATE tbl_order
             SET od_status = 'Paid', od_memo = '$memo', od_last_update = NOW()
             WHERE od_id = $invoice";
             $result = dbQuery($sql);
         }
     }
 }

} else {
  exit;
}
?>

Right at the top of the script we check if the ip address of the page requester. Since this page is
meant only to be accessed by paypal server we disallow any connection that didn't come from
paypal. Paypal server's IP address begin with 66.135.197 so if we don't see that in the remote address
we just assume someone trying to mess with our script and exit immediately.

If the request does come from paypal we send a reply POST to let Paypal know that we have receive
their message. Upon receiving our reply Paypal send another reply notifying the payment status
whether it's verified or not.

If we found the word VERIFIED in the reply that means payment has been made and we move on
updating the status for the order

Here is the series of checking that the script perform :

   1.   Check the database to see if the invoice id really exist
   2.   If it does exist check the order status is still 'New' to prevent double action
   3.   If it's a new order check the amount of money sent and make sure the currency is correct.
   4.   When everything is okay we can update the order status to "Paid", add the buyer's memo and
        modify the update time.

That's it. Once the customer made her payment you can check the order on the admin page. When
you click on the "Order" menu on the admin page you can see all paid orders. From there it's up to
you as the store owner to decide what to do with those orders.
Creating Your Paypal Account

If you want to test the shopping cart script on your own server you should create your own developer
account. Here are the steps to setup a developer account with paypal :

   1.    Go to https://developer.paypal.com ,click on on "Sign Up Now" and complete the registration
   2.    Paypal will send you a verification email. Click the link in the email to verify your email address
   3.    Login to PayPal Developer Central
   4.    Create a sandbox account by clicking on the "Sandbox" tab
   5.    Click on "Create Account". Anew window will popup. On the popup screen choose "Personal
         Account", select a country from the list then hit "Continue". This is just for testing so you can enter
         false email and use false data to fill in the form. Don't forget to enter the characters in the security
         image (it's case insensitive)
   6.    On the next screen just hit the
   7.    Complete the 'extra ...' stuff
   8.    Check your paypal inbox
   9.    Open the welcome email and click the verification link
   10.   Open the 'extra ...' email and click on the link then enter the number


You will still need to create a "normal" Paypal account not only the developer account before
testing. If not all payment status will be "Pending" instead of "Completed". If you don't already have
it yet here are the steps to create and configure your paypal account :

   1. Create a business account with paypal ( the personal account is not enough ) and add a credit card
      so your account is verified. If you don't add a credit card then IPN will always say that the payment
      status is "Pending"
   2. Login
   3. Go to My Account > Profile > Instant Payment Notification Preferences
   4. Click on the "Edit" button
   5. Check the checkbox and enter the url where we want to receive the IPN notifications ( in our case
      it's http://www.phpwebcommerce.com/plaincart/include/paypal/ipn.php) then hit "Save"

Since our script already pass the hidden input called notify_url, which is the url of the script that
process IPN notifications, this step is actually not required anymore. I just put this for the sake of
completeness.



I did try to make checkout process as simple as possible but somehow i feel it gets quite complex
now. If you found something weird / not clear please contact me. Howevert if you're question is
about paypal it would be a lot better to ask the questions in the paypal forum especially the IPN
section. This forum is packed with paypal experts.
                      Shopping Cart Source Code
You can download the whole script as a zip file here. To install the shopping cart you need to do
these :

    1. Extract the zip file ( ex. to C:\Webroot\plaincart )
    2. Create a new MySQL database ( ex. 'plaincart' );
    3. Create the tables using the SQL in plaincart.sql. On windows just open a command prompt
       window ( Start > Run > cmd.exe ) and go to the directory where you unzip the files then type
       this :

        C:\mysql\bin\mysql plaincart < plaincart.sql

       I'm assuming you install mysql in C:\MySQL
    4. Modify the database connection settings in library/config.php.

By the way, someone asked me about the license for the code so here it is :

The code is free to use, modify, or enhance. If you want to show you appreciation by placing a link
to this site then i'll be very very very grateful :-). If you don't want to link to this site it's allright
because the code was meant to be free anyway.

Below is the list of the shopping cart files. I only list the php files excluding the css and javascript
files.


       source/admin/library/functions.php
       source/admin/login.php
       source/admin/include/template.php
       source/admin/include/footer.php
       source/admin/include/header.php
       source/admin/index.php
       source/admin/category/processCategory.php
       source/admin/category/add.php
       source/admin/category/error_log
       source/admin/category/index.php
       source/admin/category/list.php
       source/admin/category/modify.php
       source/admin/order/detail.php
       source/admin/order/index.php
       source/admin/order/list.php
       source/admin/order/processOrder.php
       source/admin/product/detail.php
       source/admin/product/add.php
       source/admin/product/index.php
   source/admin/product/list.php
   source/admin/product/modify.php
   source/admin/product/processProduct.php
   source/admin/config/index.php
   source/admin/config/processConfig.php
   source/admin/config/main.php
   source/admin/user/processUser.php
   source/admin/user/add.php
   source/admin/user/error_log
   source/admin/user/index.php
   source/admin/user/list.php
   source/admin/user/changePass.php
   source/admin/user/modify.php
   source/admin/main.php
   source/cart.php
   source/library/errorMessage.php
   source/library/category-functions.php
   source/library/checkout-functions.php
   source/library/product-functions.php
   source/library/config.php
   source/library/cart-functions.php
   source/library/database.php
   source/library/common.php
   source/success.php
   source/error.php
   source/include/thankyou.php
   source/include/top.php
   source/include/leftNav.php
   source/include/productList.php
   source/include/checkoutConfirmation.php
   source/include/miniCart.php
   source/include/productDetail.php
   source/include/paypal/paypal.inc.php
   source/include/paypal/error_log
   source/include/paypal/payment.php
   source/include/paypal/ipn.php
   source/include/footer.php
   source/include/categoryList.php
   source/include/shippingAndPaymentInfo.php
   source/include/header.php
   source/plaincart.sql
   source/index.php
   source/checkout.php
   source/db/db.sql

								
To top