spree_core 3.0.1 → 3.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/app/models/friendly_id/slug_decorator.rb +3 -0
  3. data/app/models/spree/adjustment.rb +1 -1
  4. data/app/models/spree/calculator.rb +5 -0
  5. data/app/models/spree/customer_return.rb +22 -16
  6. data/app/models/spree/gateway/bogus.rb +4 -0
  7. data/app/models/spree/line_item.rb +1 -1
  8. data/app/models/spree/order.rb +22 -48
  9. data/app/models/spree/order/checkout.rb +270 -255
  10. data/app/models/spree/order_contents.rb +64 -61
  11. data/app/models/spree/order_merger.rb +65 -0
  12. data/app/models/spree/payment.rb +5 -0
  13. data/app/models/spree/payment/processing.rb +2 -1
  14. data/app/models/spree/payment_method/check.rb +12 -2
  15. data/app/models/spree/product.rb +5 -0
  16. data/app/models/spree/promotion_handler/cart.rb +18 -14
  17. data/app/models/spree/shipment.rb +1 -1
  18. data/app/models/spree/stock/availability_validator.rb +10 -9
  19. data/app/models/spree/stock/content_item.rb +8 -0
  20. data/app/models/spree/stock/package.rb +8 -0
  21. data/app/models/spree/stock_item.rb +5 -1
  22. data/app/models/spree/stock_movement.rb +6 -1
  23. data/app/models/spree/variant.rb +18 -15
  24. data/app/models/spree/zone.rb +39 -29
  25. data/app/views/spree/order_mailer/cancel_email.html.erb +1 -1
  26. data/app/views/spree/order_mailer/cancel_email.text.erb +1 -1
  27. data/app/views/spree/order_mailer/confirm_email.html.erb +1 -1
  28. data/app/views/spree/order_mailer/confirm_email.text.erb +1 -1
  29. data/config/initializers/user_class_extensions.rb +4 -0
  30. data/config/locales/en.yml +4 -3
  31. data/db/default/spree/default_reimbursement_type.rb +1 -0
  32. data/db/migrate/20150515211137_fix_adjustment_order_id.rb +70 -0
  33. data/db/migrate/20150522181728_add_deleted_at_to_friendly_id_slugs.rb +6 -0
  34. data/db/migrate/20150609093816_increase_scale_on_pre_tax_amounts.rb +16 -0
  35. data/db/migrate/20150707204155_enable_acts_as_paranoid_on_calculators.rb +6 -0
  36. data/lib/spree/core/controller_helpers/auth.rb +1 -1
  37. data/lib/spree/core/engine.rb +7 -0
  38. data/lib/spree/core/validators/email.rb +7 -3
  39. data/lib/spree/core/version.rb +1 -1
  40. data/lib/spree/permitted_attributes.rb +2 -2
  41. data/lib/spree/testing_support/common_rake.rb +3 -8
  42. data/lib/spree/testing_support/factories.rb +1 -1
  43. data/lib/spree/testing_support/factories/order_factory.rb +11 -0
  44. data/lib/spree/testing_support/order_walkthrough.rb +1 -1
  45. metadata +11 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 1d168d119d3a44a1239b14adbbbbd73e6fe2d23e
4
- data.tar.gz: a3fa4208dc96c9d0c279fcd46a579a309faed8de
3
+ metadata.gz: 7aeecb32436b2f2e63f695ae7f8e72e0fc5d3dff
4
+ data.tar.gz: d3d9c62816751345378389914b51d876ac8f2910
5
5
  SHA512:
6
- metadata.gz: c968dcf2009be78df09de90f3760f77c945cc86c16d2ca356213defed6699f049fc8823303b737ab6518ce0cec7ff5243d317b8464147e19e90df97dc03d593c
7
- data.tar.gz: 45a207f6c1c87d87f49d14f0f18798568cb0709ed35f0c8de036c23ef335bf1cb2bbf8046f31ed8b176d452252f7f910f3e1d9f3c7f6d71b4fb6ee2e875b8c07
6
+ metadata.gz: 36a03013620da8c4081e87c51c29722106df038560a5cc0da2a6c3d39e5441d073e7deb643c349aacf276d96fb0c53f6fa3bb880ba6082dcb5ef86add172c2ce
7
+ data.tar.gz: e7e6816d26ae750cc2c9b5faee24fe5814d280679758e77e271d9a35952dcb52eb85e4bd54fb9598a1ea0240426f4870a51eb29a3725457b4573381bbce1fcdb
@@ -0,0 +1,3 @@
1
+ FriendlyId::Slug.class_eval do
2
+ acts_as_paranoid
3
+ end
@@ -24,7 +24,7 @@ module Spree
24
24
  class Adjustment < Spree::Base
25
25
  belongs_to :adjustable, polymorphic: true, touch: true
26
26
  belongs_to :source, polymorphic: true
27
- belongs_to :order, class_name: "Spree::Order"
27
+ belongs_to :order, class_name: 'Spree::Order', inverse_of: :all_adjustments
28
28
 
29
29
  validates :adjustable, presence: true
30
30
  validates :order, presence: true
@@ -1,5 +1,10 @@
1
1
  module Spree
2
2
  class Calculator < Spree::Base
3
+ # Conditional check for backwards compatibilty since acts as paranoid was added late https://github.com/spree/spree/issues/5858
4
+ if connection.table_exists?(:spree_calculators) && connection.column_exists?(:spree_calculators, :deleted_at)
5
+ acts_as_paranoid
6
+ end
7
+
3
8
  belongs_to :calculable, polymorphic: true
4
9
 
5
10
  # This method calls a compute_<computable> method. must be overriden in concrete calculator.
@@ -2,15 +2,17 @@ module Spree
2
2
  class CustomerReturn < Spree::Base
3
3
  belongs_to :stock_location
4
4
 
5
- has_many :return_items, inverse_of: :customer_return
6
- has_many :return_authorizations, through: :return_items
7
5
  has_many :reimbursements, inverse_of: :customer_return
6
+ has_many :return_authorizations, through: :return_items
7
+ has_many :return_items, inverse_of: :customer_return
8
8
 
9
9
  after_create :process_return!
10
10
  before_create :generate_number
11
11
 
12
12
  validates :return_items, presence: true
13
13
  validates :stock_location, presence: true
14
+
15
+ validate :must_have_return_authorization, on: :create
14
16
  validate :return_items_belong_to_same_order
15
17
 
16
18
  accepts_nested_attributes_for :return_items
@@ -18,8 +20,12 @@ module Spree
18
20
  extend DisplayMoney
19
21
  money_methods pre_tax_total: { currency: Spree::Config[:currency] }
20
22
 
21
- def pre_tax_total
22
- return_items.sum(:pre_tax_amount)
23
+ def completely_decided?
24
+ !return_items.undecided.exists?
25
+ end
26
+
27
+ def fully_reimbursed?
28
+ completely_decided? && return_items.accepted.includes(:reimbursement).all? { |return_item| return_item.reimbursement.try(:reimbursed?) }
23
29
  end
24
30
 
25
31
  # Temporarily tie a customer_return to one order
@@ -32,15 +38,21 @@ module Spree
32
38
  order.try(:id)
33
39
  end
34
40
 
35
- def fully_reimbursed?
36
- completely_decided? && return_items.accepted.includes(:reimbursement).all? { |return_item| return_item.reimbursement.try(:reimbursed?) }
41
+ def pre_tax_total
42
+ return_items.sum(:pre_tax_amount)
37
43
  end
38
44
 
39
- def completely_decided?
40
- !return_items.undecided.exists?
45
+ private
46
+
47
+ def inventory_units
48
+ return_items.flat_map(&:inventory_unit)
41
49
  end
42
50
 
43
- private
51
+ def must_have_return_authorization
52
+ if item = return_items.find { |ri| ri.return_authorization.blank? }
53
+ errors.add(:base, Spree.t(:missing_return_authorization, item_name: item.inventory_unit.variant.name))
54
+ end
55
+ end
44
56
 
45
57
  def generate_number
46
58
  self.number ||= loop do
@@ -55,15 +67,9 @@ module Spree
55
67
  end
56
68
 
57
69
  def return_items_belong_to_same_order
58
- if return_items.select{ |return_item| return_item.inventory_unit.order_id != order_id }.any?
70
+ if return_items.select { |return_item| return_item.inventory_unit.order_id != order_id }.any?
59
71
  errors.add(:base, Spree.t(:return_items_cannot_be_associated_with_multiple_orders))
60
72
  end
61
73
  end
62
-
63
- def inventory_units
64
- return_items.flat_map(&:inventory_unit)
65
- end
66
-
67
74
  end
68
75
  end
69
-
@@ -60,6 +60,10 @@ module Spree
60
60
  ActiveMerchant::Billing::Response.new(true, 'Bogus Gateway: Forced success', {}, :test => true, :authorization => '12345')
61
61
  end
62
62
 
63
+ def cancel(_response_code)
64
+ ActiveMerchant::Billing::Response.new(true, 'Bogus Gateway: Forced success', {}, test: true, authorization: '12345')
65
+ end
66
+
63
67
  def test?
64
68
  # Test mode is not really relevant with bogus gateway (no such thing as live server)
65
69
  true
@@ -132,8 +132,8 @@ module Spree
132
132
 
133
133
  def update_adjustments
134
134
  if quantity_changed?
135
- update_tax_charge # Called to ensure pre_tax_amount is updated.
136
135
  recalculate_adjustments
136
+ update_tax_charge # Called to ensure pre_tax_amount is updated.
137
137
  end
138
138
  end
139
139
 
@@ -58,7 +58,7 @@ module Spree
58
58
  alias_attribute :shipping_address, :ship_address
59
59
 
60
60
  belongs_to :store, class_name: 'Spree::Store'
61
- has_many :state_changes, as: :stateful
61
+ has_many :state_changes, as: :stateful, dependent: :destroy
62
62
  has_many :line_items, -> { order("#{LineItem.table_name}.created_at ASC") }, dependent: :destroy, inverse_of: :order
63
63
  has_many :payments, dependent: :destroy
64
64
  has_many :return_authorizations, dependent: :destroy, inverse_of: :order
@@ -70,6 +70,11 @@ module Spree
70
70
  has_many :products, through: :variants
71
71
  has_many :variants, through: :line_items
72
72
  has_many :refunds, through: :payments
73
+ has_many :all_adjustments,
74
+ class_name: 'Spree::Adjustment',
75
+ foreign_key: :order_id,
76
+ dependent: :destroy,
77
+ inverse_of: :order
73
78
 
74
79
  has_and_belongs_to_many :promotions, join_table: 'spree_orders_promotions'
75
80
 
@@ -99,6 +104,7 @@ module Spree
99
104
  validate :has_available_shipment
100
105
 
101
106
  delegate :update_totals, :persist_totals, :to => :updater
107
+ delegate :merge!, to: :merger
102
108
 
103
109
  class_attribute :update_hooks
104
110
  self.update_hooks = Set.new
@@ -132,11 +138,6 @@ module Spree
132
138
  self.line_item_comparison_hooks.add(hook)
133
139
  end
134
140
 
135
- def all_adjustments
136
- Adjustment.where("order_id = :order_id OR (adjustable_id = :order_id AND adjustable_type = 'Spree::Order')",
137
- order_id: self.id)
138
- end
139
-
140
141
  # For compatiblity with Calculator::PriceSack
141
142
  def amount
142
143
  line_items.inject(0.0) { |sum, li| sum + li.amount }
@@ -205,6 +206,10 @@ module Spree
205
206
  updater.update
206
207
  end
207
208
 
209
+ def merger
210
+ @merger ||= Spree::OrderMerger.new(self)
211
+ end
212
+
208
213
  def clone_billing_address
209
214
  if bill_address and self.ship_address.nil?
210
215
  self.ship_address = bill_address.clone
@@ -229,16 +234,17 @@ module Spree
229
234
 
230
235
  # Associates the specified user with the order.
231
236
  def associate_user!(user, override_email = true)
232
- self.user = user
233
- attrs_to_set = { user_id: user.id }
234
- attrs_to_set[:email] = user.email if override_email
235
- attrs_to_set[:created_by_id] = user.id if self.created_by.blank?
236
- assign_attributes(attrs_to_set)
237
+ self.user = user
238
+ self.email = user.email if override_email
239
+ self.created_by ||= user
240
+ self.bill_address ||= user.bill_address
241
+ self.ship_address ||= user.ship_address
237
242
 
238
- if persisted?
239
- # immediately persist the changes we just made, but don't use save since we might have an invalid address associated
240
- self.class.unscoped.where(id: id).update_all(attrs_to_set)
241
- end
243
+ changes = slice(:user_id, :email, :created_by_id, :bill_address_id, :ship_address_id)
244
+
245
+ # immediately persist the changes we just made, but don't use save
246
+ # since we might have an invalid address associated
247
+ self.class.unscoped.where(id: self).update_all(changes)
242
248
  end
243
249
 
244
250
  def quantity_of(variant, options = {})
@@ -395,44 +401,12 @@ module Spree
395
401
  end
396
402
  end
397
403
 
398
- def merge!(order, user = nil)
399
- order.line_items.each do |other_order_line_item|
400
- next unless other_order_line_item.currency == currency
401
-
402
- # Compare the line items of the other order with mine.
403
- # Make sure you allow any extensions to chime in on whether or
404
- # not the extension-specific parts of the line item match
405
- current_line_item = self.line_items.detect { |my_li|
406
- my_li.variant == other_order_line_item.variant &&
407
- self.line_item_comparison_hooks.all? { |hook|
408
- self.send(hook, my_li, other_order_line_item.serializable_hash)
409
- }
410
- }
411
- if current_line_item
412
- current_line_item.quantity += other_order_line_item.quantity
413
- current_line_item.save!
414
- else
415
- other_order_line_item.order_id = self.id
416
- other_order_line_item.save!
417
- end
418
- end
419
-
420
- self.associate_user!(user) if !self.user && !user.blank?
421
-
422
- updater.update_item_count
423
- updater.update_item_total
424
- updater.persist_totals
425
-
426
- # So that the destroy doesn't take out line items which may have been re-assigned
427
- order.line_items.reload
428
- order.destroy
429
- end
430
-
431
404
  def empty!
432
405
  line_items.destroy_all
433
406
  updater.update_item_count
434
407
  adjustments.destroy_all
435
408
  shipments.destroy_all
409
+ state_changes.destroy_all
436
410
 
437
411
  update_totals
438
412
  persist_totals
@@ -1,323 +1,338 @@
1
1
  module Spree
2
2
  class Order < Spree::Base
3
3
  module Checkout
4
- def self.included(klass)
5
- klass.class_eval do
6
- class_attribute :next_event_transitions
7
- class_attribute :previous_states
8
- class_attribute :checkout_flow
9
- class_attribute :checkout_steps
10
- class_attribute :removed_transitions
11
-
12
- def self.checkout_flow(&block)
13
- if block_given?
14
- @checkout_flow = block
15
- define_state_machine!
16
- else
17
- @checkout_flow
18
- end
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ class_attribute :next_event_transitions
8
+ class_attribute :previous_states
9
+ class_attribute :checkout_flow
10
+ class_attribute :checkout_steps
11
+ class_attribute :removed_transitions
12
+
13
+ self.checkout_steps ||= {}
14
+ self.next_event_transitions ||= []
15
+ self.previous_states ||= [:cart]
16
+ self.removed_transitions ||= []
17
+
18
+ def self.checkout_flow(&block)
19
+ if block_given?
20
+ @checkout_flow = block
21
+ define_state_machine!
22
+ else
23
+ @checkout_flow
19
24
  end
25
+ end
20
26
 
21
- def self.define_state_machine!
22
- self.checkout_steps = {}
23
- self.next_event_transitions = []
24
- self.previous_states = [:cart]
25
- self.removed_transitions = []
26
-
27
- # Build the checkout flow using the checkout_flow defined either
28
- # within the Order class, or a decorator for that class.
29
- #
30
- # This method may be called multiple times depending on if the
31
- # checkout_flow is re-defined in a decorator or not.
32
- instance_eval(&checkout_flow)
33
-
34
- klass = self
35
-
36
- # To avoid a ton of warnings when the state machine is re-defined
37
- StateMachines::Machine.ignore_method_conflicts = true
38
- # To avoid multiple occurrences of the same transition being defined
39
- # On first definition, state_machines will not be defined
40
- state_machines.clear if respond_to?(:state_machines)
41
- state_machine :state, initial: :cart, use_transactions: false, action: :save_state do
42
- klass.next_event_transitions.each { |t| transition(t.merge(on: :next)) }
43
-
44
- # Persist the state on the order
45
- after_transition do |order, transition|
46
- order.state = order.state
47
- order.state_changes.create(
48
- previous_state: transition.from,
49
- next_state: transition.to,
50
- name: 'order',
51
- user_id: order.user_id
52
- )
53
- order.save
54
- end
27
+ def self.define_state_machine!
28
+ self.checkout_steps = {}
29
+ self.next_event_transitions = []
30
+ self.previous_states = [:cart]
31
+ self.removed_transitions = []
55
32
 
56
- event :cancel do
57
- transition to: :canceled, if: :allow_cancel?
58
- end
33
+ # Build the checkout flow using the checkout_flow defined either
34
+ # within the Order class, or a decorator for that class.
35
+ #
36
+ # This method may be called multiple times depending on if the
37
+ # checkout_flow is re-defined in a decorator or not.
38
+ instance_eval(&checkout_flow)
39
+
40
+ klass = self
41
+
42
+ # To avoid a ton of warnings when the state machine is re-defined
43
+ StateMachines::Machine.ignore_method_conflicts = true
44
+ # To avoid multiple occurrences of the same transition being defined
45
+ # On first definition, state_machines will not be defined
46
+ state_machines.clear if respond_to?(:state_machines)
47
+ state_machine :state, initial: :cart, use_transactions: false, action: :save_state do
48
+ klass.next_event_transitions.each { |t| transition(t.merge(on: :next)) }
49
+
50
+ # Persist the state on the order
51
+ after_transition do |order, transition|
52
+ order.state = order.state
53
+ order.state_changes.create(
54
+ previous_state: transition.from,
55
+ next_state: transition.to,
56
+ name: 'order',
57
+ user_id: order.user_id
58
+ )
59
+ order.save
60
+ end
59
61
 
60
- event :return do
61
- transition to: :returned, from: [:complete, :awaiting_return, :canceled], if: :all_inventory_units_returned?
62
- end
62
+ event :cancel do
63
+ transition to: :canceled, if: :allow_cancel?
64
+ end
63
65
 
64
- event :resume do
65
- transition to: :resumed, from: :canceled, if: :canceled?
66
- end
66
+ event :return do
67
+ transition to: :returned,
68
+ from: [:complete, :awaiting_return, :canceled],
69
+ if: :all_inventory_units_returned?
70
+ end
67
71
 
68
- event :authorize_return do
69
- transition to: :awaiting_return
70
- end
72
+ event :resume do
73
+ transition to: :resumed, from: :canceled, if: :canceled?
74
+ end
75
+
76
+ event :authorize_return do
77
+ transition to: :awaiting_return
78
+ end
71
79
 
72
- if states[:payment]
73
- before_transition to: :complete do |order|
74
- if order.payment_required? && order.payments.valid.empty?
75
- order.errors.add(:base, Spree.t(:no_payment_found))
76
- false
77
- elsif order.payment_required?
78
- order.process_payments!
79
- end
80
+ if states[:payment]
81
+ before_transition to: :complete do |order|
82
+ if order.payment_required? && order.payments.valid.empty?
83
+ order.errors.add(:base, Spree.t(:no_payment_found))
84
+ false
85
+ elsif order.payment_required?
86
+ order.process_payments!
80
87
  end
81
- after_transition to: :complete, do: :persist_user_credit_card
82
- before_transition to: :payment, do: :set_shipments_cost
83
- before_transition to: :payment, do: :create_tax_charge!
84
- before_transition to: :payment, do: :assign_default_credit_card
85
88
  end
89
+ after_transition to: :complete, do: :persist_user_credit_card
90
+ before_transition to: :payment, do: :set_shipments_cost
91
+ before_transition to: :payment, do: :create_tax_charge!
92
+ before_transition to: :payment, do: :assign_default_credit_card
93
+ end
86
94
 
87
- before_transition from: :cart, do: :ensure_line_items_present
95
+ before_transition from: :cart, do: :ensure_line_items_present
88
96
 
89
- if states[:address]
90
- before_transition from: :address, do: :create_tax_charge!
91
- before_transition to: :address, do: :assign_default_addresses!
92
- before_transition from: :address, do: :persist_user_address!
93
- end
97
+ if states[:address]
98
+ before_transition from: :address, do: :create_tax_charge!
99
+ before_transition to: :address, do: :assign_default_addresses!
100
+ before_transition from: :address, do: :persist_user_address!
101
+ end
94
102
 
95
- if states[:delivery]
96
- before_transition to: :delivery, do: :create_proposed_shipments
97
- before_transition to: :delivery, do: :ensure_available_shipping_rates
98
- before_transition to: :delivery, do: :set_shipments_cost
99
- before_transition from: :delivery, do: :apply_free_shipping_promotions
100
- end
103
+ if states[:delivery]
104
+ before_transition to: :delivery, do: :create_proposed_shipments
105
+ before_transition to: :delivery, do: :ensure_available_shipping_rates
106
+ before_transition to: :delivery, do: :set_shipments_cost
107
+ before_transition from: :delivery, do: :apply_free_shipping_promotions
108
+ end
101
109
 
102
- before_transition to: :resumed, do: :ensure_line_item_variants_are_not_deleted
103
- before_transition to: :resumed, do: :ensure_line_items_are_in_stock
110
+ before_transition to: :resumed, do: :ensure_line_item_variants_are_not_deleted
111
+ before_transition to: :resumed, do: :ensure_line_items_are_in_stock
104
112
 
105
- before_transition to: :complete, do: :ensure_line_item_variants_are_not_deleted
106
- before_transition to: :complete, do: :ensure_line_items_are_in_stock
113
+ before_transition to: :complete, do: :ensure_line_item_variants_are_not_deleted
114
+ before_transition to: :complete, do: :ensure_line_items_are_in_stock
107
115
 
108
- after_transition to: :complete, do: :finalize!
109
- after_transition to: :resumed, do: :after_resume
110
- after_transition to: :canceled, do: :after_cancel
116
+ after_transition to: :complete, do: :finalize!
117
+ after_transition to: :resumed, do: :after_resume
118
+ after_transition to: :canceled, do: :after_cancel
111
119
 
112
- after_transition from: any - :cart, to: any - [:confirm, :complete] do |order|
113
- order.update_totals
114
- order.persist_totals
115
- end
120
+ after_transition from: any - :cart, to: any - [:confirm, :complete] do |order|
121
+ order.update_totals
122
+ order.persist_totals
116
123
  end
124
+ end
117
125
 
118
- alias_method :save_state, :save
126
+ alias_method :save_state, :save
127
+ end
128
+
129
+ def self.go_to_state(name, options = {})
130
+ self.checkout_steps[name] = options
131
+ previous_states.each do |state|
132
+ add_transition({ from: state, to: name }.merge(options))
133
+ end
134
+ if options[:if]
135
+ previous_states << name
136
+ else
137
+ self.previous_states = [name]
119
138
  end
139
+ end
120
140
 
121
- def self.go_to_state(name, options={})
122
- self.checkout_steps[name] = options
123
- previous_states.each do |state|
124
- add_transition({from: state, to: name}.merge(options))
141
+ def self.insert_checkout_step(name, options = {})
142
+ before = options.delete(:before)
143
+ after = options.delete(:after) unless before
144
+ after = self.checkout_steps.keys.last unless before || after
145
+
146
+ cloned_steps = self.checkout_steps.clone
147
+ cloned_removed_transitions = self.removed_transitions.clone
148
+ checkout_flow do
149
+ cloned_steps.each_pair do |key, value|
150
+ go_to_state(name, options) if key == before
151
+ go_to_state(key, value)
152
+ go_to_state(name, options) if key == after
125
153
  end
126
- if options[:if]
127
- self.previous_states << name
128
- else
129
- self.previous_states = [name]
154
+ cloned_removed_transitions.each do |transition|
155
+ remove_transition(transition)
130
156
  end
131
157
  end
158
+ end
132
159
 
133
- def self.insert_checkout_step(name, options = {})
134
- before = options.delete(:before)
135
- after = options.delete(:after) unless before
136
- after = self.checkout_steps.keys.last unless before || after
137
-
138
- cloned_steps = self.checkout_steps.clone
139
- cloned_removed_transitions = self.removed_transitions.clone
140
- self.checkout_flow do
141
- cloned_steps.each_pair do |key, value|
142
- self.go_to_state(name, options) if key == before
143
- self.go_to_state(key, value)
144
- self.go_to_state(name, options) if key == after
145
- end
146
- cloned_removed_transitions.each do |transition|
147
- self.remove_transition(transition)
148
- end
160
+ def self.remove_checkout_step(name)
161
+ cloned_steps = self.checkout_steps.clone
162
+ cloned_removed_transitions = self.removed_transitions.clone
163
+ checkout_flow do
164
+ cloned_steps.each_pair do |key, value|
165
+ go_to_state(key, value) unless key == name
149
166
  end
150
- end
151
-
152
- def self.remove_checkout_step(name)
153
- cloned_steps = self.checkout_steps.clone
154
- cloned_removed_transitions = self.removed_transitions.clone
155
- self.checkout_flow do
156
- cloned_steps.each_pair do |key, value|
157
- self.go_to_state(key, value) unless key == name
158
- end
159
- cloned_removed_transitions.each do |transition|
160
- self.remove_transition(transition)
161
- end
167
+ cloned_removed_transitions.each do |transition|
168
+ remove_transition(transition)
162
169
  end
163
170
  end
171
+ end
164
172
 
165
- def self.remove_transition(options={})
166
- self.removed_transitions << options
167
- self.next_event_transitions.delete(find_transition(options))
168
- end
169
-
170
- def self.find_transition(options={})
171
- return nil if options.nil? || !options.include?(:from) || !options.include?(:to)
172
- self.next_event_transitions.detect do |transition|
173
- transition[options[:from].to_sym] == options[:to].to_sym
174
- end
175
- end
173
+ def self.remove_transition(options = {})
174
+ self.removed_transitions << options
175
+ self.next_event_transitions.delete(find_transition(options))
176
+ end
176
177
 
177
- def self.next_event_transitions
178
- @next_event_transitions ||= []
178
+ def self.find_transition(options = {})
179
+ return nil if options.nil? || !options.include?(:from) || !options.include?(:to)
180
+ self.next_event_transitions.detect do |transition|
181
+ transition[options[:from].to_sym] == options[:to].to_sym
179
182
  end
183
+ end
180
184
 
181
- def self.checkout_steps
182
- @checkout_steps ||= {}
183
- end
185
+ def self.checkout_step_names
186
+ self.checkout_steps.keys
187
+ end
184
188
 
185
- def self.checkout_step_names
186
- self.checkout_steps.keys
187
- end
189
+ def self.add_transition(options)
190
+ self.next_event_transitions << { options.delete(:from) => options.delete(:to) }.merge(options)
191
+ end
188
192
 
189
- def self.add_transition(options)
190
- self.next_event_transitions << { options.delete(:from) => options.delete(:to) }.merge(options)
191
- end
193
+ def checkout_steps
194
+ steps = (self.class.checkout_steps.each_with_object([]) do |(step, options), checkout_steps|
195
+ next if options.include?(:if) && !options[:if].call(self)
196
+ checkout_steps << step
197
+ end).map(&:to_s)
198
+ # Ensure there is always a complete step
199
+ steps << "complete" unless steps.include?("complete")
200
+ steps
201
+ end
192
202
 
193
- def checkout_steps
194
- steps = self.class.checkout_steps.each_with_object([]) { |(step, options), checkout_steps|
195
- next if options.include?(:if) && !options[:if].call(self)
196
- checkout_steps << step
197
- }.map(&:to_s)
198
- # Ensure there is always a complete step
199
- steps << "complete" unless steps.include?("complete")
200
- steps
201
- end
203
+ def has_checkout_step?(step)
204
+ step.present? && self.checkout_steps.include?(step)
205
+ end
202
206
 
203
- def has_checkout_step?(step)
204
- step.present? && self.checkout_steps.include?(step)
205
- end
207
+ def passed_checkout_step?(step)
208
+ has_checkout_step?(step) && checkout_step_index(step) < checkout_step_index(state)
209
+ end
206
210
 
207
- def passed_checkout_step?(step)
208
- has_checkout_step?(step) && checkout_step_index(step) < checkout_step_index(self.state)
209
- end
211
+ def checkout_step_index(step)
212
+ self.checkout_steps.index(step).to_i
213
+ end
210
214
 
211
- def checkout_step_index(step)
212
- self.checkout_steps.index(step).to_i
213
- end
215
+ def can_go_to_state?(state)
216
+ return false unless has_checkout_step?(self.state) && has_checkout_step?(state)
217
+ checkout_step_index(state) > checkout_step_index(self.state)
218
+ end
214
219
 
215
- def self.removed_transitions
216
- @removed_transitions ||= []
217
- end
220
+ define_callbacks :updating_from_params, terminator: ->(_target, result) { result == false }
218
221
 
219
- def can_go_to_state?(state)
220
- return false unless has_checkout_step?(self.state) && has_checkout_step?(state)
221
- checkout_step_index(state) > checkout_step_index(self.state)
222
- end
222
+ set_callback :updating_from_params, :before, :update_params_payment_source
223
223
 
224
- define_callbacks :updating_from_params, terminator: ->(target, result) { result == false }
224
+ def update_from_params(params, permitted_params, request_env = {})
225
+ success = false
226
+ @updating_params = params
227
+ run_callbacks :updating_from_params do
228
+ # Set existing card after setting permitted parameters because
229
+ # rails would slice parameters containg ruby objects, apparently
230
+ existing_card_id = @updating_params[:order] ? @updating_params[:order].delete(:existing_card) : nil
225
231
 
226
- set_callback :updating_from_params, :before, :update_params_payment_source
232
+ attributes = @updating_params[:order] ? @updating_params[:order].permit(permitted_params).delete_if { |_k, v| v.nil? } : {}
227
233
 
228
- def update_from_params(params, permitted_params, request_env = {})
229
- success = false
230
- @updating_params = params
231
- run_callbacks :updating_from_params do
232
- # Set existing card after setting permitted parameters because
233
- # rails would slice parameters containg ruby objects, apparently
234
- existing_card_id = @updating_params[:order] ? @updating_params[:order].delete(:existing_card) : nil
234
+ if existing_card_id.present?
235
+ credit_card = CreditCard.find existing_card_id
236
+ if credit_card.user_id != user_id || credit_card.user_id.blank?
237
+ raise Core::GatewayError.new Spree.t(:invalid_credit_card)
238
+ end
235
239
 
236
- attributes = @updating_params[:order] ? @updating_params[:order].permit(permitted_params).delete_if { |k,v| v.nil? } : {}
240
+ credit_card.verification_value = params[:cvc_confirm] if params[:cvc_confirm].present?
237
241
 
238
- if existing_card_id.present?
239
- credit_card = CreditCard.find existing_card_id
240
- if credit_card.user_id != self.user_id || credit_card.user_id.blank?
241
- raise Core::GatewayError.new Spree.t(:invalid_credit_card)
242
- end
242
+ attributes[:payments_attributes].first[:source] = credit_card
243
+ attributes[:payments_attributes].first[:payment_method_id] = credit_card.payment_method_id
244
+ attributes[:payments_attributes].first.delete :source_attributes
245
+ end
243
246
 
244
- credit_card.verification_value = params[:cvc_confirm] if params[:cvc_confirm].present?
247
+ if attributes[:payments_attributes]
248
+ attributes[:payments_attributes].first[:request_env] = request_env
249
+ end
245
250
 
246
- attributes[:payments_attributes].first[:source] = credit_card
247
- attributes[:payments_attributes].first[:payment_method_id] = credit_card.payment_method_id
248
- attributes[:payments_attributes].first.delete :source_attributes
249
- end
251
+ success = update_attributes(attributes)
252
+ set_shipments_cost if shipments.any?
253
+ end
250
254
 
251
- if attributes[:payments_attributes]
252
- attributes[:payments_attributes].first[:request_env] = request_env
253
- end
255
+ @updating_params = nil
256
+ success
257
+ end
254
258
 
255
- success = self.update_attributes(attributes)
256
- set_shipments_cost if self.shipments.any?
257
- end
259
+ def assign_default_addresses!
260
+ if user
261
+ clone_billing
262
+ # Skip setting ship address if order doesn't have a delivery checkout step
263
+ # to avoid triggering validations on shipping address
264
+ clone_shipping if checkout_steps.include?("delivery")
265
+ end
266
+ end
258
267
 
259
- @updating_params = nil
260
- success
268
+ def clone_billing
269
+ if !bill_address_id && user.bill_address.try(:valid?)
270
+ self.bill_address = user.bill_address.try(:clone)
261
271
  end
272
+ end
262
273
 
263
- def assign_default_addresses!
264
- if self.user
265
- self.bill_address = user.bill_address.try(:clone) if !self.bill_address_id && user.bill_address.try(:valid?)
266
- # Skip setting ship address if order doesn't have a delivery checkout step
267
- # to avoid triggering validations on shipping address
268
- self.ship_address = user.ship_address.try(:clone) if !self.ship_address_id && user.ship_address.try(:valid?) && self.checkout_steps.include?("delivery")
269
- end
274
+ def clone_shipping
275
+ if !ship_address_id && user.ship_address.try(:valid?)
276
+ self.ship_address = user.ship_address.try(:clone)
270
277
  end
278
+ end
271
279
 
272
- def persist_user_address!
273
- if !self.temporary_address && self.user && self.user.respond_to?(:persist_order_address) && self.bill_address_id
274
- self.user.persist_order_address(self)
275
- end
280
+ def persist_user_address!
281
+ if !temporary_address && user && user.respond_to?(:persist_order_address) && bill_address_id
282
+ user.persist_order_address(self)
276
283
  end
284
+ end
277
285
 
278
- def persist_user_credit_card
279
- if !self.temporary_credit_card && self.user_id && self.valid_credit_cards.present?
280
- default_cc = self.valid_credit_cards.first
281
- default_cc.user_id = self.user_id
282
- default_cc.default = true
283
- default_cc.save
284
- end
286
+ def persist_user_credit_card
287
+ if !temporary_credit_card && user_id && valid_credit_cards.present?
288
+ default_cc = valid_credit_cards.first
289
+ default_cc.user_id = user_id
290
+ default_cc.default = true
291
+ default_cc.save
285
292
  end
293
+ end
286
294
 
287
- def assign_default_credit_card
288
- if self.payments.from_credit_card.count == 0 && self.user && self.user.default_credit_card.try(:valid?)
289
- cc = self.user.default_credit_card
290
- self.payments.create!(payment_method_id: cc.payment_method_id, source: cc)
291
- end
295
+ def assign_default_credit_card
296
+ if payments.from_credit_card.count == 0 && user_has_valid_default_card? && payment_required?
297
+ cc = user.default_credit_card
298
+ payments.create!(payment_method_id: cc.payment_method_id, source: cc, amount: total)
292
299
  end
300
+ end
293
301
 
294
- private
295
- # For payment step, filter order parameters to produce the expected nested
296
- # attributes for a single payment and its source, discarding attributes
297
- # for payment methods other than the one selected
298
- #
299
- # In case a existing credit card is provided it needs to build the payment
300
- # attributes from scratch so we can set the amount. example payload:
301
- #
302
- # {
303
- # "order": {
304
- # "existing_card": "2"
305
- # }
306
- # }
307
- #
308
- def update_params_payment_source
309
- if @updating_params[:payment_source].present?
310
- source_params = @updating_params.delete(:payment_source)[@updating_params[:order][:payments_attributes].first[:payment_method_id].to_s]
302
+ def user_has_valid_default_card?
303
+ user && user.default_credit_card.try(:valid?)
304
+ end
311
305
 
312
- if source_params
313
- @updating_params[:order][:payments_attributes].first[:source_attributes] = source_params
314
- end
306
+ private
307
+
308
+ # For payment step, filter order parameters to produce the expected nested
309
+ # attributes for a single payment and its source, discarding attributes
310
+ # for payment methods other than the one selected
311
+ #
312
+ # In case a existing credit card is provided it needs to build the payment
313
+ # attributes from scratch so we can set the amount. example payload:
314
+ #
315
+ # {
316
+ # "order": {
317
+ # "existing_card": "2"
318
+ # }
319
+ # }
320
+ #
321
+ def update_params_payment_source
322
+ if @updating_params[:payment_source].present?
323
+ source_params = @updating_params.
324
+ delete(:payment_source)[@updating_params[:order][:payments_attributes].
325
+ first[:payment_method_id].to_s]
326
+
327
+ if source_params
328
+ @updating_params[:order][:payments_attributes].first[:source_attributes] = source_params
315
329
  end
330
+ end
316
331
 
317
- if @updating_params[:order] && (@updating_params[:order][:payments_attributes] || @updating_params[:order][:existing_card])
318
- @updating_params[:order][:payments_attributes] ||= [{}]
319
- @updating_params[:order][:payments_attributes].first[:amount] = self.total
320
- end
332
+ if @updating_params[:order] && (@updating_params[:order][:payments_attributes] ||
333
+ @updating_params[:order][:existing_card])
334
+ @updating_params[:order][:payments_attributes] ||= [{}]
335
+ @updating_params[:order][:payments_attributes].first[:amount] = total
321
336
  end
322
337
  end
323
338
  end