Module: Spree::Order::Checkout

Included in:
Spree::Order
Defined in:
app/models/spree/order/checkout.rb

Class Method Summary collapse

Class Method Details

.included(klass) ⇒ Object



4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
# File 'app/models/spree/order/checkout.rb', line 4

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!
      # Needs to be an ordered hash to preserve flow order
      self.checkout_steps = ActiveSupport::OrderedHash.new
      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
      StateMachine::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
        end

        event :resume do
          transition :to => :resumed, :from => :canceled, :if => :allow_resume?
        end

        event :authorize_return do
          transition :to => :awaiting_return
        end

        before_transition :to => :complete do |order|
          begin
            order.process_payments!
          rescue Spree::Core::GatewayError
            !!Spree::Config[:allow_checkout_on_gateway_error]
          end
        end

        before_transition :to => :delivery, :do => :remove_invalid_shipments!

        after_transition :to => :complete, :do => :finalize!
        after_transition :to => :delivery, :do => :create_tax_charge!
        after_transition :to => :resumed,  :do => :after_resume
        after_transition :to => :canceled, :do => :after_cancel

        after_transition :from => :delivery,  :do => :create_shipment!
      end
    end

    def self.go_to_state(name, options={})
      self.checkout_steps[name] = options
      if options[:if]
        previous_states.each do |state|
          add_transition({:from => state, :to => name}.merge(options))
        end
        self.previous_states << name
      else
        previous_states.each do |state|
          add_transition({:from => state, :to => name}.merge(options))
        end
        self.previous_states = [name]
      end
    end

    def self.remove_transition(options={})
      if transition = find_transition(options)
        self.next_event_transitions.delete(transition)
      end
    end

    def self.find_transition(options={})
      self.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 ||= ActiveSupport::OrderedHash.new
    end

    def self.add_transition(options)
      self.next_event_transitions << { options.delete(:from) => options.delete(:to) }.merge(options)
    end

    def checkout_steps
      checkout_steps = []
      # TODO: replace this with each_with_object once Ruby 1.9 is standard
      self.class.checkout_steps.each do |step, options|
        if options[:if]
          next unless options[:if].call(self)
        end
        checkout_steps << step
      end
      checkout_steps.map(&:to_s)
    end
  end
end