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).
- 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.
- 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.
- Each row of this table refers to one user (id_user field), and to one article (id_prod field).
- The quantity of the "id_prod" article that the "id_user" user set in the cart is stored in the qty field.
- One more information is needed about this row: its status. Does this row belong to a pending shopping cart? to a validated one (which actually means an order)? to an order that has been delivered? received by the customer? This information is stored in the status field.
Note: this information needs to be analysed row by row, not globally for all rows beonging to an order: all lines of a same order won't necessary have the same status. For example, all articles are delivered, except for one that is out of stock. - Once a user validates his cart, it becomes an order, to which a number must be given for practical reasons (logistic and administrative workflows): this is the role of the id_order field. Its value is zero as long as the cart isn't validated.
- Now, this table may contain all kind of useful information addressing the specific needs of your application. For example, let's add a (comment) field, so the user can enter remarks while adding an article to his cart.
One could also imagine logging the date/hour of shipment, the carrier reference and so on...
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:
- get the session ID (id_mvsappmobile, see "User session" HOWTO).
- get the article ID and the quantity Q1 to add to cart
(Q1>0: add ; Q1<0: remove) - request the DB to check if the session ID belongs to a logged in user, get his user ID, check if the article exists, and if there's already a non validated row for this user and this article
(a validated row would mean an already placed order, which cannot be modified any more).- if no connected user, or unexisting article, exit.
- if no existing row in cart, create it.
- if the row already exists, the qty field must be updated to the new quantity (old quantity Q2 + quantity to add Q1).
Note: when the user removes articles from cart (Q1<0), if the new quantity (Q2+Q1) is negative or zero, the row must be deleted from cart table.
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 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
- 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:
- first of all, because these are usually processed through a classic web based interface, not on smartphone, so they are out of the scope of the present explanations;
- then, because customer's orders recording applications for smartphone usually comes as a complementary tool to an existing system (e-commerce web-site, sales management software, ERP...). It wouldn't make much sense to re-develop a whole system working in parallel, instead of having the AppMobile orders exported to the existing system;
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.