Skip to main content
Spree does not run your checkout — your storefront does. What Spree owns is the list of things a cart still needs, which it reports on cart reads and enforces when you complete one. Adding to that list is how you make checkout require something of your own.
For what a cart reports and how to read it, see Carts. This page is about adding to it.

Requirements are the gate, steps are a label

Two things are easy to confuse:
  • A requirement is enforced. It appears on cart reads while unmet, and completing a cart recomputes the list and refuses if anything is outstanding.
  • A step is descriptive. It exists so a storefront can render a progress indicator. Nothing on the server checks which step a customer is on.
So a requirement is what you reach for when something must be true before an order exists. A step is only worth adding when your checkout genuinely has a stage the built-in five don’t describe.

Adding a requirement

When the flow is unchanged and you only need one more condition:
server/config/initializers/spree.rb
While satisfied: returns false the cart reports the requirement and refuses to complete. Its code is derived as <field>_required — vat_number_required here. The storefront satisfies it by writing the value into the cart’s metadata. When the cart is completed, its metadata is copied onto the order — onto every order if the checkout is split between sellers — so the VAT number is still there after placement as order.metadata['vat_number']. Pass applicable: to scope it. It is checked before satisfied:, so a requirement that doesn’t apply never appears at all:
message is stored verbatim — wrap it in Spree.t yourself if it needs translating.

Adding a step

A step bundles its own requirements and says when it is satisfied:
server/config/initializers/spree.rb
The step now shows up in the cart’s current_step and completed_steps, its requirements appear in requirements while unsatisfied, and completion refuses the cart until they are met. Each lambda receives the cart. Placement is controlled by two keywords: An unanchored step lands immediately before complete. An anchor naming a step this particular cart doesn’t have — before: :payment on a free cart — falls back to the same place. Unlike an added requirement, a step’s requirements: lambda may set its own code:.

Reordering or removing built-in steps

server/config/initializers/spree.rb
Removing a step removes it from the reported list. It does not remove the requirements filed under it — dropping payment does not let an unpaid cart complete. Requirements and steps are separate on purpose.

Prefer a requirement to a workflow hook

A workflow hook on carts.complete.validate can also reject a cart, and there are cases for it — anything needing a network call, or a decision that only makes sense at the moment of completion. For anything a customer has to fix, a requirement is better: it is reported on every cart read, so the storefront can show it while they can still act, rather than failing at the last step with an error they have to interpret.