Spree Commerce

Try It Now

Spree Checkout - An Introduction

Posted on January 24, 2011 by Zac Williams

One of the more frequently discussed topics on the Spree Google Group is the checkout process in Spree. It is one of the most basic and important features of any e-commerce application, but it can also be very complicated under the hood. This post attempts to break down the various aspects of the checkout process in Spree.


In Spree, the checkout process is driven by the CheckoutController. The CheckoutController is unique within Spree in that it is not a typical RESTful controller. There is no ‘checkouts’ table, nor is there a Checkout model. The default CheckoutController implements a couple of actions (edit and update) that, at the most basic level, advance the state of an Order object.

Order Model

The Order model is a typical ActiveRecord class that encapsulates the attributes and business logic of an order. For purposes of this post, relevant bits of the Order model are: the definition of the states, events, and transitions (and related callbacks) for the Order state machine. (The Order model utilizes the state_machine gem to define the states, transitions, etc. For more information on state_machine, please see the state_machine Github page.) Below is the state_machine block for the Order model from Spree v0.40.2.


<p>state_machine :initial =&gt; &#8216;cart&#8217;, :use_transactions =&gt; false do</p>
event :next do
transition :from =&gt; &#8216;cart&#8217;, :to =&gt; &#8216;address&#8217;
transition :from =&gt; &#8216;address&#8217;, :to =&gt; &#8216;delivery&#8217;
transition :from =&gt; &#8216;delivery&#8217;, :to =&gt; &#8216;payment&#8217;
transition :from =&gt; &#8216;payment&#8217;, :to =&gt; &#8216;confirm&#8217;
transition :from =&gt; &#8216;confirm&#8217;, :to =&gt; &#8216;complete&#8217;
event :cancel do
transition :to =&gt; &#8216;canceled&#8217;, :if =&gt; :allow_cancel?
event :return do
transition :to =&gt; &#8216;returned&#8217;, :from =&gt; &#8216;awaiting_return&#8217;
event :resume do
transition :to =&gt; &#8216;resumed&#8217;, :from =&gt; &#8216;canceled&#8217;, :if =&gt; :allow_resume?
event :authorize_return do
transition :to =&gt; &#8216;awaiting_return&#8217;
before_transition :to =&gt; &#8216;complete&#8217; do |order|
rescue Spree::GatewayError
if Spree::Config[:allow_checkout_on_gateway_error]
after_transition :to =&gt; &#8216;complete&#8217;, :do =&gt; :finalize!
after_transition :to =&gt; &#8216;delivery&#8217;, :do =&gt; :create_tax_charge!
after_transition :to =&gt; &#8216;payment&#8217;, :do =&gt; :create_shipment!
after_transition :to =&gt; &#8216;canceled&#8217;, :do =&gt; :after_cancel


The most important event during checkout (in fact the only event called during a normal checkout) is the ‘next’ event. By examining the code above, you will notice that the ‘next’ event transitions the order through the various checkout steps: ‘cart’, ‘address’, ‘delivery’, ‘payment’, ‘confirm’ and ‘complete’.

Checkout routes

Assuming a customer has at least one item in their cart, they should be able to access the first checkout step at http://my-spree-store.com/checkout. Looking at the routes file in Spree Core, you will see that a GET request on /checkout/:state route’s to the CheckoutController’s edit action. Leaving the :state parameter blank will pre-populate the :state parameter with ‘address’:


<p>match &#8216;/checkout/update/:state&#8217; =&gt; &#8216;checkout#update&#8217;, :as =&gt; :update_checkout<br />
match &#8216;/checkout/:state&#8217; =&gt; &#8216;checkout#edit&#8217;, :as =&gt; :checkout_state<br />
match &#8216;/checkout&#8217; =&gt; &#8216;checkout#edit&#8217;, :state =&gt; &#8216;address&#8217;, :as =&gt; :checkout</p>

Rendering the checkout form

When the checkout/edit.html.erb template is rendered, a form is built that renders fields defined in a partial named with the same value of @order.state. For example http://my-spree-store.com/checkout/address renders checkout/edit.html.erb, which in turn renders [checkout/address.html.erb](https://github.com/railsdog/spree/blob/v0.40.2/core/app/views/checkout/address.html.erb).

Checkout Form Submission

Navigating through the various checkout screens by submitting the form performs a PUT request to the CheckoutController’s ‘update’ action. The first thing the ‘update’ action does after loading the order, update the order’s attributes with params[:order]. Assuming this is successful, the ‘next’ method is called on the updated order object, and the request is redirected to the next step in the checkout.

The Final Checkout Step

The final checkout step is the the ‘confirm’ step. During this step (http://my-spree-store.com/checkout/confim) the customer is allowed to review their order details one last time before placing the order. When the customer submits the form from this step, one final CheckoutController#update is executed, and the order transitions to the ‘complete’ state. At this point, the request is redirected back to the order_path and the checkout is complete.

state_machine callbacks

The Order state machine also defines various callbacks. For example, there is an after_transition callback that executes the Order#finalize! when an order transitions to the ‘complete’ state. I will leave it as an exercise for the reader to follow-up on what these callbacks actually do, but it is worth pointing out that they are present.

CheckoutController callbacks

In addition to the callbacks in the Order state_machine, there are also various callbacks that are executed before transitioning to/from various states. Again, the reader should investigate further, but you may want to start with load_order and update.