Full E-Commerce Integration of Snipcart with WordPress

Integrating an HTML/JS-based shopping cart platform directly within WordPress's CMS admin.

Even though an exponential amount of online tools keep popping up, WordPress remains a domineering web behemoth. More than 25% of active sites run on the famous CMS. Quite a chunk of the whole world wide web.

On the other hand, with the rise of vertical, third party solutions, we see developers adopting more and more a modular approach in their workflow.

Many front-end developers I've met and talked to enjoy working with a lean, quick e-commerce solution like Snipcart. But, they also want to give more autonomy in familiar CMS to their merchant clients. Since many of them use WordPress, I thought I'd write this article to provide them with a useful resource.

This is a full technical tutorial on how to completely integrate Snipcart's shopping cart platform with WordPress.

Contents of the post:

  • How to create and manage Snipcart products & inventory directly within WordPress' CMS
  • How to display Snipcart dashboard data directly within WordPress' CMS
  • How to manage Snipcart orders directly within WordPress' CMS
  • All relevant code snippets to clarify development steps
  • A complete associated repo for code demo
  • A live website demo

Preconditions

For this post, we assume the following:

  • You have a functional installation of WordPress 4.5.x
  • You're comfortable with PHP and WordPress development
  • You have a GitHub account to access the entirety of the demo code
  • You have a PHP version compiled with Curl

Step 1 - Install the required plugins

The two main plugins we'll need for this integration are Custom Post Type UI & Advanced Custom Fields.

We'll use the former to create—you guessed it—custom post types and the latter to speed up custom field creation.

So go ahead and install both plugins.

Depending on your needs, you might want to do this step manually, with code instead of plugins

Start by creating a custom post type "product" using cpt-ui

create-product-type

Now add custom fields: Custom fields -> Add new field group -> Add the following:

create-product-fields

  • Id (text)
  • Price (number)
  • Inventory (number) (You can put an inventory value if you want)

Make sure to set Show this field group if Post Type is equal to product:

set-custom-fields-location

Publish it and voilà, you can now add a few products!

Step 2 - Show products in the theme

It's time to create a theme template to display products on our site. For this demo, we'll use a child theme of Twenty Sixteen. You can find more details about child themes in the WordPress doc.

First, create a folder with your theme name in wp-content/themes. Then, add a file style.css to declare our theme as a child of Twenty Sixteen.

/*
 Theme Name:   Snipcart Theme
 Description:  Snipcart custom theme
 Author:       Yanick Ouellet
 Author URI:   http://snipcart.com
 Template:     twentysixteen
 Version:      1.0.0
*/

We also need to enqueue the Twenty Sixteen stylesheet. Let's do it in a new function.php file:

add_action( 'wp_enqueue_scripts', 'snipcart_enqueue_styles' );
function snipcart_enqueue_styles() {
    wp_enqueue_style( 'parent-style', get_template_directory_uri() . '/style.css' );
}

Have a look at your site. Nothing changed? Good! Now, we'll create a custom template for product display: single-product.php. We'll use single.php from Twenty Sixteen and customize it. Mainly, we need to add price and inventory information:

<div>
    price: <strong><?php echo get_post_meta($id, 'price')[0]; ?>$</strong> <br />
    inventory: <strong><?php echo get_post_meta($id, 'inventory')[0]; ?> left</strong>
</div>

To retrieve our customs fields, we use get_post_meta($id, 'field_name')[0]. The Advanced Custom Fields plugin stores it as an array, and the value is at index 0.

See the complete code for this step here: Step 2.2 - Customize product detail page

Go ahead and refresh any product page. You should now see inventory and price displayed.

What comes next is listing all of our products. Create a custom template to do that: product-list.php. Once again, we will customize a Twenty Sixteen template.

Here's the full code for this step: Step 2.3 - Add product list template

A few things to note here:

  • Don't forget to declare it as a template: /* Template Name: Product list */
  • Query all products query_posts(array('post_type' => 'product'));
  • For the demo, we won't bother with pagination

Now, create a page using this template:

create-product-list-page

And set it as your home page (Appearance -> Customize -> Static Front Page):

use-product-list-as-home

Step 3 - Integrate Snipcart's shopping cart

First, we need to load the Snipcart style. We will enqueue it just after the Twenty Sixteen style:

function snipcart_enqueue_styles() {
    wp_enqueue_style( 'parent-style', get_template_directory_uri() . '/style.css' );
    wp_enqueue_style( 'snipcart-style', 'https://cdn.snipcart.com/themes/2.0/base/snipcart.min.css');
}

We also need to load Snipcart's scripts. Since we need to add a data-api-key attribute to the src tag, we can't use wp_enqueue_script. Instead, we will copy the Twenty Sixteen footer.php and add <script src="https://cdn.snipcart.com/scripts/2.0/snipcart.js" data-api-key="YOU-PUBLIC_KEY" id="snipcart"></script> just before the closing body tag.

See the complete code for this step here: Step 3.1 - Load Snipcart script and stylesheet

Next, let's add the buy button to product detail:

<button
    class="snipcart-add-item"
    data-item-id="<?php echo get_post_meta($id, 'id')[0]; ?>"
    data-item-name="<?php echo get_the_title(); ?>"
    data-item-price="<?php echo get_post_meta($id, 'price')[0]; ?>"
    data-item-max-quantity="<?php echo get_post_meta($id, 'inventory')[0]; ?>"
    data-item-url="<?php echo get_the_permalink(); ?>"
    data-item-image="<?php echo wp_get_attachment_image_src(get_post_thumbnail_id($id), 'single-post-thumbnail')[0]?>"
>
    Buy
</button>

Full code for this step: Step 3.2 - Add Snipcart buy button

Step 4 - Update inventory with webhooks

For easier e-commerce operations management, let's decrease products inventory using Snipcart webhooks. To do so, we will register a webhook in Snipcart's merchant dashboard to call our WordPress site when an order is completed.

We will use admin-ajax.php to handle webhooks.

Go ahead and register a webhook to http://yoursite.com/wp-admin/admin-ajax.php?action=snipcart_endpoint

snipcart-register-webhook

Note that, as seen in the screenshot, I use ngrok to develop with Snipcart locally and allow it to access my website over the web.

Now, in functions.php, we need to add an endpoint to admin-ajax.php?action=snicart_endpoint:

add_action('wp_ajax_nopriv_snipcart_endpoint', 'snipcart_endpoint');
function snipcart_endpoint() {
    $json = file_get_contents('php://input');
    $body = json_decode($json, true);

    if (is_null($body) or !isset($body['eventName'])) {
        header('HTTP/1.1 400 Bad Request');
        wp_die();
    }

    switch ($body['eventName']) {
    case 'order.completed':
        foreach($body['content']['items'] as $item) {
            handle_item($item);
        }
        break;
    }

    wp_die();
}

For each item in a completed order, we call a handle_item to process it and decrease inventory for that item.

So let's define handle_item:

function handle_item($item) {
    global $wpdb;

    $id = $wpdb->get_var( $wpdb->prepare( 
        "
        SELECT post_id
        FROM $wpdb->postmeta
        WHERE meta_value = %s AND meta_key = 'id'
    ", $item['id']
    ) );

    $qte = get_post_meta($id, 'inventory')[0];
    update_post_meta($id, 'inventory', $qte - $item['quantity']);
}

With the Snipcart product ID (a product custom field), we can query the WordPress post ID of the product. Then, we get the current item inventory and decrease it by the quantity registered in the order.

Now, if you buy a product, it should decrease the quantity!

See full code for this step here: Step 4.1 - Decrease inventory on Snipcart webhook

But we've got ourselves a little problem here: everybody can call the endpoint URL and trigger the quantity decrease.

So we will validate that the caller is Snipcart with a handshake, as described in the doc section here.

Before doing that, we will define a generic function to call the Snipcart API:

function call_snipcart_api($url, $method = "GET", $post_data = null) {
    $url = 'https://app.snipcart.com/api' . $url;

    $query = curl_init();

    $headers = array();
    $headers[] = 'Content-type: application/json';
    if ($post_data)
        $headers[] = 'Content-Length: ' . strlen($post_data);
    $headers[] = 'Accept: application/json';

    $secret = file_get_contents(get_stylesheet_directory() . "/secret.txt");
    $secret = str_replace("\n", "", $secret);
    $secret = str_replace("\r", "", $secret);
    $headers[] = 'Authorization: Basic '.base64_encode($secret . ":");
    $options = array(
        CURLOPT_RETURNTRANSFER => 1,
        CURLOPT_URL => $url,
        CURLOPT_HTTPHEADER => $headers,
        CURLOPT_SSL_VERIFYHOST => 0,
        CURLOPT_SSL_VERIFYPEER => 0
    );

    if ($post_data) {
        $options[CURLOPT_CUSTOMREQUEST] = $method;
        $options[CURLOPT_POSTFIELDS] = $post_data;
    }

    curl_setopt_array($query, $options);
    $resp = curl_exec($query);
    curl_close($query);

    return json_decode($resp);
}

Few things to note here:

  • The function can handle GET, POST and PUT calls.
  • We need to set Authorization: Basic SNIPCART_SECRET_KEY: header to allow Snipcart to recognize us. Since we do not want the secret key to be versioned, we put it in a secret.txt (gitignored) file at the root of the theme.
  • This function use php_curl, so your PHP version needs to be compiled with Curl.

Now, we need to verify that Snipcart really sent a webhook before processing it. At the beginning of snipcart_endpoint, add:

$token = $_SERVER["HTTP_X_SNIPCART_REQUESTTOKEN"];
$resp = call_snipcart_api('/requestvalidation/' . $token);

if (strpos($resp->resource, 'wp-admin/admin-ajax.php?action=snipcart_endpoint') === false) {
   echo "Caller is not snipcart";
   wp_die();
}

Only webhooks from Snipcart will now be handled!

See full code for this step: Step 4.2 - Handle webhooks only from Snipcart

Step 5 - Add Snipcart data to WordPress admin

Next up on the menu: we add a Snipcart page directly in WordPress' CMS. :)

Start by registering a menu page in the admin:

add_action( 'admin_menu', 'register_custom_menu_page' );
function register_custom_menu_page() {
    add_menu_page('snipcart', 'Snipcart', 'manage_options', 'snipcart', 'snipcart_dashboard', '', 6);
}

And let's call Snipcart /api/orders endpoint and display orders in our new admin page:

function snipcart_dashboard() {
    $resp = call_snipcart_api('/orders');
    $statuses = array("Processed", "Disputed", "Shipped", "Delivered", "Pending", "Cancelled");

    echo "<table class='snip-table'>";

    echo "<tr>
            <th>Invoice number</th>
            <th>Payment method</th>
            <th>Email</th>
            <th>Total</th>
            <th>Date</th>
            <th>Order status</th>
            <th>Update status</th>
            <th>Items</th>
          </tr>";

    foreach ($resp->items as $order) {
        echo "<tr>";
        echo "<td>";
        echo "<a target='_blank' href='https://app.snipcart.com/dashboard/orders/$order->token'>";
        echo $order->invoiceNumber. "</a></td>";
        echo "<td>" . $order->paymentMethod. "</td>";
        echo "<td>" . $order->email . "</td>";
        echo "<td>" . $order->finalGrandTotal. "$</td>";
        $date = new DateTime($order->creationDate);
        $outputDate = date_format($date, 'Y-m-d H:i');
        echo "<td>" . $outputDate. "</td>";
        echo "<td>" . $order->status. "</td>";
        echo "<td><select class='order-status-select' data-token='$order->token'>";

        foreach ($statuses as $status) {
            echo "<option value='$status' ";
            if ($status == $order->status) echo "selected='selected'";
            echo ">$status</option>";
        }

        echo "</select>";

        echo "<td>";
        foreach ($order->items as $item) {
            echo $item->name . "<br/>";
        }

        echo "</tr>";
    }

    echo "</table>";

    echo "<script src='". get_stylesheet_directory_uri() . '/js/admin.js' . "' />";
}

We used the call_snipcart_api function defined earlier to get all orders and display them in a table. To keep things simple, we used echo to render the table, but on a real website, you could make it cleaner!

We iterate over the order list returned by Snipcart, which is converted from JSON to PHP objects in call_snipcart_api. You can view all available fields in the API doc.

Note that we include a JavaScript file at the end of the page. We will come back on that later.

If you go to the Snipcart page in the admin, you should now have the order list!

See full code for this step: Step 5.1 - Display order list in Wordpress admin

Let's style our data a bit, shall we?

Add the following css file : css/admin.css

.snip-table td, .snip-table th{
    padding: 10px;
    background-color: white;
}

Then, enqueue it:

add_action( 'admin_enqueue_scripts', 'snipcart_enqueue_admin_script' );
function snipcart_enqueue_admin_script( $hook ) {
    wp_register_style('snipcart_admin_style',
                        get_stylesheet_directory_uri() . '/css/admin.css', false, '1.0.0');
    wp_enqueue_style('snipcart_admin_style');
}

Full code for this step: Step 5.2 - Add custom css to style order list

order-list

Notice the order status dropdown? We will add some JS to detect the change and call an admin-ajax.php action which will call Snipcart's API to update the status.

First, add the admin-ajax.php action:

add_action('wp_ajax_snipcart_update_status', 'snipcart_update_status');
function snipcart_update_status() {
    if (!isset($_POST['token']) || !isset($_POST['value'])) {
        header('HTTP/1.1 400 Bad Request');
        echo "Bad request";
        wp_die();
    }

    $url = "/orders/" . $_POST['token'];
    $result = call_snipcart_api($url, "PUT", json_encode(array(
        "status" => $_POST['value']
    )));

    if ($result->status !== $_POST['value']) {
        header('HTTP/1.1 500 Internal Server Error');
        echo "Error while communicating with Snipcart";
    }

    wp_die();
}

Provided we receive the order token and new status value by post, this action will call Snipcart's API via PUT and update the order. More on the order endpoint here.

Note: we used orders for this exemple, but there's a bunch of other things you can do with the API! :)

Now, add the JavaScript part: js/admin.js:

(function($){
    $(document).ready(function() {
        $('.order-status-select').change(function(elem) {
            var select = $(elem.target);
            $.post('/wp-admin/admin-ajax.php', {
                action: 'snipcart_update_status',
                token: select.data('token'),
                value: select.val()
            }, function() {
                alert('Status updated!');
                location.reload();
            })
                .fail(function(data) {
                    alert('Error: ' + data.responseText);
                });
        });
    });
}(jQuery));

This way, we simply listen for changes on the dropdown and call, via ajax, the action defined earlier. On success we display an alert and reload the page. Of course, it should be handled more gracefully in a real website.

See full code for this step: Step 5.3 - Update order status via WordPress admin

Step 6 - Quick edit of inventory

Now, we will add a way to quickly edit the inventory of a product within the WP product list. For more info here, see the Wordpress doc

First, add an inventory custom columns to the product list in the admin:

add_filter( 'manage_product_posts_columns', 'manage_product_posts_columns' );
function manage_product_posts_columns( $columns ) {
    $columns['inventory'] = esc_html__( 'Inventory');

    return $columns;
}
add_action( 'manage_posts_custom_column' , 'display_product_inventory', 10, 2 );
function display_product_inventory( $column, $post_id ) {
    if ($column == 'inventory'){
        echo '<span>', get_post_meta($post_id, 'inventory')[0], '</span>';
    }
}

Then, add a field in the quickedit box:

add_filter( 'quick_edit_custom_box', 'snipcart_quick_edit_box');
function snipcart_quick_edit_box($column_name) {
    if ($column_name !== 'inventory') return;

    ?>
    <fieldset class="inline-edit-col-right inline-edit-book">
                  <div class="inline-edit-col column-<?php echo $column_name; ?>">
                  <label class="inline-edit-group">
                  <span class="title">Inventory</span><input name="inventory" />
                  </label>
                  </div>
    </fieldset>
}

We need to save the value when the post is saved, so:

add_action('save_post', 'save_inventory');
function save_inventory($id) {
    if ("product" !== $_POST['post_type']) return;

    if (isset($_POST['inventory'])) {
        update_post_meta($id, 'inventory', $_POST['inventory']);
    }
}

Finally, we need to add some JS to populate the quickedit box. Register the JS in snipcart_enqueue_admin_script:

if ( 'edit.php' === $hook && isset( $_GET['post_type'] ) && 'product' === $_GET['post_type'] ) {

        wp_enqueue_script( 'snipcart_quick_edit',
                            get_stylesheet_directory_uri() . '/js/quickedit.js',
                            false, null, true );
    }

Create the js/quickedit.js file:

(function($) {
  var $wp_inline_edit = inlineEditPost.edit;

  inlineEditPost.edit = function(id) {
    $wp_inline_edit.apply(this, arguments);
    var $post_id = 0;
    if (typeof(id) == 'object') {
          $post_id = parseInt(this.getId(id));
    }

    if ($post_id > 0) {
      var $edit_row = $('#edit-' + $post_id);
      var $post_row = $('#post-' + $post_id);

      var $quantity = $('.column-inventory', $post_row).text();

      $(':input[name="inventory"]', $edit_row).val($quantity);
        }
  };
})(jQuery);

Now, you can quick edit the inventory directly in your product listing!

See full code for this step here: Step 6 - Add quickedit box

Check out the live demo & full GH repo!

Snipcart + WordPress live demo store

Product listing, buy buttons, inventory decrease, cart summary... it's all there, all live. Buy a few dummy t-shirts and have a look for yourself!

snipcart/snipcart-wordpress-demo on GitHub

I shared a public repo in case you want to analyze or copy the code for this integration! Enjoy. :)

Also, I've recently stumbled on a pretty awesome hosting solution for WordPress sites: SpudPress. They offer blazing fast, cheap & secure static hosting for WP sites. So make sure you check 'em out!

Conclusion

If you made it this far: congrats! I'm aware this full integration can seem like a lot of work. But if you get comfortable with WP & Snipcart, you should be able to pull it off in a day of focused coding.

Following these steps will result in a solid e-commerce set up. You'll end up with a deep shopping cart integration that provides more autonomy to your merchants.

Of course, I'd love to know your thoughts about the whole thing. So if you have questions, suggestions, feedback: hit the comments! And don't hesitate to share the post on Twitter if you found it valuable. :)

Yanick Ouellet

Full stack developer at Snipcart, a simple e-commerce solution for developers. Also a command line lover and a math buff.