I’m always looking new ways to monetise my craft website, It’s a Stitch Up. I sell PDF knitting patterns, distributed through knitting community site Ravelry, earn commission though Amazon Associates links and accept donations on free patterns using PayPal. I’m not looking to make my fortune, but enough to cover my site costs and pay for my hobby would be great. An online shop where I can sell physical products – both things I make and things that the crafters who make up my core audience might use – is a logical step.
I considered several ways to do this:
Selling through a specialist handmade site like Etsy or UK equivalent Floksy. I’ve had limited success with Etsy in the past, you have to work hard to stand out, and it’s impossible to be competitive on price with the US market. Folksy on the other hand has a fraction of the audience, and a silly name. It costs money to list items on these sites and it’s frustrating when they don’t sell before the listing expires.
By moving the selling to my own site, I reduce my costs, cut down the admin in maintaining a presence on a 3rd party and leverage my site’s excellent search rankings and visitor numbers. I know from experience selling PDF patterns that I get most of my sales through my own site, despite Ravelry’s 1.8 million registered users.
There are plenty of E-Commerce plugins available for WordPress, which powers the rest of the website. I tried out some free ones and trials but decided not to use a 3rd party plugin because:
- The front-end output was too restricted – I want complete control over the UI, I am a designer after all.
- It’s not my style, where is the fun in that?
Building an e-commerce engine is a big task on top of my day job so it’s starting small and growing in phases and making the most of what WordPress gives for free, designing and building new features as they become a priority. [Truly Agile, one might say]
Phase 1: The bare bones
Get a functioning shop online as quickly as possible.
- Product pages using a custom type (‘product’) and a custom taxonomy (‘product_type’) (see Experiments with WordPress custom post types)
- Basic product meta using my favourite plugin, Custom Field Template
- Dynamic navigation using the ‘product_type’ taxonomy with product counts
- A shop home page template (archive-product.php) and landing page template for the product categories (taxonomy-product_category.php), which for the moment simply includes archive-product.php
- Purchase using PayPal hosted buttons
- A link to the PayPal basket
- A plugin using the marvellous Flickr API and WordPress shortcode API to build an image gallery for each product
- Minimum visual design to make it not ugly
I got a functioning shop online very quickly, but after adding a few products it became apparent that PayPal hosted buttons are a massive PITA. They take ages to create, I had to maintain prices in two places, I had no flexibility over shipping and the PayPal shopping basket is ugly and cannot be integrated into the website. A bit more development work would soon pay for it self in time saved by not using PayPal buttons.
Phase 2: Shopping basket
Integrating a pretty, user friendly shopping basket into the site then passing complete the basket to PayPal, to their well optimised and trustworthy checkout process. This is essential to many things I’d like the to do in the future, like offering different shipping options.
Creating a WordPress page that’s transactional rather than content is straight-forward. Create a new page template, e.g. ‘page-basket.php’, adding this code to the top of the file (it wont work without the closing
<?php /* Template Name: Shop basket */ ?><?php // rest of code ?>
Then adding a new page in the WP admin site and choose the template name for your new template.
Before I could add things to my basket, I needed a database table to put them in, with four columns; the WP ID of the product content item, a unique user identifier – I’m using session ID for the moment, quantity – how many are in the basket, and time stamp – useful for sorting and housekeeping.
I’ve used simple GET parameters to give instructions to the basket script – it’s nice and simple to debug because I can call actions just by typing a URL – but it re-directs back to itself with out the query string after successful completion of the action, avoiding those pesky page refresh problems [don’t you just hate that form re-submission warning?].
There are only four actions:
Add – If the product is in stock, insert a line into the basket table, or fetch the basket URL, calling ‘update’ if it’s already in the basket.
Update – Change the quantity of the item in the users basket if there is sufficient stock for the new quantity, or fetch the basket URL delete if the new quantity is zero.
Delete – Delete the product from basket for the current user.
Empty – Delete all products from the basket for the current user.
The WordPress $wpdb class offers some useful methods for retrieving data and working directing with the database.
$wpdb->query($query) will execute any query, while
$wpdb->get_results($query) returns nicely packaged results and
$wpdb->get_var($query) returns a value as a single variable.
To retrieve a custom field value for a post – e.g. the stock level for a product – there is no need to write a query; I can use
get_post_meta($prodid, 'stock_level', true).
Retrieving the contents of the user’s basket requires joining the WordPress
postmeta tables, and my basket table.
If my shopping basket were part of a plugin, and it were an archive template, I might choose to set a custom query for the archive using filters;
But, it’s a page, and I want to show the actual page content as well as the basket, so I wrote a custom select query and used
setup_postdata($post) to turn each returned row into a post object inside the loop.
$query = "SELECT $wpdb->posts.*, basket_quantity FROM $wpdb->posts, $wpdb->postmeta, $wpdb->prefix".'basket'." WHERE ID = post_id AND ID = basket_prod_id AND basket_session = '$session' AND post_status = 'publish' AND meta_key = 'stock_level' AND meta_value > 0 ORDER BY basket_timestamp"; $products = $wpdb->get_results($querystr, OBJECT); if ($products): global $post; foreach ($pageposts as $post): ?> setup_postdata($post); [...] endforeach; endif;
As well as displaying the basket contents on the page, I need to put them in a form to send to PayPal using the cart upload command (reference).
My form looks like this:
<form action="https://www.paypal.com/cgi-bin/webscr" method="post"> <input type="hidden" name="cmd" value="_cart" /> <input type="hidden" name="upload" value="1" /> <input type="hidden" name="business" value="[merchant id]" /> <input type="hidden" name="upload" value="1" /> <input type="hidden" name="currency_code" value="GBP" /> <? // start WP loop ?> <input type="hidden" name="item_name_<? echo $counter; ?>" value="<? echo $post->post_title ?>" /> <input type="hidden" name="quantity_<? echo $counter; ?>" value="<? echo $quantity ?>" /> <input type="hidden" name="amount_<? echo $counter; ?>" value="<? echo $price ?>" /> <input type="hidden" name="item_number_<? echo $counter; ?>" value="<? $post->ID ?>" /> <input type="hidden" name="shipping_<? echo $counter; ?>" value="0" /> <? // end WP loop ?> <!-- require a shipping address --> <input type="hidden" name="no_shipping" value="2" /> <!-- set flat rate shipping by setting shipping value of first item --> <input type="hidden" name="shipping_1" value="<? echo $shipping ?>" /> <input type="submit" value="Go to checkout" /> </form>
Et voilà! A vastly superior shopping experience to the PayPal basket.
Of course I also added functionality to allow the user to edit their basket contents, and display messages explaining when things go wrong.
Phase 3 and beyond
These things are on my wish list:
- A choice of shipping options
- Stock control
- User accounts and order tracking
But before deciding which features to add next, I’ve let it out into the wild to see how it does.
Feedback and ideas welcome!