spree_core 3.0.1 → 3.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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