Blog

I put off writing music by writing…a shopping cart

One of the things I really enjoy about being a self-publisher composer is that there’s a variety of tasks to work on (read: get distracted by) when I’m not composing. This is a story about one of those tasks.

You may know that I’m addition to my work as a composition instructor at NDSU and my work as a composer, one of my side gigs is web development—I write software (Liszt) for running schools of music. So of course, one of my composing tasks is maintaining a website where people can buy my music. And to make that happen, you kind of need some sort of shopping cart.

For the past several years, I’ve used a great shopping cart platform called Snipcart to handle the shopping part of my websites, both at KyleVanderburg.com and at NoteForge. It’s kind of a drop-in solution: include some code, and as long as your “add to cart” buttons are coded right, they take care of everything. It’s $10 a month, and they package up all the purchasing information and send it off to Stripe, my payment processor.

Recently though, Snipcart has been unable to charge my card for the monthly subscription. When I’ve pressed for diagnostic information, they’ve pointed me to Stripe, and I figure if I’m going to have to deal with Stripe anyway, why not save $10 a month and build my own shopping cart?

The past few days have involved just that. Here’s how it has worked.

First off, I started off with some things that helped out already:

  • My sites are built on Liszt, which means I have a lot of control over the databases and how the site can communicate with the database.
  • I already have a database with info about prices, shipping weight, product images, etc. I’m not tracking stocking info because most of my work is print-on-demand at this point.
  • I have a pre-existing relationship/development account with Stripe due to building a payment gateway for invoicing several years ago.
  • I don’t want to use the pre-existing payment gateway I’ve built because…we’ll, I just don’t. It would make things complicated (collecting name and address information for example). We’ll just let Stripe handle it.
  • My websites already have a built-in off-canvas “drawer” component that I can use (on the NoteForge site, it comes up when you click the perusal score button).
  • I want to take advantage of Stripe’s checkout feature, so the only thing I need to worry about is the cart functionality.

So with those constraints, the first thing I need are a couple of databases, one for the “cart” and one for “cart items.” Cart doesn’t need much more than some sort of identifier, while cart items need fields for cart id, item, and quantity. (It occurs to me on this write-up that there might be a way to build this without a Cart database, but too late now).

Next up, we need a cart page on the website to handle all the cart functions. I decided to program it in PHP because I’m faster at that than writing it with JavaScript. The first time the cart page (let’s call it cart.php) is loaded, it creates a Cart database record, gets a Globally Unique Identifier (GUID) and writes that GUID as a cookie to the user’s browser. (I considered using HTML local storage or PHP sessions, and cookies seemed like the easiest.) Liszt just generated GUIDs for every table row anyway so that’s easy.

In pseudocode: (Note: none of this is actually production code, but it’s close enough to explain it)

<?php
//Generate Cart ID if not set.
if(empty($_COOKIE['NoteForgeCart'])){
	$cart->build(); //This will create the record and the GUID.
	setcookie("NoteForgeCart",$cart->guid,time()+60*60*24*30,"/"); /* expires in 30 days */
	$cartid=$cart->guid;
}else{$cartid = $_COOKIE['NoteForgeCart'];}
?>

I opted to use url constructions like cart.php?addItem=myAwesomeScore to manipulate the cart. This requires that every change to the cart cart involves a page reload, but the code is lightweight enough to where I’m not worried about performance. I could have written this in JavaScript and done some sort of Ajax call but…this was faster. Oh and of course instead of myAwesomeScore, I’m using the GUID of the product we’re adding.

<?php
//When we're adding items
if(isset($_GET['addNewItem'])){
	$row = $product->getByGUID($_GET['addNewItem']);
	if(!empty($row)){
		//Add to Cart
		$cart_items->build();
		$update['cart']=$cartid;
		$update['item']=$_GET['addNewItem'];
		$update['qty']="1";
		$cart_items->update($update);
	}else{
		//Invalid Product ID
		echo "Invalid Product";
	}
}
?>

From a user interface level, this means that product links can just be to cart.php?addNewItem=guid. Some css styling to load that page in an iFrame in the drawer I mentioned, and it’s an easy implementation.

Retrieving cart contents is easy since we can just do a database query for all the rows in the Cart Items database with a certain cart I’d. That code goes on Cart.php last.

<?php
foreach($cart_items->getByCart($cartid) as $cartitem){
	$item=$product->getByGUID($cartitem['item']);
	/* display cart items here, prettily */
}
?>

Deleting a cart item is easy to work out: a button which loads cart.php?removeItem=item reloads the cart and removes that row from the database.

<?php
//When we're deleting items
if(isset($_GET['removeItem'])){
	$cart_items->getByGUID($_GET['removeItem']);
	$cart_items->delete();
}
?>

Adding an item to the cart that’s already in the cart proves a challenge. When an item is added to the cart, the cart contents needs to be loaded to see if that item is already on the cart, and if so, to increase the quantity by one. This requires some additions to the addNewItem method.

<?php
$contents = $cart_items->getCartContents($cartid);
if(in_array($_GET['addNewItem'],$contents)){
	//Item In Cart, Update Quantity
	$cart_items->getSingleItem($cartid,$_GET['addNewItem']);
	$update['qty']=$cart_items->row['qty']+1;
	$cart_items->update($update);
}
?>

Changing quantities poses the next problem. A simple way would be to include a text field for quantity, and then add a handler for when it changes, to make a database update. That was a little more complicated than I wanted it to be. I considered + and – buttons, but if the page reloaded every time, it would be obnoxious for large quantities. I considered +1, +10, and +100 buttons, but that seemed similarly awkward. I opted for + and – buttons that ask the user how much to add or remove from the cart.

The next challenge is the actual checkout process. We need an intermediate page between cart.php and Stripe to format the data—something like cart-process.php. This will package the cart in a format that Stripe understands and pass it off to Stripe. Since the cart ID is just in a cookie, we can use that. This takes a bit of time to figure out the nested arrays, but the Stripe documentation (and the Stripe errors in the Apache logs) are well-written.

Once you can get Stripe to catch the data, you’re home free.

There’s a lot of things I haven’t sorted out in this quick and dirty process: shipping prices, whether products need to be shipped, taxes (though I think Stripe is doing that for me)(I figured that out since this write-up), digital assets, and so on. Snipcart used to automatically send out download links for digital goods, and I think I’ll just have to not have that for a bit.

I’ve been using a GitHub project to track everything, here’s what that looks like:

GitHub Project View

There’s some room for improvement, but it’s not bad for several hours of worth over the weekend to save $120 a year by writing 200 lines of code.

This code will (hopefully) go live later this week.

Decorative element
Kyle Vanderburg