Redefining checkout steps
Solidus comes bundled with a robust checkout system that caters to the needs of the majority of eCommerce stores. However, this system doesn't fit the needs for every business.
In this guide, we'll cover the steps you need to take to customize the checkout flow in your Solidus store.
The default checkout flow
The default Solidus checkout flow follows these steps, from start to finish:
- Cart
- Address
- Delivery
- Payment (if needed)
- Confirm
- Complete
Removing checkout steps
You'll need to override the order model to add or remove checkout steps. Inside the override, you
just need to add remove_checkout_step
and pass in the name of the step you want to remove. For
example, if you wanted to remove the address step, your override might look like this:
# frozen_string_literal: true
module MyApp
module Spree
module Order
module RemoveCheckoutStep
def self.prepended(base)
base.remove_checkout_step :address
end
::Spree::Order.prepend self
end
end
end
end
Please keep in mind the following caveats for removing checkout steps:
- If you remove the address step but keep the delivery step, the delivery step will break, as it expects the order to have an address.
- If you remove the payment step but the order has a positive balance, the order will not be able to complete.
Adding checkout steps
If you want to add a custom checkout step, you will need to call add_checkout_step
in the order
override, and pass in your custom step name, as well as a before
or after
attribute, so that
the order state machine knows where to put this custom step in the checkout flow.
# frozen_string_literal: true
module MyApp
module Spree
module Order
module AddCheckoutStep
def self.prepended(base)
base.insert_checkout_step :my_custom_step, { before: :confirm }
end
::Spree::Order.prepend self
end
end
end
end
You will also need to add a view for this custom step:
<div>
The customer will see this partial when they are on your checkout step.
Be sure to add a continue button!
</div>
<div class="form-buttons" data-hook="buttons">
<%= submit_tag t('spree.save_and_continue'), class: 'continue button primary' %>
<script>Spree.disableSaveOnClick();</script>
</div>
And finally, our step needs a translation that Solidus will display to the user:
en:
spree:
order_state:
my_custom_step: My Custom Step
Before step action
Before proceeding to any step, the checkout controller will check to see if a method
named before_#{checkout_step_name}
exists. If it does, it runs that method. That means that you
can run custom logic before the controller moves on to your step, which can be handy if you want to
set things up for the view.
# frozen_string_literal: true
module MyApp
module Spree
module CheckoutController
module AddBeforeCustomStep
def before_my_custom_step
@custom_object = CustomObject.new()
end
::Spree::CheckoutController.prepend self
end
end
end
end
Checkout Controller Attributes
If you need to permit additional attributes for your custom step, you can add the attributes to
the checkout_confirm_attributes
set in an initializer.
Spree::PermittedAttributes.checkout_my_custom_step_attributes << [:my_custom_attribute]
The attribute set that is used changes depending on the step: payment
uses checkout_payment_attributes
, address
uses checkout_address_attributes
, and so
on. checkout_confirm_attributes
is used for the confirm step, and for any step that the checkout
controller does not recognize - like our new custom step.
Conditional checkout steps
You can conditionally include steps in the checkout flow if necessary, by passing a conditional in
with the step in add_checkout_step
. For example, if we only want to include our custom step if the
orders total is over $50, we can pass that requirement in as a conditional:
# frozen_string_literal: true
module MyApp
module Spree
module Order
module AddCheckoutStep
def self.prepended(base)
base.insert_checkout_step :my_custom_step, {
before: :confirm, if: -> (order) { order.total > 50 }
}
end
::Spree::Order.prepend self
end
end
end
end
Now the custom step will only be displayed during checkout if the order total is above $50.00.
Overriding the state machine
If the customization options above are still not enough, you can override the entire state machine and use your own. We recommend that your new state machine inherit from the old one, so that you can utilize some of the logic of the old state machine.
First of all, you will have to define your own state machine:
# frozen_string_literal: true
require 'spree/core/state_machines/order'
module MyApp
class StateMachine
module Order
include ::Spree::Core::StateMachines::Order
module ClassMethods
include ::Spree::Core::StateMachines::Order::ClassMethods
def define_state_machine!
# Go crazy!
end
end
def self.included(klass)
klass.extend ClassMethods
end
end
end
end
Once it's defined, you just need to tell Solidus to use it:
Spree.config do |config|
config.state_machines.order = "MyApp::StateMachine::Order"
# ....
end