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.
Adding a requirement
When the flow is unchanged and you only need one more condition:server/config/initializers/spree.rb
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 I18n.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
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
Prefer a requirement to a workflow hook
A workflow hook oncarts.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.
Carrying your own records onto the order
A cart’smetadata is copied onto the order for you. Records your extension keeps in
its own tables against a cart or its line items are not. Register a cart copier to
bring them across:
config/initializers/engravings.rb
app/services/my_app/copy_engravings.rb
line_item_map pairs each cart line item with its copy on the order. Copiers run
whenever a cart becomes an order: at checkout, and when a draft order is created from a
cart (Spree::Orders::CreateFromCart, which quoting extensions use). They run inside
that copy’s transaction once the order’s line items exist, so a copier that raises rolls
the whole copy back, and the cart is left as it was.
The copy commits before the payment is taken. If the payment fails before anything was
captured, the order is deleted rather than rolled back, and the customer’s next attempt
runs your copier again on a new order. Once a payment has been captured the order is
kept, and completing it resumes that order without copying again. Tie the copied records
to the order’s line items with a dependent or cascading delete, so they go with a deleted
order.
When a checkout spans several sellers, the order is divided after the copy. Line items
keep their ids as they move to each seller’s order, so key your records by line item
rather than by order.
When a guest signs in
A guest’s cart is folded into the customer’s cart when they sign in, and the guest cart is then deleted. That is a move, not a copy, so it has its own hook rather than a cart copier. A guest item the customer’s cart did not have moves across with its id; one it already had is added to the matching item and deleted.carts.merge.after_items_moved
runs before those deletions, inside the merge’s transaction:
config/initializers/engravings.rb
Related
- Carts — reading requirements and completing a cart
- Custom fields — storing your own data on a cart
- Services & Workflows — hooks that run during completion

