*/ ?>
Shopping cart management

In this HOWTO, you will see how you can introduce shopping cart functionalities in your apps: add articles to cart, display content, validate order...

HOWTO's context:

The examples exposed here are based on the explanations given in the "Dynamic XML list" and "User session" HOWTOs; if you haven't read them yet, you should do it before you get any further.

In the previous HOWTOs, you learned how to generate a list of articles on the fly, and how to link a session to a user account.

What we want here is to allow a logged in user to add articles in his shopping cart, to remove them, to display the content of the cart, and of course to order them.

Requirements

To start with, you should ahve read previous articles: "Dynamic XML list" and "User session".

To test the sample scripts of this HOWTO, you need a web server with PHP language and MySQL database. Minimal comprehension of PHP and MySQL is required.

You can modify following examples to test them with another language or DB. The only technical constraint is : the produced XML document must be compliant to AppMobile XML format.

If you don't have a server, just type "free hosting php mysql" in your favourite internet search engine and find plenty of solutions that will be more than enough for your testing phase.

Awaited functionalities

We already have scripts to display the products catalog: list of categories, list of products of a given category, and detailed page of an article (see "Dynamic lists" HOWTO).
We also have everything needed to deal with user's log in & log out (see "User session" HOWTO).

So what what we still have to write is:
  • a script to add articles to the current user's cart or to remove articles from it;
  • a script to display the content of the cart;
  • a script to order the content of the cart;
  • a script to show a list of past orders and another to show the details of a given order.
Some improvements will also be brought to the scripts of previous HOWTOs:
  • show the quantity-in-cart in the article page, and add [+] & [-] buttons calling the appropriate script to add/remove articles into cart;
  • display the quantity-in-cart for each item in the articles list.

Data structure

One user can have more than one article in his cart, and one article can be refered to in several user's carts. From a technical point of view, this is a "many to many" relation between the tables users and articles.

So we have to create the amdoc_orders link table.

Cart and orders table :

CREATE TABLE IF NOT EXISTS `amdoc_orders` (
  `id_user` int(11) NOT NULL,
  `id_prod` int(11) NOT NULL,
  `qty` int(11) NOT NULL,
  `status` tinyint(4) NOT NULL DEFAULT '0',
  `id_order` int(11) NOT NULL,
  `comment` varchar(300) DEFAULT '',
  PRIMARY KEY (`id_user`,`id_prod`,`id_order`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;

Add/remove an article to cart

The script has to:

This script doesn't produce any output to be displayed on the smartphone, it just handles server side DB queries and returns a value (quantity in cart if everything ok, or error code if something went wrong).
Refreshing the display depending on the return value is the job of the calling page or list (AJAX call / Javascript analyse of return value, as detailed below). You can naturally have the scripts working another way without Javascript, this is just a choice we made here to show that you can use AppMobile in smarter ways than just calling a script to generate a page or a list.

Warning: we decided to make use of the deprecated mysql_* functions in the demo scripts, for larger compatibility reasons. You should NOT run these in production, but download the PDO based modules scripts instead.

cart_add.php :

<?php
include('config.php');
$id=$_GET["id"];
$id_mvs=$_GET["id_mvsappmobile"];
$toAdd=$_GET["qty"];
function quit_script($errorMsg) {//to be customized depending on your app's specific return neeeds in matter of return value
	die($errorMsg);
}
if(!is_numeric($toAdd)||$toAdd==0) { quit_script("Bad_parameter"); }
$db =  @mysql_connect($dbhost, $dbuser, $dbpassword);
if(!$db) { quit_script("DB_connect_error"); } // MySQL connection problem
if(!@mysql_select_db($dbname)) { quit_script("DB_select_error"); } // MySQL DB selection problem
$query="SELECT u.id_user, p.id_prod, o.qty FROM `amdoc_users` u 
	left join `amdoc_orders` o on u.id_user=o.id_user and o.id_prod='$id' and o.status=0
	left join `amdoc_products` p on p.id_prod='$id' and p.del=0
	where u.id_mvsappmobile='$id_mvs'";
/* user not connected:empty result -- unexisting prod:id_prod=null -- prod not in cart yet:qty=null */
$result = @mysql_query($query,$db);
if(!$result) { quit_script("DB_query_error"); }
if(mysql_num_rows($result)==0) { quit_script("Unlogged"); }
extract(mysql_fetch_assoc($result));
if($id_prod===null) { //unexisting prod 
	quit_script("Unexisting_prod"); }
if($qty===null) //prod not in cart yet
	if($toAdd>0) { //add if $toAdd>0, or ignore
		$req="INSERT INTO `$db_tbl_ord` (`id_user`,`id_prod`,`qty`, `id_order`, `status`) values ('$id_user','$id_prod',$toAdd,'0','0')";
		$res=$toAdd;}
	else {$req="";$res=0;} //remove product not in cart yet... nothing to do
else if($toAdd<0 && $qty<=(-$toAdd)) { //less in cart than qty to remove -> delete row
	$req="DELETE FROM `$db_tbl_ord` where `id_user`='$id_user' and `id_prod`='$id_prod' and `status`=0";
	$res=0;}
else { //in all other cases
	$req="update `$db_tbl_ord` set `qty`=`qty`+$toAdd where `id_prod`='$id' and `id_user`='$id_user' and `status`=0";
	$res=$qty+$toAdd;}
if($req=="") { quit_script("Nothing_to_do"); }
$result = @mysql_query($req,$db);
@mysql_close($db);
if(!$result) quit_script("DB_write_error");
echo($res);
?>

Display "cart-controls" in the article page

We now have a script adding articles into the user's cart or removing articles from it... That's wonderful, but how do we call this script?

A nice, intuitive and classic wy to do this is to add a little [ - ][Qty][ + ] block on the article page.

To avoid refreshing the whole page when the user taps on the + or - button, which could cause the display to blink in an unpleasant way (especially whith smartphone slower connections), we wrote following script using a light touch of Javascript and AJAX call, in order to refresh only the quantity value.

art_description.php :

<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<script>
//<![CDATA[
function affiche(elementID,texttoprint){
	//todo: here just add a little test on "texttoprint" in order to display an error message if necessary
	document.getElementById(elementID).innerHTML = texttoprint;
	if(texttoprint=="0") {
		document.getElementById('commentForm').style.display = "none";
		document.getElementById('comment').innerHTML = "";
	}
	else document.getElementById('commentForm').style.display = "block";
}
function ajaxPost(myurl,param){
	if (window.XMLHttpRequest){xmlhttp=new XMLHttpRequest();}// code for IE7+, Firefox, Chrome, Opera, Safari
	else {xmlhttp=new ActiveXObject("Microsoft.XMLHTTP");}// code for IE6, IE5
	xmlhttp.open("post",myurl,false);
	xmlhttp.setRequestHeader("Content-type","application/x-www-form-urlencoded");
	xmlhttp.send(param);
	affiche("qty",xmlhttp.responseText);
}
function ajaxGet(myurl,param){
	if (window.XMLHttpRequest){xmlhttp=new XMLHttpRequest();}// code for IE7+, Firefox, Chrome, Opera, Safari
	else {xmlhttp=new ActiveXObject("Microsoft.XMLHTTP");}// code for IE6, IE5
	xmlhttp.open("get",myurl+"?"+param,false);
	xmlhttp.send(null);
	affiche("qty",xmlhttp.responseText);
}
//]]>
</script>
</head>
<?php
    require_once('config.php');
    $id=$_GET["id"];//produit
		$uid=$_GET["id_mvsappmobile"];//user
    $db =  mysql_connect($dbhost, $dbuser, $dbpassword);
    mysql_select_db($dbname);
    $query = "SELECT p.name, p.descr,p.pic, p.price, c.qty, c.comment FROM $db_tbl_prod as p left JOIN $db_tbl_ord as c ON p.id_prod = c.id_prod and c.id_user=(select id_user from $db_tbl_user where id_mvsappmobile='$uid') and c.id_order=0 where p.id_prod='$id'";
    $result = mysql_query($query,$db);
    mysql_close($db);

    $l = mysql_fetch_assoc($result);
		extract($l);//$name=$l["name"]; $descr=$l["descr"]; $qty=$l["qty"]; $comment=$l["comment"];
		if($qty==null) $qty=0; if($comment==null) $comment="";
?>
<body style="width:100%;margin:0;padding:0;">
	<div><h1 style="text-align:center;margin-top:5px;margin-bottom:5px"><b><?echo($name);?></b><br /></h1></div>
	<div><?php echo("<img src='".$urlprefix."/pic/$pic' />");?></div>
	<div style="width:80%;margin-right:auto;margin-left:auto;">
		<p style="text-align:right;margin-top:0px;"><?echo(number_format($price, 2, ',', ' ')." €");?><br /></p>
		<p><?echo($descr);?><br /></p>
		<p>
			<? echo("<a style='text-decoration: none' 
				href=\"javascript:ajaxGet('".$urlprefix."/cart_add.php','id=$id&id_mvsappmobile=$uid&qty=-1')\">");?>
				<img src="<?php echo($urlprefix);?>pic/minus.png" style="width:24px;height:24px;vertical-align:-7px;" />
			</a>
			<span id='qty' style='border:1px solid;padding:0 15px'><?echo($qty);?></span>
			<? echo("<a style='text-decoration: none' 
				href=\"javascript:ajaxGet('".$urlprefix."/cart_add.php','id=$id&id_mvsappmobile=$uid&qty=1')\">");?>
				<img src="<?php echo($urlprefix);?>pic/plus.png" style="width:24px;height:24px;vertical-align:-7px;" />
			</a>
		</p>
	</div>
</body>

Note: The called cart_add.php script awaits "GET" parameters. So the javascript AjaxPost() function is not used, it's just given here to help you easily adapt the scripts depending on your needs.

Display cart content

No need here for long explanations, everything has been explained above concerning the shopping cart data structure.
To display the cart is nothing more than requesting the DB for articles and quantities in the cart of a logged in user, then a loop to add an item for each article in the XML list, with a few particular cases to handle (unlogged user, empty cart...).

cart_list.php :

<?php
include('config.php');//config data, especially $dbhost,$dbuser,... for DB connection 
$id_mvs=$_GET['id_mvsappmobile']; //session ID
$xml="<?xml version='1.0' encoding='UTF-8'?>\r\n<items version='1.0' type='1' reload='1' locate='0'>";//XML list description
header ("Content-Type:text/xml; charset=utf-8");//http header to tell it's XML content
function add_item(&$xmlstring, $typ, $img, $tx1, $tx2, $tx3, $link) {
	$xmlstring.="
	<item type='$typ'> 
		<img>$urlprefix/$img</img>
		<txt1>$tx1</txt1>
		<txt2>$tx2</txt2>    
		<txt3>$tx3</txt3>
		<link type='1' url='$link' title='Panier' />
	</item>";
}
function quit_script(&$xmlstring, $pic, $Msg1, $Msg2="", $Msg3="",$lnk="") {
	add_item($xmlstring, 1, $pic, $Msg1, $Msg2, $Msg3, $lnk);
	echo($xmlstring."\r\n</items>");
	exit;
}
$db =  @mysql_connect($dbhost, $dbuser, $dbpassword);
if(!$db) { quit_script($xml,"pic/noConnec.png","Pas de connexion,","Merci de réessayer plus tard."); } //MySQL connection problem
if(!@mysql_select_db($dbname)) { quit_script($xml,"pic/noConnec.png","Pas de connexion,","Merci de réessayer plus tard."); } //MySQL DB selection problem
$query="SELECT u.id_user, p.id_prod, p.name, p.price, p.del, p.pic, o.qty, c.name as catname
	FROM `amdoc_users` u 
	left join `amdoc_orders` o on u.id_user=o.id_user and o.status=0
	left join `amdoc_products` p on p.id_prod=o.id_prod 
	left join `amdoc_categories` c on c.id=p.cat
	where u.id_mvsappmobile='$id_mvs'";
	//empty if not connected -- one single line with id_prod==null if empty cart -- one or more lines if cart not empty 
$result = @mysql_query($query,$db);
if (!$result) { quit_script($xml,"pic/noConnec.png","Pas de connexion","Merci de réessayer plus tard."); } //Problème MySQL
if(mysql_num_rows($result)==0) { quit_script($xml,"pic/noSession.png","Vous n'êtes pas connecté,","Merci de vous identifier.","(onglet \"Accueil\")"); }//no connected user for this session
while ($l = mysql_fetch_assoc($result)) {
	extract($l);
	if($del!=0)$name="[Supprimé: $name]";//attirer l'attention de l'utilisateur si un produit du panier n'existe plus...
	//echo($query."     *****     ".print_r($l,true));
	if($id_prod===null) { quit_script($xml,"pic/emptyCart.png","Panier vide","Consultez la carte pour faire votre choix"); }
	add_item($xml,'1',"pic/th_$pic",$catname,"$name".($qty>1?" (x$qty)":""),number_format($price, 2, ',',' ')." €","$urlprefix/art_description.php?id=$id_prod&id_mvsappmobile=$id_mvs");
	//for a picture whose name 'picname.png' you store in product DB entry, create a thumbnail picture named 'th_picname.png'
}
echo("$xml
</items>");
?>

Transform cart content to order

This is nothing more than
  • for each row of the user's cart, change the status field from "0" (pending cart) to "1" (validated cart, ie order);
  • affect an order number to these rows
  • alert the concerned persons/services in order to process the order. Here we send an e-mail to the address defined in config.php. You can adapt the notify_order() function to redefine the action to be taken: send the information to your sales management software, to your ERP, export the order to your e-commerce website...

cart_order.php

<?php
include('config.php');
$id_mvs=$_GET["id_mvsappmobile"];
function quit_script($errorMsg) {//to be customized depending on your app's specific neeeds in matter of return value
	die("<p>$errorMsg</p></body></html>");
}
function notify_order($msg) {//to be customized depending on your app's specific neeeds in matter of return value
	$headers  = "From: \"SpeedZa\"<speedza@fpierrat.fr>\n";
	$headers .= "Reply-To: speedza@fpierrat.fr\n";
	$headers .= "Content-Type: text/plain; charset=\"utf-8\"\n";
	$headers .= "Content-Transfer-Encoding: 8bit";
	$msg = wordwrap($msg, 70, "\r\n");//lines over 70 characters -> split with wordwrap()
	$mailRet=mail($emailaddress, "Commande smartphone", $msg, $headers);//$emailaddress defined in config.php
}
echo("<html><head><meta http-equiv='Content-Type' content='text/html;charset=UTF-8' /></head><body>");
$db =  @mysql_connect($dbhost, $dbuser, $dbpassword);
if(!$db) { quit_script("noConnection"); } // MySQL connection problem
if(!@mysql_select_db($dbname)) { quit_script("noConnection"); } // MySQL DB selection problem
$query = "select p.name as name, cat.name as catname, o.qty, o.comment, u.id_user 
	from `$db_tbl_ord` o, `$db_tbl_prod` p, `$db_tbl_cat` cat, `$db_tbl_user` u 
	where o.`id_user`=u.id_user and u.id_mvsappmobile='$id_mvs' and o.`status`=0 and p.`id_prod`=o.`id_prod` and p.cat=cat.id and p.del=0";
$result = @mysql_query($query,$db);
if(!$result) quit_script("queryFail"); // MySQL query problem
if (mysql_num_rows($result) == 0) quit_script("emptyCart_or_notConnected");
while ($l = mysql_fetch_assoc($result)) {
	extract($l);//$name=,$catname,$qty,$comment, $id_user
	$message .= "\r\n$qty x $catname  --  $name\r\n";
	if($comment!=null) $message.="  -> $comment\r\n";
}
$query = "delete from o using $db_tbl_ord as o, $db_tbl_prod as p 
	where o.`status`=0 and o.`id_user`=$id_user and p.id_prod=o.id_prod and p.del!=0";//delete lines concerning eventually deleted items. Todo: user should be explicitly notified.
$result2 = @mysql_query($query,$db);
if(!result2) echo("Un problème a été rencontré avec des produits supprimés.");//debug msg, unnecessary for user to know
$query = "UPDATE $db_tbl_ord set id_order = ((SELECT selected_value FROM (SELECT MAX(id_order) AS selected_value FROM $db_tbl_ord) AS sub_selected_value) + 1), `status`=1 WHERE `id_user`=$id_user and `status`=0";//définit un nouvel ID de commande (max+1) et l'assigne à chaque ligne du panier
$result3 = @mysql_query($query,$db);
if(!result3) quit_script("Erreur: votre commande n'a pas pu être validée.");
notify_order($message);
echo("<p style='margin-top:20px'>Votre commande a été envoyée à $emailaddress</p><p style='margin-top:10px;'>Sa préparation sera finalisée dès votre arrivée.</p><p>Le paiement aura lieu au comptoir.</p>
<p style='margin:10px 0;'>A bientôt!</p>
<p style='border-top:solid 1px;border-bottom:solid 1px;margin-top:40px;text-align:center;'>Copie de la commande envoyée :</p>
<p>".str_replace("\r\n","<br />",$message)."</p></body></html>");
?>

Le script une fois réalisé, il reste à prévoir un endroit d'où l'utilisateur pourra l'appeler. Ajoutons un item "Valider la commande" à la liste des articles en panier:

Ajout à: cart_list.php

<?php
add_item($xml, "1", "pic/valid-ico.jpg", "", "Valider la commande", "", "$urlprefix/cart_order.php?id_mvsappmobile=$id_mvs");
?>

What comes next?...

Maybe you'll need a back-office interface to process and keep track of customers orders (open, shipped, delivered,...) as well as to manage the products catalog base. These aspects are not handled in AppMobile's HOWTOs for several reasons:

However, if you needed such a simple order visualization an processing tool, then the data structures and examples presented in these HOWTOs should be a good base for it.

TODO: Pour la gestion des produits, adapter et mettre en ligne les scripts "admin" existants.