Welcome to a tutorial on how to create a simple shopping cart with PHP and MySQL. Yes, there are a ton of shopping cart scripts in the world, but they are also bloated with features that people rarely even use. So let us walk through a basic shopping cart, without using any third-party frameworks – Read on!
TABLE OF CONTENTS
DOWNLOAD & NOTES
Here is the download link to the example code, so you don’t have to copy-paste everything.
EXAMPLE CODE DOWNLOAD
Just click on “download zip” or do a git clone. I have released it under the MIT license, so feel free to build on top of it or use it in your own project.
SORRY FOR THE ADS...
But someone has to pay the bills, and sponsors are paying for it. I insist on not turning Code Boxx into a "paid scripts" business, and I don't "block people with Adblock". Every little bit of support helps.
Buy Me A Coffee Code Boxx eBooks
PHP MYSQL SHOPPING CART
All right, let us now get started with building a shopping cart.
PART 1) THE DATABASE
1A) PRODUCTS TABLE
-- (A) PRODUCTS TABLE
CREATE TABLE `products` (
`pid` bigint(20) NOT NULL,
`name` varchar(255) NOT NULL,
`image` varchar(255) NOT NULL,
`price` decimal(10,2) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
ALTER TABLE `products`
ADD PRIMARY KEY (`pid`);
ALTER TABLE `products`
MODIFY `pid` bigint(20) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=1;
Let us start by addressing the elephant in the room, we need a table to store the products.
pid
Product ID, primary key.name
Product name.image
Product image.price
Product price.
1B) ORDERS TABLE
-- (B) ORDERS TABLE
CREATE TABLE `orders` (
`oid` bigint(20) NOT NULL,
`date` datetime NOT NULL DEFAULT current_timestamp(),
`name` varchar(255) NOT NULL,
`email` varchar(255) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
ALTER TABLE `orders`
ADD PRIMARY KEY (`oid`),
ADD KEY `name` (`name`),
ADD KEY `email` (`email`),
ADD KEY `date` (`date`);
ALTER TABLE `orders`
MODIFY `oid` bigint(20) NOT NULL AUTO_INCREMENT;
Then, a table to hold the order details – The customer’s name, email, order date, and whatever is required.
oid
Order ID, primary key.date
Date of purchase.name
Customer’s name.email
Customer’s email.
1C) ORDER ITEMS TABLE
-- (C) ORDERS ITEMS TABLE
CREATE TABLE `orders_items` (
`oid` bigint(20) NOT NULL,
`name` varchar(255) NOT NULL,
`price` decimal(10,2) NOT NULL,
`qty` int(11) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
ALTER TABLE `orders_items`
ADD PRIMARY KEY (`oid`,`name`);
Lastly, another table to hold all the order items.
oid
Order ID, primary and foreign key.name
Product name, primary and foreign key.price
Product price (per item).qty
Quantity.
Some of you sharp code ninjas may be wondering – Why not just use the product ID as the foreign key? Isn’t the name and price redundant? Consider what happens when a product is updated or deleted in the future… Yep, keeping the product name and price at the time of checkout is “data integrity”, not “redundant”.
PART 2) SHOPPING CART LIBRARY
<?php
class Cart {
// (A) CONSTRUCTOR - CONNECT TO DATABASE
private $pdo = null;
private $stmt = null;
public $lastID = null;
public $error = "";
function __construct() {
$this->pdo = new PDO(
"mysql:host=".DB_HOST.";dbname=".DB_NAME.";charset=".DB_CHARSET,
DB_USER, DB_PASSWORD, [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC
]);
}
// (B) DESTRUCTOR - CLOSE DATABASE CONNECTION
function __destruct() {
if ($this->stmt !== null) { $this->stmt = null; }
if ($this->pdo !== null) { $this->pdo = null; }
}
// (C) HELPER - EXECUTE SQL QUERY
function exec ($sql, $data=null) : void {
$this->stmt = $this->pdo->prepare($sql);
$this->stmt->execute($data);
}
// (D) GET PRODUCTS
function getProducts ($cart=false) {
// (D1) GET CART ITEMS - EMPTY
if ($cart && $_SESSION["cartC"]==0) { return null; }
// (D2) GET ALL PRODUCTS OR ITEMS IN CART?
$sql = "SELECT * FROM `products`";
$data = null;
if ($cart) {
$sql .= " WHERE `pid` IN (";
$sql .= str_repeat("?,", count($_SESSION["cartI"]) - 1) . "?";
$sql .= ")";
$data = array_keys($_SESSION["cartI"]);
}
// (D3) FETCH PRODUCTS
$this->exec($sql, $data);
unset($sql); $data = [];
while ($r = $this->stmt->fetch()) { $data[$r["pid"]] = $r; }
// (D4) REMOVE ILLEGAL CART ITEMS
if ($cart) { foreach ($_SESSION["cartI"] as $id=>$qty) {
if (isset($data[$id])) { $data[$id]["qty"] = $qty; }
else {
$_SESSION["cartC"] -= $_SESSION["cartI"][$id];
unset($_SESSION["cartI"][$id]);
}
}}
// (D5) DONE
return count($data)!=0 ? $data : null ;
}
// (E) CHECKOUT
function checkout ($name, $email) {
// (E1) GET ITEMS + CHECK
$items = $this->getProducts(true);
if ($_SESSION["cartC"] == 0) {
$this->error = "Cart is empty";
return false;
}
// (E2) AUTO-COMMIT OFF
$this->pdo->beginTransaction();
// (E3) MAIN ORDER
$this->exec("INSERT INTO `orders` (`name`, `email`) VALUES (?,?)", [$name, $email]);
$oid = $this->pdo->lastInsertId();
// (E4) ORDER ITEMS
$sql = "INSERT INTO `orders_items` (`oid`, `name`, `price`, `qty`) VALUES ";
$sql .= str_repeat("(?,?,?,?),", count($_SESSION["cartI"]));
$sql = substr($sql, 0, -1) . ";";
$data = [];
foreach ($_SESSION["cartI"] as $pid=>$qty) {
array_push($data, $oid, $items[$pid]["name"], $items[$pid]["price"], $qty);
}
$this->exec($sql, $data);
// (E5) COMMIT + EMPTY CART
$this->pdo->commit();
$_SESSION["cartI"] = [];
$_SESSION["cartC"] = 0;
return true;
}
}
// (F) DATABASE SETTINGS - CHANGE TO YOUR OWN!
define("DB_HOST", "localhost");
define("DB_NAME", "test");
define("DB_CHARSET", "utf8mb4");
define("DB_USER", "root");
define("DB_PASSWORD", "");
// (G) NEW CART OBJECT
$_CART = new Cart();
With the database in place, we can now build a library to work with it. This looks intimidating, but keep calm and look closely.
- (A, B, G) When
$_CART = new Cart()
is created, the constructor connects to the database. The destructor closes the connection. - (C)
exec()
A helper function to run an SQL query. - (D, E) There are only 2 “actual shopping cart” functions.
getProducts()
Get all products from the database, or the current cart items.checkout()
Self-explanatory. Checkout the current cart.
- (F) Remember to change the database settings to your own.
PART 3) CART AJAX HANDLER
<?php
if (isset($_POST["req"])) {
// (A) INIT SHOPPING CART
session_start();
if (!isset($_SESSION["cartI"])) {
$_SESSION["cartI"] = []; // cart items
$_SESSION["cartC"] = 0; // total quantity
}
// (B) UPDATE CART COUNT
function ccount () {
$_SESSION["cartC"] = 0;
if (count($_SESSION["cartI"])!=0) {
foreach ($_SESSION["cartI"] as $id=>$qty) { $_SESSION["cartC"] += $qty; }
}
}
// (C) STANDARD SYSTEM RESPONSE
function respond ($status=1, $msg="OK") {
echo json_encode(["status"=>$status, "msg"=>$msg, "count" => $_SESSION["cartC"]]);
}
// (D) CART ACTIONS
switch ($_POST["req"]) {
// (D1) GET COUNT
case "count": respond(); break;
// (D2) ADD / CHANGE QUANTITY / REMOVE
// send id only to add item
// send id and qty to set quantity
// send id and 0 qty to remove item
case "set":
$max = 99; // max allowed quantity per item
$item = &$_SESSION["cartI"][$_POST["id"]];
if (isset($_POST["qty"])) { $item = $_POST["qty"]; }
else { if (isset($item)) { $item++; } else { $item = 1; } }
if ($item<=0) { unset($_SESSION["cartI"][$_POST["id"]]); }
if ($item > $max) { $item = $max; }
ccount(); respond(); break;
// (D3) NUKE
case "nuke":
$_SESSION["cartI"] = [];
$_SESSION["cartC"] = 0;
respond(); break;
// (D4) GET ALL ITEMS IN CART
case "get":
// (D4-1) CART IS EMPTY
if ($_SESSION["cartC"]==0) { respond(1, null); break; }
// (D4-2) GET ITEMS IN CART
require "2-lib-cart.php";
respond(1, $_CART->getProducts(true)); break;
// (D5) CHECKOUT
case "checkout":
require "2-lib-cart.php";
if ($_CART->checkout($_POST["name"], $_POST["email"])) { respond(); }
else { respond(0, $_CART->error); }
break;
}
}
Next, we have another piece of system foundation that drives the shopping cart. How it works is actually dreadfully simple:
- (A) We keep the shopping cart in the PHP session.
$_SESSION["cartI"]
contains the cart items, in the format ofPRODUCT ID => QUANTITY
.$_SESSION["cartC"]
contains the total quantity.
- (D) We send
$_POST["req"]
and the required parameters to perform an action. For example,$_POST = ["req"=>"add", "pid"=>123]
will add product ID 123 to the cart.
Request | Description & Parameters |
count |
Get the total quantity in the cart. |
set |
|
nuke |
Empty the entire cart. |
get |
Get all items in the cart. |
checkout |
Checkout the current cart.
|
PART 4) SHOPPING PAGE
4A) HTML SHOPPING PAGE
<!-- (A) PRODUCTS + SHOPPING CART -->
<div id="wrap">
<!-- (A1) HEADER -->
<div id="head">
<div id="iCart" onclick="cart.show()">
My Cart <span id="cCart">0</span>
</div>
</div>
<!-- (A2) PRODUCTS -->
<div id="products"><?php
require "2-lib-cart.php";
$products = $_CART->getProducts();
foreach ($products as $i=>$p) { ?>
<div class="pCell">
<img class="pImg" src="<?=$p["image"]?>">
<div class="pName"><?=$p["name"]?></div>
<div class="pPrice">$<?=$p["price"]?></div>
<input class="pAdd button" type="button" value="Add To Cart" onclick="cart.add(<?=$i?>)">
</div>
<?php } ?>
</div>
<!-- (A3) CART ITEMS -->
<div id="wCart">
<span id="wCartClose" class="button" onclick="cart.toggle(cart.hWCart, false)">⇦</span>
<h2>SHOPPING CART</h2>
<div id="cart"></div>
</div>
</div>
<!-- (B) CHECKOUT FORM -->
<div id="checkout"><form onsubmit="return cart.checkout()">
<div id="coClose" class="button" onclick="cart.toggle(cart.hCO, false)">X</div>
<label>Name</label>
<input type="text" id="coName" required value="Jon Doe">
<label>Email</label>
<input type="email" id="coEmail" required value="jon@doe.com">
<input class="button" type="submit" value="Checkout">
</form></div>
The system foundations are complete, and all we need is to build the shopping page. The essential parts are:
- (A2)
<div id="products">
We use the cart library to get all the products and draw the HTML here. - (A3)
<div id="cart">
The shopping cart items. - (B)
<div id="checkout">
The checkout form.
4B) SHOPPING CART JAVASCRIPT
var cart = {
// (A) HELPER - AJAX FETCH
ajax : (data, after) => {
// (A1) FORM DATA
let form = new FormData();
for (let [k, v] of Object.entries(data)) { form.append(k, v); }
// (A2) FETCH
fetch("3-ajax-cart.php", { method:"POST", body:form })
.then(res => res.json())
.then(res => {
if (res.status==1) { after(res); }
else { alert(res.msg); }
})
.catch(err => console.error(err));
},
// (B) HELPER - TOGGLE HTML SECTIONS
toggle : (target, show) => {
if (show) { target.className = "show"; }
else { target.className = ""; }
},
// (C) INITIALIZE
hCart : null, // html cart
hWCart : null, // html cart wrapper
hCCart : null, // html cart count
hCO : null, // html checkout wrapper
init : () => {
// (C1) GET HTML ELEMENTS
cart.hCart = document.getElementById("cart");
cart.hWCart = document.getElementById("wCart");
cart.hCCart = document.getElementById("cCart");
cart.hCO = document.getElementById("checkout");
// (C2) UPDATE CART COUNT
cart.ajax({ req : "count" }, res => cart.count(res.count));
},
// (D) UPDATE CART COUNT
count : qty => {
cart.hCCart.innerHTML = qty;
if (cart.hWCart.classList.contains("show")) { cart.show(); }
},
// (E) SHOW CART ITEMS
show : () => {
// (E1) RESET
cart.toggle(cart.hWCart, true);
cart.hCart.innerHTML = "";
// (E2) LOAD & GENERATE HTML
cart.ajax({ req : "get" }, items => {
// (E2-1) ITEMS IN CART
items = items.msg;
// (E2-2) CART IS EMPTY
if (items==null) {
cart.hCart.innerHTML = `<div class="cCell empty">Cart is empty</div>`;
}
// (E2-3) DRAW CART ITEMS
else {
let row, subtotal, total = 0;
for (let [id, item] of Object.entries(items)) {
// CALCULATE SUBTOTAL
subtotal = item["price"] * item["qty"];
total += subtotal;
// ITEM ROW
row = document.createElement("div");
row.className = "cCell";
row.innerHTML = `<input class="cQty" type="number" value="${item["qty"]}"
min="0" max="99" onchange="cart.change(${id}, this.value)">
<div class="cInfo">
<div class="cName">${item["name"]}</div>
<div class="cPrice">$${subtotal.toFixed(2)}</div>
</div>
<input class="cDel button" type="button" value="X" onclick="cart.remove(${id})">`;
cart.hCart.appendChild(row);
}
// CART TOTALS
row = document.createElement("div");
row.className = "cCell";
row.innerHTML = `<input class="cDel button" type="button" value="X" onclick="cart.empty()">
<div class="cInfo">
<div class="cName">Total</div>
<div class="cPrice">$${total.toFixed(2)}</div>
</div>
<input class="cDel button" type="button" value=">" onclick="cart.toggle(cart.hCO, true)">`;
cart.hCart.appendChild(row);
}
});
},
// (F) ADD ITEM TO CART
add : id => cart.ajax(
{ req : "set", id : id },
res => cart.count(res.count)
),
// (G) CHANGE ITEM QTY
change : (id, qty) => cart.ajax(
{ req : "set", id : id, qty : qty },
res => cart.count(res.count)
),
// (H) REMOVE ITEM
remove : id => cart.ajax(
{ req : "set", id : id, qty : 0 },
res => cart.count(res.count)
),
// (I) NUKE
empty : () => { if (confirm("Reset cart?")) {
cart.ajax(
{ req : "nuke" },
res => cart.count(res.count)
);
}},
// (J) CHECKOUT
checkout : () => {
cart.ajax({
req : "checkout",
name : document.getElementById("coName").value,
email : document.getElementById("coEmail").value
}, res => location.href = "4c-thank-you.html");
return false;
}
};
window.onload = cart.init;
Once again, not going to explain line by line. Here’s a quick summary.
- (A)
ajax()
A helper function to do a fetch call to3-ajax-cart.php
. - (B)
toggle()
Toggle show/hide for the HTML cart and checkout form. - (C)
init()
Runs on window load. Get the HTML elements and load the cart count. - (D To J) The shopping cart actions.
count()
Update the total cart quantity.show()
Show the cart items.add()
Add an item to the shopping cart.change()
Change item quantity.remove()
Remove an item from the cart.empty()
Empty the entire cart.checkout()
Checkout.
4C) THANK YOU PAGE
<h1>Thank You</h1>
<p>Order received.</p>
Lastly, just a dummy thank you page after successful checkout.
EXTRAS
That’s all for this project, and here is a small section on some extras that may be useful to you.
NOTES & IMPROVEMENT IDEAS
My developer senses are tingling, I can feel the trolls banging on their keyboards. Of course, this is only a tutorial and a barebones shopping cart. A lot needs to be done before it is a “complete shopping cart system”:
- If you don’t already have an existing user system – I will leave some links below.
- Complete your own checkout sequence – Collect more customer information, send an email, etc…
- Integrate your own payment gateway – Paypal, Stripe, Venmo, Payoneer Apple Pay, Google Pay, etc…
- Build your own admin panel to better manage things.
- Reports.
- For you guys who are thinking of “customize product” and “product options” – See this ERD diagram, and you will know how complex it is. I cannot answer that in a few lines, get my eBook instead. 😛
Yep, you are building your own system here – It is up to you to complete the rest. Good luck.
A WAY BETTER SHOPPING CART
A shopping cart with product options, categories, discount coupons, user system, admin panel, API, and an installable web app - Click here to check out my eBook!
LINKS & REFERENCES
- User Registration With PHP MYSQL – Code Boxx
- Simple User Login System With PHP MySQL – Code Boxx
- User Role Management In PHP MYSQL – Code Boxx
- Simple PHP Admin Panel – Code Boxx
- If you change your mind, there are plenty of “full shopping carts”.
THE END
Thank you for reading, and we have come to the end. I hope that it has helped you to better understand, and if you want to share anything with this guide, please feel free to comment below. Good luck and happy coding!
Hello!! finally a fully functional source code Im so grateful thank you so much ^_^
Im new to this field and i want to explore more Im really confused can I ask how can I change the database settings in lib/core.php? Where can i change it T^T
Eh… You can do that by learning basic HTML, CSS, JS, PHP, MYSQL, AMP servers, and how to use a code editor first.
Seriously, start from the basics, go through the basic tutorials one-by-one. If you are asking this kind of question, this tutorial is way beyond your current level. Good luck.
Hi
I want to buy the e-book but when i press purshase i get checkout page with this text: your cart is impty !!
Thanks for reporting. Disabled caching on the shop and checkout pages… It should be less funky now.
I followed all instruction step by step still not able to view the cart details page.
please help!
What is “not able to view the cart details”?
https://code-boxx.com/faq/#notwork
https://code-boxx.com/simple-javascript-debugging-troubleshooting/
* What if I want the product name and subtotal to be inserted into the db tho?
* Hey if I wanted to insert product name into the order item db, where do i need to alter?
* I’ve got a uncaught error: call to a member function getAll() on null when i used $_CC->Cart->getAll() in lib-cart.php
* Where do you have to use $_CC->Cart->getAll()? I received a fatal error that traced back to ajax-cart.php(100): Cart->checkout(‘John Doe’, ‘john@doe.com’)
A quick freebie. Add a product name and subtotal field to the order items table, and change the checkout function. Do consider getting the ebook if you are working on a commercial project – https://code-boxx.com/shop/shopping-cart-system/
Here are possible places to get free help with your project – https://code-boxx.com/websites-get-help-programming/
Otherwise… https://code-boxx.com/faq/#help
What should I do if I want to add products to the cart using their id instead of select from the list.
$_CC->Cart->add(ID, QTY);