Behavior, Content, Money – 3 Things you should never give away for free!!!

BCmoney MobileTV

E-Commerce Shopping Cart in JavaScript and PHP

Posted by bcmoney on May 18, 2015 in E-Commerce, JavaScript, PHP with No Comments


No Gravatar
English: Jewel-Osco - monster shopping cart truck

English: Jewel-Osco – monster shopping cart truck (Photo credit: Wikipedia)

In E-Commerce, a “frictionless experience” is often described as the ultimate design goal when it comes to the consumer’s purchasing experience. An easy-to-use, robust shopping cart solution that can easily have any number of diverse types of items added to it, calculate shipping & handling, taxes and any other additional fees (where such apply), provides transparency and immediacy to the customer’s purchase decision. Doing this well can mean the difference between huge sales numbers and lackluster or disappointing sales figures.

Boiling all the Shopping Cart solutions out there to the most common, key functions we should expect a solution to support are:

  • Add/Remove items
  • Tabulate itemized sub-total
  • Calculate shipping & handling
  • Calculate taxes & fees
  • Tabulate total
  • Remember History for later purchase completion
  • Purchase/Checkout confirmation process

Other nice-to-have features that begin to move away from basic “Cart” functionality and into holistic E-Commerce platforms, include:

  • Multi-Address memory (billing, shipping… home, work, summer, etc)
  • Multi-Currency support (switch currency at any time)
  • Multi-Lingual support (switch languages at any time, i18n)
  • Multi-Layout support (switch look & feel at any time, l10n)
  • Storefront & “canned store templates”
  • Layout drag&drop/point-click customization (as per SquareSpace, Wix, etc)
  • Auto-fill forms (with customers’ stored Address info)
  • Auto-billing (subscriptions/recurring payments)
  • Notification options for receipt (Email, SMS, Phone notification, etc… in addition to on-screen)
  • International Shipment Tracking (parcel status check)
  • Returns processing
  • 3rd party payment support options (CreditCard, Interac eTransfer via Moneris/PaySafe, PayPal, 2checkout, etc)
  • PCI & PA-DSS compliance (possibly by payment gateway deferral for sensitive data)
  • Item import/export
  • Ratings (star, thumbs up/down, etc)
  • Reviews (public or private textual customer feedback)
  • Search
  • Wish List curation
  • Product/Service Recommendations
  • Discounts (coupons, limited-time offers, affiliate codes, etc)
  • Promotions (buy X get Y, welcome emails, inactive account enticements, etc)
  • Loyalty Program (points, rewards, etc)
  • Tracking company/brand affinity & engagement
  • Inventory Management (real-time RFID, NFC, etc)
  • Supply-Chain Management (SCM)
  • Customer Relationship Management (CRM)
  • Advertising platform integration
  • Analytics platform integration
  • Social Media platform integration

There are tons more possible features but those two lists capture the main ones. Having defined a Shopping Cart and our expectations of its basic capabilities, the remainder of this post will summarize how to roll your own super simple yet intuitive E-Commerce Shopping Cart in JavaScript and PHP that gets out of the customer’s way as much as possible, focusing on the first key set of options only; it will also include thoughts on how the base functionality could easily be expanded out to include some or all of the nice-to-haves of a full-fledged E-Commerce platform.

Shopping Cart

A “Shopping Cart” is a core E-commerce functionality which allows visitors to set aside items for eventual purchase as they browse through a store. The store could be anything from an online store’s website, a Rich Internet Application (RIA), a Mobile Application (app) or some other form of hardware and/or software encapsulating the buying experience for a particular brand (such as an in-store kiosk, off-site promotional kiosk, vending machine, etc). E-Commerce shopping carts are directly analogous to physical shopping carts which users push around a store and add items to as they peruse the store’s offerings. The difference between physical and E-Commerce shopping carts is that in the E-Commerce version, as items are added a running total is typically displayed so that users can see immediately what their total expenditure will be. This can be a deterrent to some cumulative or bulk purchases which does not exist at physical retail outlets. During the checkout process, either the payment service or shopping cart typically calculates a total for the order, including shipping and handling (i.e., postage and packing) charges and the associated taxes, as applicable.

 


Client-side

HTML (HyperText Markup Language) is used as the display language to communicate to the visitor’s browser how to display the shopping cart (and products) in terms of what information gets displayed and how it is semantically marked up for browser-agents to process.

<form id="shoppingCart" action="shoppingCart.php" method="post"> 
<input id="productName" name="productName" type="hidden" /> 
<input id="updateSuccess" name="updateSuccess" type="hidden" value="http://www.someurl.com/success.html" /> 
<input id="updateFailure" name="updateFailure" type="hidden" value="http://www.someurl.com/failure.html" /> 
<input id="productQuantity" maxlength="3" name="productQuantity" size="2" type="text" /> 
<input name="Add" type="button" value="Add to Cart" />
</form>

The Shopping Cart itself should be a proper button within a FORM element, in case JavaScript is disabled and AJAX communication updates cannot be established between client and server. In this case, the page would submit completely to the server and then reload. The server-side should thus be setup in a robust manner to handle both cases (an AJAX request or FORM submission) and output transaction details in either a complete page response or update-specific response. The page to display in the event of a failure or success can be specified by updateSuccess or updateFailure hidden form field values, or, specified by hard-coded business logic in the server-side response form.

CSS

Cascading StyleSheets (CSS) will be used to affect where the Shopping Cart appears on the page and how it looks. It’s also useful to reset any other styles to start with a clean slate (in this case used the classic reset2.css by Eric Meyers), especially when designing with a responsive layout in mind.

/* http://meyerweb.com/eric/tools/css/reset/ 
   v2.0 | 20110126
   License: none (public domain)
*/
html, body, div, span, applet, object, iframe,
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
a, abbr, acronym, address, big, cite, code,
del, dfn, em, img, ins, kbd, q, s, samp,
small, strike, strong, sub, sup, tt, var,
b, u, i, center,
dl, dt, dd, ol, ul, li,
fieldset, form, label, legend,
table, caption, tbody, tfoot, thead, tr, th, td,
article, aside, canvas, details, embed, 
figure, figcaption, footer, header, hgroup, 
menu, nav, output, ruby, section, summary,
time, mark, audio, video {
	margin: 0;
	padding: 0;
	border: 0;
	font-size: 100%;
	font: inherit;
	vertical-align: baseline;
}
/* HTML5 display-role reset for older browsers */
article, aside, details, figcaption, figure, footer, header, hgroup, menu, nav, section, audio, video, canvas {
	display: block;
}
body {
	line-height: 1;
}
ol, ul {
	list-style: none;
}
blockquote, q {
	quotes: none;
}
blockquote:before, blockquote:after,
q:before, q:after {
	content: '';
	content: none;
}
table {
	border-collapse: collapse;
	border-spacing: 0;
}

/* ShoppingCart.css */
h1 {
	text-align: center;
	font-weight: bold;
	font-size: 16pt;
	text-shadow: 2px 2px 2px rgba(150, 150, 150, 1);
}
nav {
	font-weight: bold;
}
section {
	float: left;
}
aside {
	float: right;
}
article {
	margin: 5px;
}
.receipt {
	padding: 0px 2px 1px 8px;
	list-style-type: square;
}
.receipt li {
	width: 100%;
	min-width: 240px;
}
.receipt li:hover {
	background: #ccc;
}
footer {
	border-top: 1px dashed #ccc;
	text-align: center;
	font-size: 0.8em; 
}
.clear {
	clear: both;
}
.negative, .credit {
	color: green;
}
.positive, .debit {
	color: red;
}

JS

JavaScript is only required for controlling the interaction with the server-side, by making AJAX calls to add items to or remove items from the shopping cart object held on the server-side in your language of choice. Often times JavaScript is used too liberally and depended on throughout the HTML markup which makes separation of concerns impossible and thus removes the capability of porting code to new platforms/languages and also negatively effects the maintainability and readability of the client-side code.

 

Server-side

Based on PDO database connector (which everyone should be using by now rather than MYSQL/MYSQLi or the other database-sepcific PHP drivers), you now used a Prepared Statement style of Database call.
For exmaple, to select from a table called “cart” remove the quotes from the placeholder and add a colon before your bind reference:
$query = $connDB->prepare(‘SELECT * FROM cart WHERE topic_name LIKE :keywords’);
$query->bindValue(‘:keywords’, ‘%’ . $searchQ . ‘%’);

SQL

Here’s my database which I’ve attempted to setup to cover most cases, yet still serve as a simple enough example to get your head around:
E-Commerce Shopping Cart

Here’s the 25 tables defined by the Data Definition Language (DDL) schema for the complete database:

--
-- E-Commerce ShoppingCart schema
--

-- 1
CREATE TABLE `direction` (
	`direction_id` TINYINT(1) NOT NULL,
	`direction_code` CHAR(2) NOT NULL, 
	`direction_name` VARCHAR(10) NOT NULL, 
	PRIMARY KEY (`direction_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=1;

-- 2
CREATE TABLE `location_type` (
	`location_type_id` TINYINT(1) NOT NULL,
	`location_type_code` VARCHAR(2) NOT NULL,
	`location_type_name` VARCHAR(2) NOT NULL,
	PRIMARY KEY (`location_type_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=1;

-- 3
CREATE TABLE `street_type` (
	`street_type_id` INT(2) NOT NULL,
	`street_type_code` VARCHAR(3) NOT NULL, 
	`street_type_name` VARCHAR(15) NOT NULL, 
	`street_type_mail` VARCHAR(5) NOT NULL, 
	PRIMARY KEY (`street_type_id`)	
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=1;

-- 4
CREATE TABLE `address_type` (
	`address_type_id` TINYINT(1) NOT NULL,
	`address_type_code` CHAR(1) NOT NULL,
	`address_type_name` VARCHAR(12) NOT NULL,
	PRIMARY KEY (`address_type_id`)	
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=1;

-- 5
CREATE TABLE `city` (
	`city_id` INT(5) NOT NULL, 
	`city_country_id` VARCHAR(65) NOT NULL,
	`city_name` VARCHAR(65) NOT NULL,
	`city_latitude` FLOAT(10, 6) DEFAULT NULL,
	`city_longitude` FLOAT(10, 6) DEFAULT NULL,
	PRIMARY KEY (`city_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=1;

-- 6
CREATE TABLE `province` (
	`province_id` INT(4) NOT NULL, 
	`province_code` CHAR(4) NOT NULL,
	`province_name` VARCHAR(10) NOT NULL,
	`province_region_tax_rate` VARCHAR(10) NOT NULL,
	PRIMARY KEY (`province_id`)	
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=1;

-- 7
CREATE TABLE `country` (
	`country_id` INT(3) NOT NULL, 
	`country_code` VARCHAR(10) NOT NULL, 
	`country_name` VARCHAR(10) NOT NULL, 	
	PRIMARY KEY (`country_id`)	
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=1;

-- 8
CREATE TABLE `address` (
	`address_id` INT(20) NOT NULL,
	`address_civic_number` INT(6) NULL,
	`address_predirectional_id` TINYINT(1) NULL, -- direction.ID --
	`address_street_name` VARCHAR(60) NULL,	
	`address_street_type_id` INT(2) NULL, -- street_type.ID --
	`address_postdirectional_id` TINYINT(1) NULL, -- direction.ID --
	`address_location_type_id` TINYINT(1) NULL, -- location_type.ID --
	`address_city_id` INT(5) NOT NULL, -- city.ID --
	`address_province_id` INT(4) NOT NULL, -- province.ID --
	`address_country_id` INT(3) NOT NULL,-- country.ID --
	`address_postal_code` VARCHAR(12) NOT NULL,
	`address_po_box` VARCHAR(15) NOT NULL,
	PRIMARY KEY (`address_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=1;

-- 9
CREATE TABLE IF NOT EXISTS `currency` (
  `currency_id` INT(3) NOT NULL AUTO_INCREMENT,
  `currency_code` CHAR(3) DEFAULT NULL,
  `currency_name` VARCHAR(64) DEFAULT NULL,
  PRIMARY KEY  (`currency_id`),
  KEY `idx_currency_name` (`currency_name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=1;

-- 10
CREATE TABLE `language` (	
	`language_id` INT(3) NOT NULL,
	`language_code` CHAR(3) NOT NULL,
	`language_name` VARCHAR(64) DEFAULT NULL,
	PRIMARY KEY (`language_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=1;

-- 11
CREATE TABLE `user` (
	`user_id` INT(20) NOT NULL,
	`user_login` VARCHAR(20) NOT NULL UNIQUE,
	`user_password` BINARY(100) NOT NULL,
	`user_email` VARCHAR(100) NOT NULL UNIQUE,
	`user_profile_avatar` VARCHAR(255) NULL,
	`user_firstname` VARCHAR(50) NOT NULL,
	`user_lastname` VARCHAR(50) NOT NULL,
	`user_birth_date` DATETIME NOT NULL,
	`user_joined` TIMESTAMP NOT NULL,
	`user_last_login` TIMESTAMP NOT NULL,
	`user_currency_id` INT(3) NOT NULL DEFAULT 26, -- currency.ID --
	`user_language_id` INT(3) NOT NULL DEFAULT 126, -- language.ID --
	PRIMARY KEY (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=1;

-- 12
CREATE TABLE `user_address` (
	`user_address_id` INT(20) NOT NULL,
	`user_id` INT(20) NOT NULL, -- user.ID --
	`address_id` INT(20) NOT NULL, -- address.ID --
	`address_type_id` TINYINT(1) NULL, -- address_type.ID --
	PRIMARY KEY (`user_address_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=1;

-- 13
CREATE TABLE `customer` (
	`customer_id` INT(20) NOT NULL,
	`customer_user_id` INT(20) NOT NULL, -- user.ID --
	`customer_session_id` VARCHAR(40) NOT NULL,
	PRIMARY KEY (`customer_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=1;

-- 14
CREATE TABLE `company_type` (
	`company_type_id` INT(5) NOT NULL,
	`company_type_name` VARCHAR(40) NOT NULL UNIQUE,
	PRIMARY KEY (`company_type_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=1;

-- 15
CREATE TABLE `company` (
	`company_id` INT(5) NOT NULL,
	`company_name` VARCHAR(40) NOT NULL UNIQUE,
	`company_type_id` INT(5) NOT NULL,
	PRIMARY KEY (`company_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=1;

-- 16
CREATE TABLE `brand` (
	`brand_id` INT(6) NOT NULL,
	`brand_name` VARCHAR(40) NOT NULL UNIQUE,
	`brand_company_id` INT(5) NOT NULL,
	PRIMARY KEY (`brand_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=1;

-- 17
CREATE TABLE `product_type` (
	`product_type_id` INT(4) NOT NULL,
	`product_type_name` VARCHAR(40) NOT NULL UNIQUE,
	PRIMARY KEY (`product_type_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=1;

-- 18
CREATE TABLE `barcode_type` (
	`barcode_type_id` INT(1) NOT NULL,
	`barcode_type_code` CHAR(7) NOT NULL UNIQUE,	
	PRIMARY KEY (`barcode_type_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=1;

-- 19
CREATE TABLE `condition` (
	`condition_id` TINYINT(1) NOT NULL,
	`condition_code` VARCHAR(12) NOT NULL,
	`condition_packaging` BOOLEAN NOT NULL,
	`condition_sealed` BOOLEAN NOT NULL,
	`condition_instructions` BOOLEAN NOT NULL,
	`condition_all_parts` BOOLEAN NOT NULL,
	`condition_guaranteed` BOOLEAN NOT NULL,
	PRIMARY KEY (`condition_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=1;

-- 20
CREATE TABLE `item` (
	`item_id` INT(20) NOT NULL,
	`item_title` VARCHAR(100) NOT NULL UNIQUE,
	`item_image` VARCHAR(255) NOT NULL,
	`item_link` VARCHAR(255) NOT NULL,
	`item_description` VARCHAR(255) NOT NULL,
	`item_product_type_id` INT(4) NOT NULL,  -- product_type.ID --
	`item_brand_id` INT(2) DEFAULT NULL, -- brand.ID --	
	`item_condition_id` TINYINT(1) NOT NULL, -- condition.ID --
	`item_price` DECIMAL(8) NOT NULL,		
	PRIMARY KEY (`item_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=1;

-- 21
CREATE TABLE `item_barcode` (
	`item_barcode_id` INT(20) NOT NULL,
	`item_id` INT(20) NOT NULL, -- item.ID --
	`barcode_type_id` INT(1) NOT NULL, -- barcode_type.ID --
	`item_barcode_number` VARCHAR(20) NOT NULL,
	PRIMARY KEY (`item_barcode_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=1;

-- 22
CREATE TABLE `order_status` (
	`order_status_id` INT(1) NOT NULL,
	`order_status_code` CHAR(10) NOT NULL,
	PRIMARY KEY (`order_status_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=1;

-- 23
CREATE TABLE `order` (
	`order_id` INT(20) NOT NULL,
	`order_customer_id` INT(20) NOT NULL, -- customer.ID --
	`order_status_id` INT(20) NOT NULL, -- order_status.ID --
	`order_contents` BLOB NULL, 
	`order_reservation_date` TIMESTAMP NOT NULL,
	`order_expiry_date` TIMESTAMP NOT NULL,
	`order_purchase_date` TIMESTAMP NOT NULL,
	`order_delivery_date` TIMESTAMP NOT NULL,
	`order_received_date` TIMESTAMP NOT NULL,
	PRIMARY KEY (`order_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=1;

-- 24
CREATE TABLE `discount` (
	`discount_id` INT(20) NOT NULL,
	`discount_code` VARCHAR(20) NOT NULL,
	`discount_value` DECIMAL(4,3) NOT NULL,
	PRIMARY KEY (`discount_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=1;

-- 25
CREATE TABLE `receipt` (
	`receipt_id` INT(20) NOT NULL,
	`receipt_order_id` INT(20) NOT NULL, -- order.ID --
	`receipt_item_id` INT(20) NOT NULL, -- item.ID --
	`receipt_item_quantity` INT(4) NOT NULL,
	`receipt_item_subtotal` DECIMAL(10,2) NOT NULL,
	`receipt_item_shipping` DECIMAL(10,2) NOT NULL,
	`receipt_item_tax` DECIMAL(4,3) NOT NULL, -- calculated from:  province.regional_tax_rate --
	`receipt_item_discount` DECIMAL(4,3) NOT NULL, -- calculated from:  discount.value --
	`receipt_item_total` DECIMAL(10,2) NOT NULL,
	`receipt_currency_id` INT(2) NOT NULL, -- currency.ID --
	PRIMARY KEY (`receipt_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=1;

ALTER TABLE `address` ADD CONSTRAINT `address_fk0` FOREIGN KEY (`address_predirectional_id`) REFERENCES `direction`(`direction_id`);
ALTER TABLE `address` ADD CONSTRAINT `address_fk1` FOREIGN KEY (`address_street_type_id`) REFERENCES `street_type`(`street_type_id`);
ALTER TABLE `address` ADD CONSTRAINT `address_fk2` FOREIGN KEY (`address_postdirectional_id`) REFERENCES `direction`(`direction_id`);
ALTER TABLE `address` ADD CONSTRAINT `address_fk3` FOREIGN KEY (`address_location_type_id`) REFERENCES `location_type`(`location_type_id`);
ALTER TABLE `address` ADD CONSTRAINT `address_fk4` FOREIGN KEY (`address_city_id`) REFERENCES `city`(`city_id`);
ALTER TABLE `address` ADD CONSTRAINT `address_fk5` FOREIGN KEY (`address_province_id`) REFERENCES `province`(`province_id`);
ALTER TABLE `address` ADD CONSTRAINT `address_fk6` FOREIGN KEY (`address_country_id`) REFERENCES `country`(`country_id`);

ALTER TABLE `user` ADD CONSTRAINT `user_fk0` FOREIGN KEY (`user_currency_id`) REFERENCES `currency`(`currency_id`);
ALTER TABLE `user` ADD CONSTRAINT `user_fk1` FOREIGN KEY (`user_language_id`) REFERENCES `language`(`language_id`);

ALTER TABLE `user_address` ADD CONSTRAINT `user_address_fk0` FOREIGN KEY (`user_id`) REFERENCES `user`(`user_id`);
ALTER TABLE `user_address` ADD CONSTRAINT `user_address_fk1` FOREIGN KEY (`address_id`) REFERENCES `address`(`address_id`);
ALTER TABLE `user_address` ADD CONSTRAINT `user_address_fk2` FOREIGN KEY (`address_type_id`) REFERENCES `address_type`(`address_type_id`);

ALTER TABLE `item` ADD CONSTRAINT `item_fk0` FOREIGN KEY (`item_product_type_id`) REFERENCES `product_type`(`product_type_id`);
ALTER TABLE `item` ADD CONSTRAINT `item_fk1` FOREIGN KEY (`item_brand_id`) REFERENCES `brand`(`brand_id`);
ALTER TABLE `item` ADD CONSTRAINT `item_fk2` FOREIGN KEY (`item_condition_id`) REFERENCES `condition`(`condition_id`);

ALTER TABLE `item_barcode` ADD CONSTRAINT `item_barcode_fk0` FOREIGN KEY (`item_id`) REFERENCES `item`(`item_id`);
ALTER TABLE `item_barcode` ADD CONSTRAINT `item_barcode_fk1` FOREIGN KEY (`barcode_type_id`) REFERENCES `barcode_type`(`barcode_type_id`);

ALTER TABLE `company` ADD CONSTRAINT `company_fk0` FOREIGN KEY (`company_type_id`) REFERENCES `company_type`(`company_type_id`);

ALTER TABLE `brand` ADD CONSTRAINT `brand_fk0` FOREIGN KEY (`brand_company_id`) REFERENCES `company`(`company_id`);

ALTER TABLE `customer` ADD CONSTRAINT `customer_fk0` FOREIGN KEY (`customer_user_id`) REFERENCES `user`(`user_id`);

ALTER TABLE `order` ADD CONSTRAINT `order_fk0` FOREIGN KEY (`order_customer_id`) REFERENCES `customer`(`customer_id`);
ALTER TABLE `order` ADD CONSTRAINT `order_fk1` FOREIGN KEY (`order_status_id`) REFERENCES `order_status`(`order_status_id`);

ALTER TABLE `receipt` ADD CONSTRAINT `receipt_fk0` FOREIGN KEY (`receipt_order_id`) REFERENCES `order`(`order_id`);
ALTER TABLE `receipt` ADD CONSTRAINT `receipt_fk1` FOREIGN KEY (`receipt_item_id`) REFERENCES `item`(`item_id`);
ALTER TABLE `receipt` ADD CONSTRAINT `receipt_fk2` FOREIGN KEY (`receipt_currency_id`) REFERENCES `currency`(`currency_id`);

PHP

PHP is required only to manage the session to ensure that pages can be refreshed without losing the items stored in the Shopping Cart. Again, PHP is used too liberally and depended on throughout the code, losing separation of Model, View and Controller, but by simply managing the session itself in PHP, we have a more modular piece of code which could just as easily be refactored to use JSP, ASP/C#, Ruby, Python, Perl or any other server-side language to manage the shopping cart session.

ShoppingCart.class.php

<?php
/**
 * ShoppingCart.class
 *   Standalone monolithic Shopping Cart example in PHP. Includes the PDO 
 *   Database code required to connect and get the data out. Typically you'd 
 *   want to separate out the DB connection code and the Class objects for 
 *   modularity and maintainability, as in:
 *   "db.php", "User.class.php", "Item.class.php", "ShoppingCartHandler.php"
 *
 * USAGE:
 *	require_once("ShoppingCart.class.php");
 *	$cart = new ShoppingCart("someuser", 5);
 *	echo "Hi, " . $cart->getUser(). "! Purchasing: ". $cart->getItem()->name[0];
 *
 * @author Bryan Copeland
 * @date 2015-05-12
 * @version 1.0
 */

class ShoppingCart {
 
	private $customer;
	private $cart;
	private $conn;
	
	/* configuration constants */
	private $MINIMUM_CUSTOMER_AGE = 18; //years old to allow purchases
	private $DB_SERVER = "localhost";
	private $DB_NAME = "ecommerce";
	private $DB_USER = "myshop"; 		//your DB username here    "~USERNAME~"
	private $DB_PWD = "4io1_e9f}l7ka2"; //your DB password here    "~PASSWORD~"

	/* constructor */
	public function __construct() {
		establishSession();
		connectToDB();
	}
	
	/* constructor 
	function __construct($customer, $cart) {
		$this->customer = $customer;
		$this->cart = $cart;
		establishSession();
		connectToDB();
	}
	*/
	
	/* initiate a SESSION if one doesn't already exist */
	private function establishSession() {
		if (session_status() == PHP_SESSION_NONE) { 
			session_start();
		} else {
			$customerID = $_SESSION['customerID']; //userID
			$cartID = $_SESSION['cartID']; //cartID			
		}
	}
	
	/* connect to the Persistence layer */
	private function connectToDB() {
		try {
			$this->conn = new PDO("mysql:host=$DB_SERVER;dbname=$DB_NAME", $DB_USER, $DB_PWD);
			// set the PDO error mode to exception
			$this->conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
		} catch(PDOException $e) {
			echo $sql . "
" . $e->getMessage();
		}
	}
	
	/* disconnect from the Persistence layer */
	private function disconnectDB() {
		$this->conn = null;
	}

	/*************************************************************************/
	/* Database */
	/*************************************************************************/	
	public function debugShoppingCartDB() {
		$sql = 'SHOW TABLES';
		if($conn->is_connected) {
			$query = $conn->pdo->query($sql);
			return $query->fetchAll(PDO::FETCH_COLUMN);
		}
		return FALSE;
	}
	
	/* DB - Create */
	public function createShoppingCartDB() {
		// add a record to "cart" table
		$sql = "INSERT INTO cart SET () VALUES (0,'','','','','','')";
		// use exec() because no results are returned
		$this->conn->exec($sql);
		//DEGUG:	
		echo "</pre>
Database read successfully

“; } /* DB – Read */ public function readShoppingCartDB($cartID) { //do Database lookup of items stored server-side in this cart (using ID) $query = $conn->prepare(‘SELECT * FROM cart WHERE cart_id = :cartID’); $query->bindValue(‘:cartID’, $cartID); $itemsDB = “”; //get items from Cart stored server-side (if any) //verify against Cookie values by looping through and comparing $cartCookie = $_COOKIE[“cart”]; $itemsCookie = $cartCookie.split(“,”); foreach ($itemsCookie as $itemCookie) { // } setcookie(“cart”,$cart,time()+10000); // name is your cookie’s name value is cookie’s value $int is time of cookie expires //compare lastChanged TIMESTAMP of $itemsCookie .vs. $itemsDB to pick final list of items (i.e. may have changed server-state from a different device with its own Cookies) $items = “”; //get items from Cookies stored client-side (if any) foreach ($items as $item) { //iterate through list of all items in cart $productID = $item[‘id’]; $productName = $item[‘name’]; $productDescription = $item[‘description’]; $productCurrency = $item[‘currency’]; $productPrice = $item[‘price’]; $productBrand = $item[‘brand_id’]; //TODO: bridge Item to Order $orderQuantity = $item[‘order_quantity’]; $orderDate = $item[‘order_date’]; //TODO: TABLES NOT ADDED YET $productStatus = $item[‘status’]; //available, sold out, last N left, etc $productCondition = $item[‘condition’]; //factory sealed, like new, satisfactory, poor but usable, broken, etc $productPackaging = $item[‘packaging’]; //new, used, opened in box, etc $productReleaseYear = $item[‘release_year’]; $productManufactureYear = $item[‘manufacture_year’]; $productWidth = $item[‘width’]; $productHeight = $item[‘height’]; $productDepth = $item[‘depth’]; $productWeight = $item[‘weight’]; /* DEBUG: */ echo ”

    • “.$productName.”

 

“; //keep adding items for debugging purposes /**/ } } /* DB – Update */ public function updateShoppingCartDB() { //UPDATE table SET item_id={1} WHERE user_id=? AND session_id=?; } /* DB – Update cart but remove a specific item */ public function deleteItemFromShoppingCartDB() { //UPDATE table SET cart_json={1} WHERE session_id=?; } /* DB – Delete (clear the whole cart) */ public function deleteShoppingCartDB() { //DELETE FROM table WHERE user_id=? AND session_id=?; } /*************************************************************************/ /* COOKIES */ /*************************************************************************/ function debugShoppingCartCookie() { // Print an individual cookie echo $_COOKIE[“ShoppingCart”]; // Another way to debug/test is to view all cookies for this domain print_r($_COOKIE); } /* Cookie – Create */ function createShoppingCartCookie($name, $value=”, $expire=0, $path=”, $domain=”, $secure=false, $httponly=false) { $_COOKIE[$name] = $value; return setcookie($name, $value, $expire, $path, $domain, $secure, $httponly); } /* Cookie – Read */ function readShoppingCartCookie($name) { return $_COOKIE[$name]; } /* Cookie – Update */ function updateShoppingCartCookie($name, $value) { $_COOKIE[$name] = $value; return setcookie($name, $value); } /* Cookie – Delete */ function deleteShoppingCartCookie($name) { unset($_COOKIE[$name]); return setcookie($name, NULL, -1); } /* Add an item to the Shopping Cart */ function addItemToCart($items, $customer_id, $item_id, $item_quantity) { if($item_id < 1 || $item_quantity < 1) { return; } if(is_array($_SESSION[‘cart’])) { if(itemInCart($item_id)) { return; } $items = count($_SESSION[‘cart’]); $_SESSION[‘cart’][$items][‘user_id’] = $customer_id; $_SESSION[‘cart’][$items][‘item_id’] = $item_id; $_SESSION[‘cart’][$items][‘item_quantity’] = $item_quantity; } else { $_SESSION[‘cart’] = array(); $_SESSION[‘cart’][0][‘user_id’] = $customer_id; $_SESSION[‘cart’][0][‘item_id’] = $item_id; $_SESSION[‘cart’][0][‘item_quantity’] = $item_quantity; } return true; } /* check if Item already exists in Shopping Cart */ function itemInCart($item_id) { $item_id = intval($item_id); $items = count($_SESSION[‘cart’]); $flag = 0; for($i=0; $i<$items; $i++) { if($item_id == $_SESSION[‘cart’][$i][‘item_id’]) { $flag = 1; break; } } return $flag; } /* Remove Item from Shopping Cart */ function removeItemFromCart($item_id) { $item_id = intval($item_id); $items = count($_SESSION[‘cart’]); for($i=0; $i<$items; $i++){ if($pid == $_SESSION[‘cart’][$i][‘item_id’]){ unset($_SESSION[‘cart’][$i]); break; } } $_SESSION[‘cart’] = array_values($_SESSION[‘cart’]); } /* object-oriented Getters */ function getCustomer() { return $this->customer; } function getItem() { return $this->item; } function getUser() { return $this->user; } function getReceipt() { return $this->receipt; } function getOrder() { return $this->order; } /* object-oriented Setters */ function setCustomer($customer) { $this->customer = $customer; } function setItem($item) { $this->item = $item; } function setUser($user) { $this->user = $user; } function setReceipt($receipt) { $this->receipt = $receipt; } function setOrder($order) { $this->order = $order; } /*************************************************************************/ /* HELPER DB LOOKUPS */ /*************************************************************************/ //1. DIRECTION function renderDirectionDropdown() { //SELECT UNIQUE(province_code, province_name) FROM province WHERE province_name LIKE ‘%___%’; } //2. LOCATION_TYPE function renderLocationTypeDropdown() { //SELECT UNIQUE(location_type_id, location_type_name) FROM province WHERE location_type_name LIKE ‘%___%’; } //3. STREET_TYPE function renderStreetTypeDropdown() { //SELECT UNIQUE(street_type_id, street_type_name) FROM street_type WHERE street_type_name LIKE ‘%___%’; } //4. ADDRESS_TYPE function renderAddressTypeDropdown() { //SELECT UNIQUE(address_type_id, address_type_name) FROM address_type WHERE address_type_name LIKE ‘%___%’; } //5. CITY function renderCityAutofill() { //SELECT UNIQUE(city_name) FROM city WHERE city_name LIKE ‘%___%’; } //6. PROVINCE function renderProvinceDropdown() { //SELECT UNIQUE(province_id, province_name) FROM province WHERE province_name LIKE ‘%___%’; } //7. COUNTRY function renderCountryDropdown() { //SELECT UNIQUE(country_id, country_name) FROM province WHERE province_name LIKE ‘%___%’; } //8. ADDRESS (private) function renderAddressDropdownSelections() { //SELECT * FROM address WHERE address_id=?; //////will need to then pre-fill the already selected values while rendering, i.e. ////// } //9. CURRENCY function renderCurrencyDropdown() { //SELECT * FROM currency; } //10. LANGUAGE function renderLanguageDropdown() { //SELECT * FROM language; } //11. USER (private) function renderUserProfileInfo() { //SELECT * FROM user WHERE user_id=?; } //12. USER_ADDRESS (private) function renderUserAddressBook() { //SELECT * FROM user_address WHERE user_id=?; ///// used to iterate a list of Mailing, Billing, Home, Work, etc… addresses for a particular user (possibly Tabbed, or Accordioned in…) } //13. CUSTOMER (private) function getCustomerSessions() { //SELECT * FROM customer WHERE user_id=?; ///// get list of active Customer sessions } //14. COMPANY function renderCo mpanyDirectory() { //SELECT * FROM company WHERE company_name LIKE ‘%___%’ } //15. BRAND function renderCompanyBrands() { //SELECT * FROM brand WHERE company_name LIKE ‘%___%’ } //16. PRODUCT_TYPE function renderProductTypeMenu() { //SELECT * FROM product_type WHERE product_type_name LIKE ‘%___%’ } //17. BARCODE_TYPE function renderSupportedBarcodeTypes() { //SELECT * FROM barcode_type; } //18. ITEM function renderItemList($item_name=”) { //SELECT * FROM item WHERE item_name LIKE ‘%___%’; } //19. ORDER (private) function getCustomerOrders($customer) { //SELECT * FROM order WHERE customer=?; } //20. RECEIPT (private) function getCustomerReceipts($customer) { //SELECT * FROM receipt WHERE customer=?; } /*************************************************************************/ /* UTILITIES */ /*************************************************************************/ /* check if user is an adult, as defined by config settings */ function is_adult() { return ($this->age >= $MINIMUM_CUSTOMER_AGE) ? true : false; } //to use in Production, pass required info to a Payment Gateway (i.e. PaySwarm, BitPay, PayPal, 2Checkout, OptimalPaySafe, Moneris, VISA, MasterCard, Amex, INTERAC e-Transfer, Invoice/Cheque, etc) function transferToPaymentGateway($params) { $HOSTED_PAYMENT_PAGE = “”; $PAYMENT_GATEWAY_ID = “”; $PAYMENT_GATEWAY_PARAMS = (!empty($params)) ? $params : “param1=VAL1&param2=VAL2”; return ‘‘; } function sendEmailPurchaseConfirmation() { //PHP SendMail } /* convert PHP array to JSON */ function array_to_json_string($arraydata) { $output = “”; $output .= “{“; foreach($arraydata as $key=>$val) { if (is_array($val)) { $output .= “””.$key.”” : [{“; foreach($val as $subkey=>$subval){ $output .= “””.$subkey.”” : “”.$subval.””,”; } $output .= “}],”; } else { $output .= “””.$key.”” : “”.$val.””,”; } } $output .= “}”; return $output; } /* convert JSON to PHP array */ function json_to_array($jsondata) { return json_decode($jsondata, true); } /* remove all items from the ShoppingCart database for this particular customer & session */ function empty_shopping_cart() { /*TODO: define… */ } /* destroy the Shopping Cart session */ function clear_server_session() { /*TODO: define… server-side PHP_SESSION clearing (when user logs out, or, during server cleanup process via Batch session cleanup script, i.e. 7-day limit to buy) */ } /* delete all cookies for this domain from the client’s browser */ function clear_client_session() { if (isset($_SERVER[‘HTTP_COOKIE’])) { //JSONp connection to JS to clear cookies $host = explode(‘.’, $_SERVER[‘HTTP_HOST’]); while ($host) { $domain = ‘.’ . implode(‘.’, $host); foreach ($_COOKIE as $name => $value) { setcookie($name, ”, 1, ‘/’, $domain); } array_shift($host); } } } } ?>

 

You can check it out in action here:


-OR-
00%

Leave a Reply

No trackbacks yet.

No post with similar tags yet.

Posts in similar categories

BC$ = Behavior, Content, Money

The goal of the BC$ project is to raise awareness and make changes with respect to the three pillars of information freedom - Behavior (pursuit of interests and passions), Content (sharing/exchanging ideas in various formats), Money (fairness and accessibility) - bringing to light the fact that:

1. We regularly hand over our browser histories, search histories and daily online activities to companies that want our money, or, to benefit from our use of their services with lucrative ad deals or sales of personal information.

2. We create and/or consume interesting content on their services, but we aren't adequately rewarded for our creative efforts or loyalty.

3. We pay money to be connected online (and possibly also over mobile), yet we lose both time and money by allowing companies to market to us with unsolicited advertisements, irrelevant product offers and unfairly structured service pricing plans.

  • Archives