mirror of
https://github.com/openfoodfoundation/openfoodnetwork
synced 2026-01-28 21:07:16 +00:00
163 lines
5.4 KiB
Ruby
163 lines
5.4 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
|
|
class_attribute :checkout_steps
|
|
|
|
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.checkout_steps = {}
|
|
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
|
|
|
|
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: :payment, do: :validate_shipping_method!
|
|
before_transition to: :payment, do: :create_tax_charge!
|
|
before_transition to: :confirmation, do: :validate_payment_method!
|
|
|
|
after_transition to: :complete, do: :finalize!
|
|
after_transition to: :resumed, do: :after_resume
|
|
after_transition to: :canceled, do: :after_cancel
|
|
after_transition to: :payment, do: :set_payment_amount!
|
|
end
|
|
end
|
|
|
|
def self.go_to_state(name, options = {})
|
|
checkout_steps[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.find_transition(options = {})
|
|
return nil if options.nil? || !options.include?(:from) || !options.include?(:to)
|
|
|
|
next_event_transitions.detect do |transition|
|
|
transition[options[:from].to_sym] == options[:to].to_sym
|
|
end
|
|
end
|
|
|
|
def self.next_event_transitions
|
|
@next_event_transitions ||= []
|
|
end
|
|
|
|
def self.checkout_steps
|
|
@checkout_steps ||= {}
|
|
end
|
|
|
|
def self.add_transition(options)
|
|
next_event_transitions << { options.delete(:from) => options.delete(:to) }.
|
|
merge(options)
|
|
end
|
|
|
|
def checkout_steps
|
|
steps = self.class.checkout_steps.
|
|
each_with_object([]) { |(step, options), checkout_steps|
|
|
next if options.include?(:if) && !options[:if].call(self)
|
|
|
|
checkout_steps << step
|
|
}.map(&:to_s)
|
|
# Ensure there is always a complete step
|
|
steps << "complete" unless steps.include?("complete")
|
|
steps
|
|
end
|
|
|
|
def restart_checkout_flow
|
|
update_columns(
|
|
state: checkout_steps.first,
|
|
updated_at: Time.zone.now,
|
|
)
|
|
end
|
|
|
|
private
|
|
|
|
def validate_shipping_method!
|
|
return unless user && Flipper.enabled?(:split_checkout, user)
|
|
return if shipping_method.present?
|
|
|
|
errors.add :shipping_method, I18n.t('split_checkout.errors.select_a_shipping_method')
|
|
throw :halt
|
|
end
|
|
|
|
def validate_payment_method!
|
|
return unless user && Flipper.enabled?(:split_checkout, user)
|
|
return if payments.any?
|
|
|
|
errors.add :payment_method, I18n.t('split_checkout.errors.select_a_payment_method')
|
|
throw :halt
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|