mirror of
https://github.com/openfoodfoundation/openfoodnetwork
synced 2026-02-18 00:17:25 +00:00
Triggering it for each order is inefficient when we cancel them in bulk. The callback doesn't allow us to optimise this.
166 lines
5.2 KiB
Ruby
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
|