PayPal options and ExpressionEngine’s Simple Commerce Module

By John Faulds

Recently I had to add basic shopping cart functionality to a site that had been built with ExpressionEngine (my CMS of choice). “No problem,” I thought; I can use the Simple Commerce Module (SCM), which as the names suggests, is ideally suited to simple ecommerce requirements, and which I had used before on other EE sites.

But it turns out the requirements for this ‘store’ were a little more complicated and not something that could be done by the SCM ‘out of the box’. The store was selling prints of photos with each photo available in three different sizes. Searching on the EE forums revealed that being able to have different options or variations (size, colour etc.) for products was something that a few people had been trying to achieve. The SCM integrates with PayPal standard website payments and passing options to PayPal is something that can be done but it requires some custom coding to do so.

My problem was that I not only had different sizes, but each size was actually a different price which isn't always the case for different options (for example T-shirts in small, medium or large aren't always different prices, and different colours certainly wouldn't involve a price change).

The way the SCM is set up, you create an entry in a weblog or section, and then you select that entry for adding to the shopping cart and you set a price for it. So in order to have three variations of size at different prices with the standard functionality offered by the SCM, I was faced with having to create three nearly identical products with the same name and same image (which would also require uploading the same image three times), with the only difference being the size.

From the website's owner point of view this wasn't an ideal solution as they had to keeping replicating entries; and from the site visitor's point of view it wasn't that great either because they'd be presented with three products that looked nearly identical and would have to look more closely to work out what the actual difference was (at which point they'd probably ask: "Why didn't they just group them all together like other stores do?")

Related entries to the rescue

The answer to the problem turned out to be with related entries (beefed up with the Multi Relationship extension by Mark Huot) and a bit of custom coding of the form that gets sent to PayPal.

First of all I needed to create a store section (I'll call them sections from here because I never refer to them as 'weblogs') for which I then created custom fields for the product's description, a file upload field (also courtesy of a Mark Huot extension) for the product image, and one for the size (I'll come back to this one).

I then created another section for the sizes which also had custom fields, this time with just one field for the price (the name of the size being held in the entry's title field). I then created three entries for this section with the entries for the small, medium and large sizes with their associated prices. Coming back to the custom fields for the store section, I made the field type for the sizes Multi Relationship which is added to the list of options after installing the Multi Relationship extension. So then when I created the entries for the store, I was able to select all three sizes (or I could have only chosen some of them for particular products if I wanted to). The image below shows what the publishing form for a store entry with the related entries field looks like.

Screenshot of the publishing form for a store entry in the ExpressionEngine control panel

Having created the products and assigned sizes and images to each of them, it was then time to add them to the store. To do this you need to first install the SCM, navigate to the extension's home page (I like to create a new tab for it so it's easy to get to) and then select Add Item. This takes you to a table of entries that looks like what you see when you choose the Edit tab, except that here after you've checked the checkboxes of the items you want to add, when you hit the submit button, you're adding the items to the store.

The next window you're presented with is where you fill in the price and sale price. You actually have to enter both or the entry won't be created correctly, but you only use the sale price if you check the box that says Use Sale Price? I enter the same price for both fields but really I'm only entering a price here for the item to be submitted to the store correctly. The prices that get displayed in the template and sent to PayPal don't come from the SCM but from the sizes section. I actually think using a related entries section for prices in this way can make things easier on the store owner when it comes to updating prices, because if you have a large number of products all with the same price and you want to increase the price, using the SCM way, you'd have to select all the products you wanted to edit and manually change the price for each one. Using a related entry, you just edit the entry in the options/prices section once and it'll automatically get applied to all products that are related to that option.

Creating the template

The first thing we need to do is pull in all the entries from the ‘store’ section and for that we’ll use an ordinary weblog entries tag (s_desc is the field name I’ve given to the custom field for the product description):

{exp:weblog:entries weblog="store" disable="member_data|trackbacks"}
  <h2>{title}</h2>
  <p>{s_desc}</p>

At its most basic, the tag used for creating a SCM entry in a template looks like this:

{exp:simple_commerce:purchase entry_id="{entry_id}" success="site/success" cancel="site/index"}
{title}
<p><strong>Price: {item_sale_price}</strong></p>
<p><a href="{buy_now_url}" onclick="window.open(this.href);return false;">Buy Now</a></p>
<p><a href="{add_to_cart_url}" onclick="window.open(this.href);return false;">Add to Cart</a></p>
<p><a href="{view_cart_url}" onclick="window.open(this.href);return false;">View Cart</a></p>
{/exp:simple_commerce:purchase}

This will automatically create links with query strings that include all the parameters that need to be passed to PayPal. But when creating different options, we’ll need to create our own form and not leave it up to the module to generate it for us. We’ll still use the simple_commerce tag pair, but we’ll code the form tags ourselves. First we need a form tag with an action that points to PayPal (note that this URL is for Australian PayPal accounts; for US accounts leave the /au/ out):

<form action="https://www.paypal.com/au/cgi-bin/webscr" method="post" target="paypal">

Then we need a series of hidden inputs for passing the relevant information to PayPal (you can find out more about the different variables at the PayPal Developer Center):

<input type="hidden" name="cmd" value="_xclick"/>
<input type="hidden" value="http://www.yoursite.com/?ACT=20" name="notify_url"/>
<input type="hidden" value="http://www.yoursite.com/store/success" name="return"/>
<input type="hidden" value="http://www.yoursite.com/store/" name="cancel_return"/>
<input type="hidden" name="business" value="[email protected]"/>
<input type="hidden" name="currency_code" value="AUD"/> <!-- Change this to whatever your currency is -->
<input type="hidden" name="item_name" value="{title}"/>
<input type="hidden" name="item_number" value="{item_id}"/>
<input type="hidden" name="add" value="1"/>
<input type="hidden" name="upload" value="1"/>
<input type="hidden" name="no_shipping" value="2"/>
<input type="hidden" name="no_note" value="1"/>
<input type="hidden" name="undefined_quantity" value="1"/>

Then we need to create a dropdown which enables shoppers to choose which option they want and this is where the related entries tag comes in:

<label for="amount">Size:</label>
<select name="amount">
  <option value="">Please choose</option>
  {related_entries id="s_size"}
  <option value="{size_price}">{title} ${size_price}</option>
  {/related_entries}
</select>

The id in the related_entries tag pair is the name of the field that is pulling in the related entries and instead of using the item_sale_price as the price for the item, we’ll take the price from the related entry – size_price (the name I’ve used in my custom field; you can call it whatever you like).

And then finally, a Buy Now button:

<input type="submit" name="buy_now" id="buy_now_{entry_id}" value="Buy Now">

But there’s one thing missing from the variables being passed to PayPal and that’s the actual name of the size being chosen. Because the select dropdown is being used to manipulate the price, the name of the size isn’t actually being passed. So I’ve used a bit of javascript to populate another hidden field when an option in the select is chosen. So instead, the select opening tag will now look like:

<select name="amount" onchange="setSize(this, this.form)">

That’s also the reason why there is a ‘Please choose’ option so that the shopper has to change to an option with a price before proceeding. The name of the option is passed to PayPal with:

<input type="hidden" name="on0" value="Size">

and the associated javascript:

<script type="text/javascript">
<!--
function setSize(selObj, formObj) {
for (var j = 0; j < selObj.length; j++)
    if (selObj.options[j].selected && j != 0) {
        {related_entries id="s_size"}
            if (selObj.options[j].value == '{size_price}') { formObj.elements['os0'].value = '{title}'; }
        {/related_entries}
    }
}
//-->
</script>

automatically fills in this hidden field:

<input type="hidden" name="os0">

The related entries tag is being used here too to dynamically fill the options which means that the site owner can edit their options and prices without the template needing to be edited again.

Putting it all together

The whole template should now look like this:

<ul class="store">
{exp:weblog:entries weblog="store" disable="categories|member_data|pagination|trackbacks" sort="asc"}
  {exp:simple_commerce:purchase entry_id="{entry_id}" success="store/success" cancel="store/index"}
  <li>
    <h2>{title}</h2>
    <p>{s_desc}</p>
      <form action="https://www.paypal.com/au/cgi-bin/webscr" method="post" target="paypal">
        <input type="hidden" name="cmd" value="_xclick"/>
        <input type="hidden" value="http://www.yoursite.com/?ACT=20" name="notify_url"/>
        <input type="hidden" value="http://www.yoursite.com/store/success" name="return"/>
        <input type="hidden" value="http://www.yoursite.com/store/" name="cancel_return"/>
        <input type="hidden" name="business" value="[email protected]"/>
        <input type="hidden" name="currency_code" value="AUD"/>
        <input type="hidden" name="item_name" value="{title}"/>
        <input type="hidden" name="item_number" value="{item_id}"/>
        <input type="hidden" name="add" value="1"/>
        <input type="hidden" name="upload" value="1"/>
        <input type="hidden" name="no_shipping" value="2"/>
        <input type="hidden" name="no_note" value="1"/>
        <input type="hidden" name="undefined_quantity" value="1"/>
        <label for="amount">Size:</label>
        <script type="text/javascript">
        <!--
        function setSize(selObj, formObj) {
        for (var j = 0; j < selObj.length; j++)
          if (selObj.options[j].selected && j != 0) {
            {related_entries id="s_size"}
              if (selObj.options[j].value == '{size_price}') { formObj.elements['os0'].value = '{title}'; }
            {/related_entries}
          }
        }
        //-->
        </script>
        <input type="hidden" name="on0" value="Size">
        <input type="hidden" name="os0">
        <select name="amount" onchange="setSize(this, this.form)">
          <option value="">Please choose</option>
          {related_entries id="s_size"}
          <option value="{size_price}">{title} ${size_price}</option>
          {/related_entries}
        </select>

        <input class="btn" type="submit" name="buy_now" id="buy_now_{entry_id}" value="Buy Now">
      </form>
    </li>
    {/exp:simple_commerce:purchase}
{/exp:weblog:entries}
</ul>

So the store owner can now set up different options or variations for products without having to create duplicate store entries, and can now manage these options and their prices in one location; and the store will now give site visitors the ability to choose their preferred option and have the relevant information transferred correctly to PayPal.

With ExpressionEngine 2.0 not far from being released and with rumours that it’ll include a more robust ecommerce feature, there may not be the need to use this technique for too much longer, but in the meantime, hopefully it will help some others out who have been in the same situation as me.