Build an ecommerce store with LemonStand (part 2)
In the concluding part of this tutorial, Phil Schalm explains how to set up the content sections of your LemonStand ecommerce store, including the product page, shopping cart and checkout
Now that you’ve learned all about LemonStand I’m sure you’d really like to start ripping out some cool stuff, so let’s get started! We now have a basically empty homepage and nothing else. Let’s get started by creating a Product Page so we can show off our lovely Edmonton Ale.
The Product Page
We’ll begin that process by jumping to “Pages” on the menu and creating a new page. Let’s call the Page “Product” and we’ll just keep the default URL for now. We’ll also want to select our Template from the dropdown. There’s one other thing that we definitely need to do before setting to work on the HTML. Switch to the “Action” tab and select “shop:product” from the drop down list. This action pulls the product name from the URL and sets up the variable necessary to display product information. Once that’s done, switch back to the main “Page” tab and let’s begin coding!
To display the product information, let’s use two columns: a sidebar with the image and some miscellaneous info and the main column with all the pertinent details about our ale. We’ll start with some basic HTML5 to set the frame. We’ll be using the 960.gs grid framework, so some of this will look familiar to some readers. Copy and paste the following code into the “Content” block.
<div id="product_page" class="grid_12"> <aside class="grid_3 alpha"> <img src="" alt=""/> <div id="product_information"> <h2>Product Information</h2> <p>Category: <a href="#">Beers</a></p> </div> </aside> <section id="product_details" class="grid_9 omega"> <div id="product_title"> <h1>Edmonton Ale</h1> <p class="price"><span class="">$19.99</span></p> <div id="product_add"> <input type="submit" value="Add to Cart" name="" class="btn" /> </div> </div> <div id="product_description" class="grid_9 alpha omega"> <p>Description here of this wonderful beverage from the heart of the Canadian Rockies.</p> </div> </section></div>
So far everything’s hardcoded. Let’s change that by bringing in the product specific details. With the “shop:product” action we selected earlier, the $product variable is available on this page. It’s an instance of the Shop_product class, which you can read more about here. For now I will walk you through the more pertinent fields that are available.
Let’s start with the basic product information, the name and description, which, believe it or not, are named name and description respectively. For the first edit change the <h1> to the following:
<h1><?= h($product->name) ?></h1>
Sidenote: The h() call ensures the string is html-escaped properly.
LemonStand uses PHP’s short tags internally. While this decision is debatable, I would recommend you use them for any theme-related files as well, as that will be expected by the larger community. For those of you just beginning PHP, you can read more about the tags forms in the official documentation
My recommendation: Use them for LemonStand-internal work if you want, but make sure to use the long form for anything else (as short tags) are never guaranteed to be enabled.
Now that you’ve done the name, you probably know how to do the description, but for posterity’s sake I’ll write it out:
Get the Creative Bloq Newsletter
Daily design news, reviews, how-tos and more, as picked by the editors.
<div id="product_description" class="grid_9 alpha omega"> <?= $product->description ?></div>
You may notice that I’ve removed the <p></p> tag. The description field is a rich text editor field internally, which means that paragraph (and any other html tags used) are already included with the field value.
If you reload the page now, you should see the following:
Still looking a little rough, isn’t it? You’ve probably noticed that our product doesn’t actually have a description, so the description area is now empty. The image is also still broken because nothing’s in the source yet and we haven’t added an image to the product either. Before we go and edit the product, though, let’s do a few more things to the source.
If you took a gander at the LemonStand documentation for Shop_Product, you may have noticed the image_url method. Let’s use that to display an image (once we update the product). Right now we have this:
<img src="" alt="">
Which really doesn’t do much. We’ll change it to the following to begin with:
<? $image = $product->image_url(0, 218, 'auto') ?><img src="<?= $image ?>" alt="<?= $product->name ?>"/>
The image_url function takes three main parameters and two optional ones. The first (the 0 in this case) is the index of the image. For those of you new to PHP, a lot of counting is done from zero instead of one. It can be a little confusing to start, but the general rule of thumb is that if you’re ever accessing a set or group in LemonStand you’ll want to use one number lower that you normally would. So, in this case we use 0 instead of 1 to indicate we want the first image. The second two parameters are the width and height. Our theme has a width of 218 for the images, so we use that. We also don’t really care how tall the image is, so we use 'auto'. This means that when the image is resized it will be 218 pixels wide but the height won’t be cropped or the image won’t be squished to fit into a specific height. This code will put the image out once we add it, but what about products that don’t have an image? Let’s tweak the code a bit more:
<? if ($image = $product->image_url(0, 218, 'auto')): ?> <img src="<?= $image ?>" alt="<?= $product->name ?>"/><? endif ?>
This wraps up the output nice and tidily in a check to ensure that the product actually has an image. Now that the image is done, let’s also update the category. Change the category code to the following:
<? $category = $product->category_list[0] ?><p>Category: <a href="<?= $category->page_url('/category') ?>"><?= $category->name ?></a></p>
You’ll notice that we haven’t used the same check as on the image field. This is because products are required to have at least one category. You’ll also notice that we’re getting a little ahead of ourselves with the page_url('/category') bit, even though that page doesn’t exist yet. Don’t worry, we’ll be creating it shortly.
We’re nearly done the product page now:
Now, astute follower will realise that right now that our product will always be listed as $19.99. Let’s fix that so people aren’t surprised when they believe your thousand-dollar ale is actually $19.99. We’ll update the price and change it from:
<p class="price"><span class="">$19.99</span></p>
to
<p class="price"><span class=""><?= format_currency($product->price()) ?></span></p>
There, we’re done! Done displaying the product, that is. If you try to click on the ‘Add to Cart’ button you’ll notice that it doesn’t really do much. Let’s fix that now.
LemonStand and Ajax
LemonStand features a very in-depth Ajax implementation. Essentially, to fire off an Ajax request to a LemonStand action you do something like the following:
$(element).sendRequest('some_action_name');
That’s it. Simple and sweet. Let’s use that to have our ‘Add to Cart’ button run the backend “shop:on_addToCart” action. To do that we’ll change the following line:
<input type="submit" value="Add to Cart" name="" class="btn" />
We’re going to also need a <form> tag around the <input> so I’m going to add that too. We will end up with the following:
<?= open_form() ?> <input type="submit" value="Add to Cart" name="" class="btn" onclick="return $(this).sendRequest('shop:on_addToCart')" /><?= close_form() ?>
Sidenote: I’m aware that using ‘onclick’ is not the best way to handle the event. However, for the sake of this tutorial please bear with me; it’s much easier if the code relevant to the Ajax call stays with the button rather than being placed elsewhere.
For more information on the Ajax handler names and integration with LemonStand, be sure to check out the API Documentation.
We’ve edited the product form, but that JavaScript won’t work quite yet. In order for things to happen properly we’re going to need to edit our template a little bit. Open up the template (CMS > Templates) we created earlier and add the following code right after the line with $this->css_combine on it.
<?= $this->js_combine(array('jquery', 'ls_core_jquery')) ?>
Save the template and that’s it! Now when you click on the ‘Add to Cart’ button the product will be added to your cart. However, as of right now we don’t have any way to see the contents of our shopping cart so you’ll just have to trust me, but you should be seeing a yellow ‘Loading…’ banner quickly flash in at the top of the page. We’ll get to that in the next part of the tutorial, though. For now, let’s update the homepage so we can show off some products.
The homepage
Under CMS > Pages find the homepage. It should have a ‘Page URL’ of ‘/’ and probably has a title similar to ‘Welcome to the LemonStand eCommerce system!’. Click on that to begin editing it. The first thing we should do is change the title. Let’s call it ‘Welcome to Canadian Brewers’. Once that’s done, let’s change the content a bit. We’ll start by adding a banner to the top. Delete everything currently in the Content area and add the following:
<section id="banner" class="grid_12"> <img src="<?= root_url('resources/images/banner.jpg') ?>" /></section>
Now if you reload your homepage there should be a nice banner at the top! That’s a good start, but let’s show some products as well. We’ll add the following code right after the first section.
<section id="product_list" class="grid_12"> <?= open_form() ?> <? $categories = Shop_Category::create()->list_root_children('front_end_sort_order'); foreach ($categories as $category): ?> <h2><?= $category->name ?></h2> <? endforeach ?> <?= close_form() ?></section>
This will list all the categories on the homepage, sorted by the order you specify in the ‘Categories’ area of the backend. You can also see the open_form tags: they’re being added now because we’ll need them shortly, as this list is not very interesting so let’s show some products as well.
Sidenote: To see more information about the Shop_Category class, check out the documentation here.
We can get access to the products through the list_products method of the Shop_Category object. This method will allow us to sort the products, so let’s add the following code right after the <h2><?= $category->name ?></h2> bit that we just added.
<? $products = isset($products) ? $products : null; $i = 0; if ($products):?> <ul> <? foreach ($products as $product): $i++; $class = ($i % 4 == 0) ? 'omega' : (($i % 4 == 1) ? 'alpha' : ''); ?> <li class="grid_3 <?= $class ?>"> <dl class="product_listing"> <dd class="product_info"> <h3><a href="<?= $product->page_url('/product') ?>"><?= $product->name ?></a> <span class="price"><?= format_currency($product->price()) ?></span> </h3> </dd> <? if ($image = $product->image_url(0, 218, 'auto')): ?> <dd class="thumbnail_link"><a href="<?= $product->page_url('/product') ?>"> <img src="<?= $image ?>"/></a> </dd> <? endif ?> <dd class="description"><?= $product->description ?></dd> <dd class="actions"> <a href="#" class="btn" onclick="return $(this).sendRequest('shop:on_addToCart', {extraFields: {product_id: <?= $product->id ?>}})">Add to Cart</a> </dd> </dl> </li> <? endforeach ?> </ul><? endif ?>
Sidenote: For those wondering what the find_all method chained to list_products is: list_products returns a Db_ActiveRecord object, so you can chain additional sql modifiers on to it (for example, you could chain ->limit(4). find_all is when the SQL is run and it returns the collection of records matching the query).
The following bunch of code may seem like a ton, but let’s break it down. Most of it you’ve already seen already from the product page, but there’s been a few additions. The first is the page_url method. That’s just a shortcut helper that appends the product short name to the url you’re specifying, in this case ‘/product’. You can hover over the link; it’s quite straightforward. The other addition is the change to our JavaScript.
{extraFields: {product_id: <?= $product->id ?>}}
You’ll see the new bit. sendRequest takes a JavaScript object as the second parameter, and there’s a whole ton of options you can pass in to it. You can read more about them in the documentation. In this case, because we’re not on the product page LemonStand isn’t able to tell what product we want to add to our cart, so we’re passing its internal ID in.
That’s all for the homepage, though. Not much too it but it’s beginning to look snazzy! Let’s put together a Category page as well so we don’t have to clump everything together.
The category page
Our category page is going to look quite similar to the homepage, but it’s only going to list products from a specific category. Let’s start by adding a new Page in the backend. At the ‘Add Page’ screen, enter ‘Category’ as the title, make sure the Template you created at the beginning is selected, and make sure ‘shop:category’ is selected in the ‘Action’ drop-down on the ‘Action’ tab.
Then, let’s start with the following for the content:
<section id="product_list" class="grid_12"> <h1><?= $category->name ?></h1> <? $products = $category->list_products(array('sorting' => 'name'))->find_all(); ?></section>
You’ll notice that I didn’t include anything after getting the list of products. As you can probably guess, the same code we used for the homepage is going to go in there. Let’s not duplicate work, though, in case we need to make some edits later. Save the new page for now, then go to the ‘Partials’ area in the nav at the top. Add a new partial. We’ll call it ‘shop:product_list’, and use the following for the content:
<? $products = isset($products) ? $products : null; if ($products):?> <ul> <? foreach ($products as $product): ?> <li class="grid_3 alpha"> <dl class="product_listing"> <dd class="product_info"> <h3><a href="<?= $product->page_url('/product') ?>"><?= $product->name ?></a> <span class="price"><?= format_currency($product->price()) ?></span> </h3> </dd> <? if ($image = $product->image_url(0, 218, 'auto')): ?> <dd class="thumbnail_link"><a href="<?= $product->page_url('/product') ?>"> <img src="<?= $image ?>"/></a> </dd> <? endif ?> <dd class="description"><?= $product->description ?></dd> <dd class="actions"> <a href="#" class="btn" onclick="return $(this).sendRequest('shop:on_addToCart', {extraFields: {product_id: <?= $product->id ?>}})">Add to Cart</a> </dd> </dl> </li> <? endforeach ?> </ul><? endif ?>
As you can see, this is almost identical to the code we used for the homepage. You’ll see a slight change at the top, though. A parameter named ‘products’ should be passed in to the partial, but in case it’s not we should make sure it just displays emptiness rather than giving us an error. Save the partial, then go back to the Category page we just created. Now we’re going to add a little more code, so it will look like this:
<section id="product_list" class="grid_12"> <h1><?= $category->name ?></h1> <? $products = $category->list_products(array('sorting' => 'name'))->find_all(); $this->render_partial('shop:product_list', array('products' => $products)); ?></section>
Looks good, doesn’t it? That’s all for the Category page, but let’s go back and update the homepage so it utilises our brand new partial. Open it up in the backend, and change the <section id="product_list" class="grid_12"> code so it now looks like this:
<section id="product_list" class="grid_12"> <? $categories = Shop_Category::create()->list_root_children('front_end_sort_order'); foreach ($categories as $category): ?> <h2><?= $category->name ?></h2> <? $products = $category->list_products(array('sorting' => 'name'))->limit(4)->find_all(); $this->render_partial('shop:product_list', array('products' => $products)); ?> <? endforeach ?></section>
That’s a lot simpler, now isn’t it? If you were paying attention, you’ll also notice that I added in ->limit(4) after the list_products call. This will mean that only four products are shown per-category on the Home page, rather than showing them all. Since we now have a Category page we should leave it to show off all the products.
Now that all those pages are done, things are starting to look pretty nice. Let’s go in and update our single product, though, so it actually has a description and an image. That will make these pages look even spiffier! Open it up in the backend, and let’s add “Description here of this wonderful beverage from the heart of the Canadian Rockies.” (without the quotes) as the new description. We should also add an image, let’s use the following picture:
Save the product and take a look at the Category page, which should now look like this:
I think that’s looking pretty swanky, so let’s call this part of the tutorial a done deal. Next up we’re going to add more partials and have an actual cart page.
Cart, menus, and other bits
Navigation
In this section of the tutorial, we’re going to carry on building out the store and tidy a few things up. Right now the nav doesn’t really apply to our store, so let’s get that fixed up. We created a partial called ‘html:navigation’ for our site’s navigation. Let’s go and edit that now.
First, let’s trim things down. Delete all the code currently there and replace it with the following:
<ul class="clearfix"> <li><a href="<?= root_url('/') ?>">Home</a></li></ul>
Right now we don’t have any additional pages to display, but let’s put a placeholder in for our cart page. Add the following after the Home link:
<li><a href="<?= root_url('/cart') ?>">Shopping Cart</a></li>
What about our categories? We don’t plan to have a lot of them, so let’s put all the categories in the navigation. We’re going to use some code similar to what we used on the homepage. We’ll put the next bit of code before the Shopping Cart link.
<? $categories = Shop_Category::create()->list_root_children('front_end_sort_order'); foreach ($categories as $category):?><li><a href="<?= $category->page_url('/category') ?>"><?= $category->name ?></a></li><? endforeach ?>
All in all, the new ‘html:navigation’ partial should look like this:
<ul class="clearfix"> <li><a href="<?= root_url('/') ?>">Home</a></li> <? $categories = Shop_Category::create()->list_root_children('front_end_sort_order'); foreach ($categories as $category): ?> <li><a href="<?= $category->page_url('/category') ?>"><?= $category->name ?></a></li> <? endforeach ?> <li><a href="<?= root_url('/cart') ?>">Shopping Cart</a></li></ul>
Save that and take a look. Now the nav actually makes sense with the rest of store. Let’s also update the “Checkout here” bit so it shows the current value of our cart.
Mini Cart
Right now our mini cart looks like this:
It’s pretty nice, but not showing the correct price. Let’s fix that. We’ll also turn it into a partial: you’ll see why in a little bit. Currently that code’s in our Template, but we’re not going to edit that. First go to CMS > Partials and create a new partial. We’ll call it ‘shop:mini_cart’ and use the following for the content:
<a href="<?= root_url('/checkout') ?>" class="btn checkout">Checkout here → <span class="bag"><?= format_currency(Shop_Cart::total_price()) ?></span></a>
If you compared this to the placeholder code from our Template, you’d see that it’s essentially the same with the addition of some LemonStand specifics. We’ve set up the link to our (yet to be created) checkout page, and we’re now actually displaying the current value of the cart rather than the hardcoded value. We’ll go back to Templates in the menu and let’s render the partial in our Template. Find the following code:
<div class="mini_cart" id="mini_cart"><a href="#" class="btn checkout">Checkout here → <span class="bag">$0.00</span></a></div>
and replace it with this:
<div class="mini_cart" id="mini_cart"><? $this->render_partial('shop:mini_cart') ?></div>
If you go to the product page for your Edmonton Ale, add it to the cart a few times, then reload the page you will see that the total in the checkout area is now showing the correct value.
Updating elements via AJAX
Now, if you were paying attention to that code we just added, you’re probably wondering why we didn’t put the <div class="mini_cart" id="mini_cart"> inside the ‘shop:mini_cart’ partial. This is due to LemonStand’s very nice Ajax library that will let us automatically update HTML elements with the content of partials. We’re going to update our previous code so the mini cart updates automatically after adding a product to the cart. Let’s start by going to the ‘Product’ page and editing that.
Right now our ‘Add to Cart’ button looks like this:
<input type="submit" value="Add to Cart" name="" class="btn" onclick="return $(this).sendRequest('shop:on_addToCart')" />
Let’s update it to the following:
<input type="submit" value="Add to Cart" name="" class="btn" onclick="return $(this).sendRequest('shop:on_addToCart', {update: {'mini_cart': 'shop:mini_cart'}})" />
Reload your Product page and try adding a few more of those Edmonton Ales to your cart. You should see the the cart total in the upper right hand automatically increase. This is one of the best features that separates LemonStand from its competition, as these can make developing interactive stores much easier. With the Product page updated, let’s update our ‘shop:product_list’ partial as well. Our ‘Add to Cart’ button on the partial is a little more complex as we’re also passing the product_id in. We currently have the following:
<a href="#" class="btn" onclick="return $(this).sendRequest('shop:on_addToCart', {extraFields: {product_id: <?= $product->id ?>}})">Add to Cart</a>
Much like we did on the Product page, we’ll add in the update: {'mini_cart': 'shop:mini_cart'} bit and end up with the following:
<a href="#" class="btn" onclick="return $(this).sendRequest('shop:on_addToCart', {extraFields: {product_id: <?= $product->id ?>}, update: {'mini_cart': 'shop:mini_cart'}})">Add to Cart</a>
Now you can go to either the homepage or a Category page – this is why we created a ‘shop:product_list’ partial – and test out the ‘Add to Cart’ button. It should be working just like the button we changed on the Product page.
Creating the Cart Page
Let’s finish up this section of the tutorial by creating a cart page so customers can see everything they’re planning to by. We’ll begin similar to the pages in Part 4 by going to ‘Pages’ in the backend and add a new page. We’ll call it ‘Cart’, set the Template to the one we created, and set the Action (on the ‘Action’ tab) to ‘shop:cart’. We’ll start with the following placeholder code:
<?= open_form() ?> <div id="cart" class="grid_12"> <h1>Cart</h1> <div id="cart_partial"> <? $this->render_partial('shop:cart') ?> </div> </div><?= close_form() ?>
You’re probably thinking “wait, we haven’t created that partial yet” and you would be correct. Right now if you viewed the new Cart page from the frontend you’d see LemonStand’s error page. However, we’re going to move right along and create that partial so we can see what our Cart page should actually look like. Create a new partial, and call it ‘shop:cart’ so it fits with the code we entered in the Cart page. Then, we’ll start with the following code to get things kicked off:
<table class="cart"> <thead> <tr> <th>Cart Items</th> <th class="right">Quantity</th> <th class="right">Price</th> <th class="right last">Total</th> </tr> </thead> <tbody> <tr> <td> <div class="product_description"> <strong>Product Name</strong> </div> </td> <td class="right">1</td> <td class="right">$0.99</td> <td class="right cart_control last"> $0.00 <a title="Remove item" href="#" class="remove">Remove</a> </td> </tr> </tbody> </table><input type="submit" class="update btn float_right" value="Update Basket"><div class="scoreboard"> <ul class="scoreboard right"> <li class="last"> <h3>Estimated Total</h3> <p class="tagged">$0.00</p> </li> </ul> </div> <a class="button checkout btn" value="Checkout" href="<?= root_url('/checkout') ?>"><span>Checkout</span></a> <p class="post_checkout_btn"><a href="<?= root_url('/') ?>">Continue Shopping</a></p>
Now take a look at your cart page and you should see a nice looking cart, albeit one with all the wrong information. Let’s fix that now. We’ll start by filling in the correct items. Take the current <tbody> section and we’ll replace it in with the following:
<tbody> <? $items = Shop_Cart::list_active_items(); ?> <? if ($items): ?> <? foreach ($items as $item): ?> <tr> <td> <div class="product_description"> <strong><?= $item->product->name ?></strong> </div> </td> <td class="right quantity"><input name="item_quantity[<?= $item->key ?>]" value="<?= $item->quantity ?>" type="text"></td> <td class="right"><?= format_currency($item->single_price()) ?></td> <td class="right cart_control last"> <?= format_currency($item->total_price()) ?> <a title="Remove item" href="#" onclick="return $(this).getForm().sendRequest('shop:on_deleteCartItem', {update: {'cart_partial': 'shop:cart', 'mini_cart': 'shop:mini_cart'}, confirm: 'Do you really want to remove this item from cart?', extraFields: {key: '<?= $item->key ?>'}})" class="remove">Remove</a> </td> </tr> <? endforeach ?> <? else: ?> <tr><td colspan="4">Your cart is currently empty. Please fill it first!</td></tr> <? endif ?></tbody>
That’s the majority of the cart page, all that’s left is a few of the controls. We’ll fix the “Update Basket” button so it works correctly. Replace <input type="submit" class="update btn float_right" value="Update Basket"> with the following:
<input type="submit" class="update btn float_right" value="Update Basket" onclick="return $(this).getForm().sendRequest('on_action', {update: {'cart_partial': 'shop:cart', 'mini_cart': 'shop:mini_cart'}});">
We also need to show the correct total. Change the $0.00 to <?= format_currency($estimated_total) ?>. That’s our final step for putting the cart page together, which should now look something like this:
Now the only bit that’s left is the checkout process, which we’ll go over in the next part of the tutorial!
The checkout
Here it is, the big Kahuna, where you take everyone’s personal details and sell it to advertisers … oh wait, that’s someone else. I mean, the part where you take everyone’s necessary details so you can ship them cool things they want! Much like the other sections, let’s start off with a new page. Call it ‘Checkout’, make sure the Page URL is ‘/checkout’ and the template is correct, and set the Action to ‘shop:checkout’.
While store checkouts can be very complex, we’re going to keep this one simple. You may remember that we are offering pickup only (no shipping) and you have to pay at time of pickup. With that in mind, our checkout is going to consist of three steps: customer information and review. If you want to see how more complex checkouts are built, the LemonStand documentation has lots of detailed information along with some tips and tricks.
Let’s start with just a simple checkout page.
<?= open_form() ?> <div class="grid_12"> <h1>Checkout</h1> <div id="checkout"> <? $this->render_partial('shop:checkout') ?> </div> </div><?= close_form() ?>
As you can see, we’ll need to create a ‘shop:checkout’ partial as well. Go ahead and do that: we’ll put some basic code to get started.
<? switch($checkout_step) { case 'billing_info': $this->render_partial('shop:billing_info'); break; case 'review': $this->render_partial('shop:review'); break; }?>
And yes, we’ll need another two partials to handle these two steps of our checkout process. We’re going to put a simple form in billing info:
<h3>Customer Information</h3><div class="grid_7 form"> <p class="grid_3"> <label for="first_name">First Name</label> <input name="first_name" value="<?= h($billing_info->first_name) ?>" id="first_name" type="text"> </p> <p class="grid_3"> <label for="last_name">Last Name</label> <input name="last_name" value="<?= h($billing_info->last_name) ?>" id="last_name" type="text"> </p> <p class="grid_3"> <label for="email">Email</label> <input id="email" name="email" value="<?= h($billing_info->email) ?>" type="text"> </p> <p class="grid_3"> <label for="phone">Phone</label> <input id="phone" type="text" value="<?= h($billing_info->phone) ?>" name="phone"> </p> <p class="grid_6"> <label for="street_address">Street Address</label> <input id="street_address" name="street_address" type="text" value="<?= h($billing_info->street_address) ?>"> </p> <p class="grid_3"> <label for="city">City</label> <input id="city" type="text" name="city" value="<?= h($billing_info->city) ?>"> </p> <p class="grid_3"> <label for="zip">Zip/Postal Code</label> <input id="zip" type="text" name="zip" value="<?= h($billing_info->zip) ?>"> </p> <p class="grid_3"> <label for="country">Country</label> <select id="country" name="country" onchange="return $(this).getForm().sendRequest( 'shop:on_updateStateList', { extraFields: {'country': $(this).val(), 'control_name': 'state', 'control_id': 'state', 'current_state': '<?= $billing_info->state ?>'}, update: {'billing_states': 'shop:state_selector'} })"> <? foreach ($countries as $country): ?> <option <?= option_state($billing_info->country, $country->id) ?> value="<?= h($country->id) ?>"> <?= h($country->name) ?> </option> <? endforeach ?> </select> </p> <p class="grid_3"> <label for="state">State</label> <span id="billing_states"> <?= $this->render_partial('shop:state_selector', array( 'states'=>$states, 'control_id'=>'state', 'control_name'=>'state', 'current_state'=>$billing_info->state)) ?> </span> </p></div><div class="clear"></div><input type="hidden" name="checkout_step" value="<?= $checkout_step ?>"/><input type="button" value="Next" onclick="return $(this).getForm().sendRequest('on_action', {update:{'checkout': 'shop:checkout'}})"/>
Now, if you were paying attention to all that code, you would have noticed that we also need a new “shop:state_selector” partial. This lets us have a country selector that automatically updates the list of provinces/states. We’ll make that as well before we make our review page. Add the new partial with the correct name, and use this as the code (it’s the default that LemonStand uses for the demo theme, and is just what we need).
<select autocomplete="off" id="<?= h($control_id) ?>" name="<?= $control_name ?>"> <? foreach ($states as $state): ?> <option <?= option_state($current_state, $state->id) ?> value="<?= h($state->id) ?>"><?= h($state->name) ?></option> <? endforeach ?></select>
If you go to your checkout from the frontend of your store now, you should see a working checkout form like this:
However, we still have the review form to create and a few other steps to do.
While a big review form makes sense if you have tons of convoluted checkout steps, in our case things are pretty straightforward, so we’re going to make the review page really simple. Make a new partial, call it ‘shop:review’, and we’ll use the following:
<p>You have <?= Shop_Cart::get_item_total_num() ?> product(s) in your cart, which will run you a grand total of <?= format_currency($total) ?>. Are you sure you can afford to purchase this many beverages?</p><input type="hidden" name="checkout_step" value="<?= $checkout_step ?>"/><input type="button" value="Next" onclick="return $(this).getForm().sendRequest('on_action', {update:{'checkout': 'shop:checkout'}})"/>
Now, if you’re like me you were probably super-stoked and rushed to your checkout page, filled in the Customer Information form, then hit ‘Next’ … and waited. And nothing happened. Then in frustration you threw your expensive laptop out the window. And your cat. Well, while it sucks that your laptop is now smashed, there are still a few things we need to do. Take a look at the ‘shop:checkout’ partial:
<? switch($checkout_step) { case 'billing_info': $this->render_partial('shop:billing_info'); break; case 'review': $this->render_partial('shop:review'); break; }?>
And compare it with the one from the default LemonStand theme:
<? switch ($checkout_step) { case 'billing_info': $this->render_partial('shop:checkout_billing_info'); break; case 'shipping_info': $this->render_partial('shop:checkout_shipping_info'); break; case 'shipping_method': $this->render_partial('shop:checkout_shipping_method'); break; case 'payment_method': $this->render_partial('shop:checkout_payment_method'); break; case 'review': $this->render_partial('shop:checkout_review'); break; }?>
As you can see, we’ve removed a few steps. We need to add some additional code to make sure LemonStand knows how to handle the missing steps. Let’s start by adding a code on the ‘shop:billing_info’ partial to redirect right to the review page. Add the following line just before the “Next” button.
<input type="hidden" name="skip_to" value="review"/>
This will handle the movement through the checkout steps, but LemonStand will need a few more items to make sure everything works smoothly. Go to the ‘Checkout’ page in the backend, and switch to the ‘Action’ tab. We’re going to put the following code in to handle everything that’s needed:
if (post('skip_to') == 'review') { Shop_CheckoutData::copy_billing_to_shipping(); Shop_CheckoutData::set_payment_method(Shop_PaymentMethod::create()->find()->id); Shop_CheckoutData::set_shipping_method(Shop_ShippingOption::create()->find()->id);}
This code should be pretty straightforward if you just read it. We’re copying the billing information to the shipping information, then manually setting the payment and shipping methods. Because we only have one payment and shipping method we don’t need any special logic to find them: the find() method will return them by default. Once that’s done, “Save and Close” the Page as we’re going to need to add one more.
Once someone’s completed their checkout process, we need to thank them! Let’s make a Thank You page now. Add a new page, title it “Thank You”, make sure the Template is set, and enter a basic message in like the following:
<div class="grid_12"> <h1>Thank You!</h1> <p>Hey thanks a bunch for helping support us! Come down anytime to pickup and pay for your order. Hope to see you soon!</p></div>
Once that page is created, we need to make sure the customer is redirected to it once their checkout is completed. Go to “Payment Methods” under the “Shop” menu and select the basic payment method you created earlier. On the “Configuration” tab, make sure that “Thank You” is selected for the “Payment Page” option: this is where the customer will be redirected.
Phew, now that all those settings are complete, take the store for a spin! You should be able to run through the entire checkout process right up to the 'thank you' message. If you want to verify that your order has actually been submitted, go to “Orders” on the “Shop” menu in the backend: you should see your order show up in the list.
I hope everyone reading this tutorial has enjoyed it and found it useful. For more information on LemonStand, check out the website and follow them on Twitter. If you’ve come across this tutorial while building out a LemonStand site, make sure to check out the official forums if you need assistance while building the site.
Thank you for reading 5 articles this month* Join now for unlimited access
Enjoy your first month for just £1 / $1 / €1
*Read 5 free articles per month without a subscription
Join now for unlimited access
Try first month for just £1 / $1 / €1
The Creative Bloq team is made up of a group of design fans, and has changed and evolved since Creative Bloq began back in 2012. The current website team consists of eight full-time members of staff: Editor Georgia Coggan, Deputy Editor Rosie Hilder, Ecommerce Editor Beren Neale, Senior News Editor Daniel Piper, Editor, Digital Art and 3D Ian Dean, Tech Reviews Editor Erlingur Einarsson and Ecommerce Writer Beth Nicholls and Staff Writer Natalie Fear, as well as a roster of freelancers from around the world. The 3D World and ImagineFX magazine teams also pitch in, ensuring that content from 3D World and ImagineFX is represented on Creative Bloq.