Solidus
Search…
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:
    1.
    Cart
    2.
    Address
    3.
    Delivery
    4.
    Payment (if needed)
    5.
    Confirm
    6.
    Complete

Removing checkout steps

You'll need to decorate the order model to add or remove checkout steps. Inside the decorator, 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 decorator might look like this:
app/decorators/my_app/spree/order_decorator.rb
1
# frozen_string_literal: true
2
3
module MyApp
4
module Spree
5
module OrderDecorator
6
def self.prepended(base)
7
base.remove_checkout_step :address
8
end
9
10
::Spree::Order.prepend self
11
end
12
end
13
end
Copied!
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 decorator, 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.
app/decorators/my_app/spree/order_decorator.rb
1
# frozen_string_literal: true
2
3
module MyApp
4
module Spree
5
module OrderDecorator
6
def self.prepended(base)
7
base.insert_checkout_step :my_custom_step, {before: :confirm}
8
end
9
10
::Spree::Order.prepend self
11
end
12
end
13
end
Copied!
You will also need to add a view and a translation for this custom step.
config/locales/en.yml
app/views/spree/checkout/_my_custom_step.html.erb
config/locales/en.yml
1
en:
2
spree:
3
order_state:
4
my_custom_step: My Custom Step
Copied!
app/views/spree/checkout/_my_custom_step.html.erb
1
<div>
2
The customer will see this partial when they are on your checkout step. Be sure to add a continue button!
3
</div>
4
5
<div class="form-buttons" data-hook="buttons">
6
<%= submit_tag t('spree.save_and_continue'), class: 'continue button primary' %>
7
<script>Spree.disableSaveOnClick();</script>
8
</div>
Copied!

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.
app/controllers/my_app/checkout_controller_decorator.rb
1
# frozen_string_literal: true
2
3
module MyApp
4
module CheckoutControllerDecorator
5
def before_my_custom_step
6
@custom_object = CustomObject.new()
7
end
8
9
::Spree::CheckoutController.prepend self
10
end
11
end
Copied!

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.
config/initializers/my_app_initializer.rb
1
Spree::PermittedAttributes.checkout_confirm_attributes << [:my_custom_attribute]
Copied!
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:
app/decorators/my_app/spree/order_decorator.rb
1
# frozen_string_literal: true
2
3
module MyApp
4
module Spree
5
module OrderDecorator
6
def self.prepended(base)
7
base.insert_checkout_step :my_custom_step, {
8
before: :confirm, if: -> (order) { order.total > 50 }
9
}
10
end
11
12
::Spree::Order.prepend self
13
end
14
end
15
end
Copied!
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.
After defining your own state machine, you can pass the class into an initializer so that Solidus knows to use your custom machine instead of the default machine.
config/initializers/spree.rb
lib/my_app/state_machine.rb
config/initializers/spree.rb
1
Spree.config do |config|
2
config.state_machines.order = "MyApp::StateMachine::Order"
3
4
# ....
5
end
Copied!
lib/my_app/state_machine.rb
1
# frozen_string_literal: true
2
3
require 'spree/core/state_machines/order'
4
5
module MyApp
6
class StateMachine
7
module Order
8
include ::Spree::Core::StateMachines::Order
9
10
module ClassMethods
11
include ::Spree::Core::StateMachines::Order::ClassMethods
12
13
def define_state_machine!
14
# Go crazy!
15
end
16
end
17
18
def self.included(klass)
19
klass.extend ClassMethods
20
end
21
end
22
end
23
end
Copied!
Last modified 4mo ago