Spree Commerce

Try It Now

Simplified Checkout Form Using Nested Attributes

Posted on April 08, 2009 by Sean Schofield

Rails 2.3 has an excellent new feature which allows for the use of nested
. Prior to this we had a CheckoutPresenter and separate
CheckoutController with a lot of confusing logic. We were using active_presenter to help simplify things but the nested forms allow us to simplify things even further.

In fact, we were able to consolidate all of the checkout logic back
into a single checkout method of
OrdersController and eliminate the non-restful hacks we
were relying on with CheckoutController.

Behold the new checkout method:

<p>def checkout<br />
  build_object<br />
  load_object<br />
	<li>additional default values needed for checkout<br />
  @order.bill_address ||= Address.new(:country =&gt; @default_country)<br />
  @order.ship_address ||= Address.new(:country =&gt; @default_country)<br />
  if @order.creditcards.empty?<br />
    @order.creditcards.build(:month =&gt; Date.today.month, :year =&gt; Date.today.year)<br />
  end<br />
  @shipping_method = ShippingMethod.find_by_id(params[:method_id]) if params[:method_id]<br />
  @shipping_method ||= @order.shipping_methods.first<br />
  @order.shipments.build(:address =&gt; @order.ship_address, :shipping_method =&gt; @shipping_method)</li>
if request.put?
@order.attributes = params[:order]
@order.creditcards<sup class="footnote" id="fnr0"><a href="#fn0">0</a></sup>.address = @order.bill_address if @order.creditcards.present?
@order.user = current_user
@order.ip_address = request.env[&#8216;REMOTE_ADDR&#8217;]
	<li>need to check valid b/c we dump the creditcard info while saving<br />
      if <code>order.valid?
        if params[:final_answer].blank?
          @order.creditcards[0].authorize(</code>order.total)<br />
	<li>remove the order from the session<br />
          session[:order_id] = nil<br />
        end<br />
      else<br />
        flash.now[:error] = t(&#8220;unable_to_save_order&#8221;)<br />
        render :action =&gt; &#8220;checkout&#8221; and return unless request.xhr?<br />
      end<br />
    rescue Spree::GatewayError =&gt; ge<br />
      flash.now[:error] = t(&#8220;unable_to_authorize_credit_card&#8221;) + &#8220;: #{ge.message}&#8221;</li>
render :action =&gt; &#8220;new&#8221; and return
respond_to do |format|
format.html {redirect_to order_url(@order, :checkout_complete =&gt; true) }
format.js {render :json =&gt; { :order =&gt; {:order_total =&gt; @order.total,
:ship_amount =&gt; @order.ship_amount,
:tax_amount =&gt; @order.tax_amount},
:available_methods =&gt; rate_hash}.to_json,
:layout =&gt; false}

This single method takes care of all of the steps of the checkout
process, including the AJAX posts that occur while transitioning between
steps. We’re also considering moving this checkout method into
its own module so that you can just override this library in your
extension instead of having a giant class_eval or requiring that you
duplicate the OrdersController in your site extension.

This will also mean some minor but hopefully obvious changes to your
existing sites if you have modified the checkout process. The mostly
likely change you will need to make is to move the location of any views
you have overridden so that they are in the app/views/orders folder
instead of app/views/checkout.