Files
openfoodnetwork/app/models/spree/order/checkout.rb
Maikel Linke 54738fc552 Remove unnecessary method checkout_steps
It allowed introspection of a dynamic state machine. But the only two
usages of this method only referred to the first state which is always
the same. Our complicated checkout logic needs more clarity and
introducing some hardcoded state names here can only help.
2024-03-21 13:43:54 +11:00

166 lines
5.2 KiB
Ruby

# frozen_string_literal: true
module Spree
class Order < ApplicationRecord
module Checkout
def self.included(klass)
klass.class_eval do
class_attribute :next_event_transitions
class_attribute :previous_states
class_attribute :checkout_flow
def self.checkout_flow(&block)
if block_given?
@checkout_flow = block
define_state_machine!
else
@checkout_flow
end
end
def self.define_state_machine!
self.next_event_transitions = []
self.previous_states = [:cart]
# Build the checkout flow using the checkout_flow defined either
# within the Order class, or a decorator for that class.
#
# This method may be called multiple times depending on if the
# checkout_flow is re-defined in a decorator or not.
instance_eval(&checkout_flow)
klass = self
# To avoid a ton of warnings when the state machine is re-defined
StateMachines::Machine.ignore_method_conflicts = true
# To avoid multiple occurrences of the same transition being defined
# On first definition, state_machines will not be defined
state_machines.clear if respond_to?(:state_machines)
state_machine :state, initial: :cart do
klass.next_event_transitions.each { |t| transition(t.merge(on: :next)) }
# Persist the state on the order
after_transition do |order|
order.state = order.state
order.save
end
event :cancel do
transition to: :canceled, if: :allow_cancel?
end
event :return do
transition to: :returned, from: :awaiting_return, unless: :awaiting_returns?
end
event :resume do
transition to: :resumed, from: :canceled, if: :allow_resume?
end
event :authorize_return do
transition to: :awaiting_return
end
event :restart_checkout do
transition to: :cart, unless: :completed?
end
event :confirm do
transition to: :complete, from: :confirmation
end
event :back_to_payment do
transition to: :payment, from: :confirmation
end
event :back_to_address do
transition to: :address, from: [:payment, :confirmation]
end
before_transition from: :cart, do: :ensure_line_items_present
before_transition to: :delivery, do: :create_proposed_shipments
before_transition to: :delivery, do: :ensure_available_shipping_rates
before_transition to: :confirmation, do: :validate_payment_method!
after_transition to: :payment do |order|
order.create_tax_charge!
order.update_totals_and_states
end
after_transition to: :complete, do: :finalize!
after_transition to: :resumed, do: :after_resume
after_transition to: :canceled, do: :after_cancel
end
end
def self.go_to_state(name, options = {})
previous_states.each do |state|
add_transition({ from: state, to: name }.merge(options))
end
if options[:if]
previous_states << name
else
self.previous_states = [name]
end
end
def self.next_event_transitions
@next_event_transitions ||= []
end
def self.add_transition(options)
next_event_transitions << { options.delete(:from) => options.delete(:to) }.
merge(options)
end
def restart_checkout_flow
update_columns(
state: "address",
updated_at: Time.zone.now,
)
end
def state_changed(name)
state = "#{name}_state"
return unless persisted?
old_state = __send__("#{state}_was")
state_changes.create(
previous_state: old_state,
next_state: __send__(state),
name:,
user_id:
)
end
private
def after_cancel
shipments.reject(&:canceled?).each(&:cancel!)
payments.checkout.each(&:void!)
OrderMailer.cancel_email(id).deliver_later if send_cancellation_email
update(payment_state: updater.update_payment_state)
end
def after_resume
shipments.each(&:resume!)
payments.void.each(&:resume!)
update(payment_state: updater.update_payment_state)
end
def validate_payment_method!
return unless checkout_processing
return if payments.any?
errors.add :payment_method, I18n.t('checkout.errors.select_a_payment_method')
throw :halt
end
end
end
end
end
end