effective_orders 2.2.4 → 3.0.0

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 (150) hide show
  1. checksums.yaml +4 -4
  2. data/MIT-LICENSE +1 -1
  3. data/README.md +124 -84
  4. data/app/assets/javascripts/effective_orders/customers.js.coffee +39 -0
  5. data/app/assets/javascripts/effective_orders/providers/{stripe_charges.js.coffee → stripe.js.coffee} +15 -13
  6. data/app/assets/javascripts/effective_orders/subscriptions.js.coffee +73 -0
  7. data/app/assets/stylesheets/effective_orders.scss +2 -1
  8. data/app/assets/stylesheets/effective_orders/_order.scss +16 -8
  9. data/app/assets/stylesheets/effective_orders/_subscriptions.scss +14 -0
  10. data/app/controllers/admin/customers_controller.rb +11 -8
  11. data/app/controllers/admin/order_items_controller.rb +4 -8
  12. data/app/controllers/admin/orders_controller.rb +133 -87
  13. data/app/controllers/effective/carts_controller.rb +18 -8
  14. data/app/controllers/effective/concerns/purchase.rb +39 -0
  15. data/app/controllers/effective/customers_controller.rb +43 -0
  16. data/app/controllers/effective/orders_controller.rb +73 -119
  17. data/app/controllers/effective/providers/app_checkout.rb +3 -1
  18. data/app/controllers/effective/providers/ccbill.rb +4 -6
  19. data/app/controllers/effective/providers/cheque.rb +20 -11
  20. data/app/controllers/effective/providers/free.rb +33 -0
  21. data/app/controllers/effective/providers/mark_as_paid.rb +33 -0
  22. data/app/controllers/effective/providers/moneris.rb +9 -17
  23. data/app/controllers/effective/providers/paypal.rb +4 -6
  24. data/app/controllers/effective/providers/pretend.rb +4 -4
  25. data/app/controllers/effective/providers/refund.rb +39 -0
  26. data/app/controllers/effective/providers/stripe.rb +19 -40
  27. data/app/controllers/effective/providers/stripe_connect.rb +2 -6
  28. data/app/controllers/effective/webhooks_controller.rb +44 -95
  29. data/app/datatables/effective_customers_datatable.rb +21 -29
  30. data/app/datatables/effective_order_items_datatable.rb +77 -79
  31. data/app/datatables/effective_orders_datatable.rb +67 -57
  32. data/app/helpers/effective_carts_helper.rb +17 -14
  33. data/app/helpers/effective_orders_helper.rb +40 -56
  34. data/app/helpers/effective_paypal_helper.rb +3 -3
  35. data/app/helpers/effective_stripe_helper.rb +47 -18
  36. data/app/helpers/effective_subscriptions_helper.rb +79 -0
  37. data/app/mailers/effective/orders_mailer.rb +125 -2
  38. data/app/models/concerns/acts_as_purchasable.rb +23 -33
  39. data/app/models/concerns/acts_as_subscribable.rb +68 -0
  40. data/app/models/concerns/acts_as_subscribable_buyer.rb +22 -0
  41. data/app/models/effective/cart.rb +53 -24
  42. data/app/models/effective/cart_item.rb +6 -12
  43. data/app/models/effective/customer.rb +51 -54
  44. data/app/models/effective/order.rb +160 -147
  45. data/app/models/effective/order_item.rb +18 -21
  46. data/app/models/effective/product.rb +7 -7
  47. data/app/models/effective/providers/ccbill_postback.rb +1 -1
  48. data/app/models/effective/providers/stripe_charge.rb +8 -19
  49. data/app/models/effective/subscripter.rb +230 -0
  50. data/app/models/effective/subscription.rb +27 -76
  51. data/app/models/effective/tax_rate_calculator.rb +10 -7
  52. data/app/views/admin/customers/_actions.html.haml +1 -2
  53. data/app/views/admin/customers/index.html.haml +1 -1
  54. data/app/views/admin/customers/show.html.haml +6 -0
  55. data/app/views/admin/orders/_actions.html.haml +9 -7
  56. data/app/views/admin/orders/_form.html.haml +11 -7
  57. data/app/views/admin/orders/_order_actions.html.haml +2 -1
  58. data/app/views/admin/orders/_order_item_fields.html.haml +1 -1
  59. data/app/views/admin/orders/edit.html.haml +4 -0
  60. data/app/views/admin/orders/index.html.haml +1 -4
  61. data/app/views/admin/orders/new.html.haml +1 -1
  62. data/app/views/admin/orders/show.html.haml +5 -6
  63. data/app/views/effective/carts/_cart.html.haml +2 -2
  64. data/app/views/effective/carts/show.html.haml +2 -2
  65. data/app/views/effective/customers/_customer.html.haml +152 -0
  66. data/app/views/effective/customers/_fields.html.haml +12 -0
  67. data/app/views/effective/customers/_form.html.haml +13 -0
  68. data/app/views/effective/customers/edit.html.haml +3 -0
  69. data/app/views/effective/orders/_checkout_step1.html.haml +8 -15
  70. data/app/views/effective/orders/_checkout_step2.html.haml +34 -21
  71. data/app/views/effective/orders/_order.html.haml +8 -9
  72. data/app/views/effective/orders/_order_actions.html.haml +7 -8
  73. data/app/views/effective/orders/_order_header.html.haml +1 -1
  74. data/app/views/effective/orders/_order_items.html.haml +11 -5
  75. data/app/views/effective/orders/_order_note.html.haml +4 -7
  76. data/app/views/effective/orders/_orders_table.html.haml +26 -26
  77. data/app/views/effective/orders/app_checkout/_form.html.haml +2 -2
  78. data/app/views/effective/orders/ccbill/_form.html.haml +1 -1
  79. data/app/views/effective/orders/cheque/_form.html.haml +3 -1
  80. data/app/views/effective/orders/declined.html.haml +1 -1
  81. data/app/views/effective/orders/{checkout_step1.html.haml → edit.html.haml} +0 -0
  82. data/app/views/effective/orders/free/_form.html.haml +4 -0
  83. data/app/views/effective/orders/index.html.haml +2 -4
  84. data/app/views/effective/orders/mark_as_paid/_form.html.haml +32 -0
  85. data/app/views/effective/orders/moneris/_form.html.haml +6 -6
  86. data/app/views/effective/orders/{checkout_step2.html.haml → new.html.haml} +1 -1
  87. data/app/views/effective/orders/paypal/_form.html.haml +2 -2
  88. data/app/views/effective/orders/pretend/_form.html.haml +2 -2
  89. data/app/views/effective/orders/purchased.html.haml +3 -0
  90. data/app/views/effective/orders/refund/_form.html.haml +32 -0
  91. data/app/views/effective/orders/show.html.haml +4 -1
  92. data/app/views/effective/orders/stripe/_form.html.haml +5 -5
  93. data/app/views/effective/orders_mailer/subscription_canceled.html.haml +9 -0
  94. data/app/views/effective/orders_mailer/subscription_payment_failed.html.haml +9 -0
  95. data/app/views/effective/orders_mailer/subscription_payment_succeeded.html.haml +9 -0
  96. data/app/views/effective/orders_mailer/subscription_trial_expired.html.haml +5 -0
  97. data/app/views/effective/orders_mailer/subscription_trial_expiring.html.haml +7 -0
  98. data/app/views/effective/subscriptions/_fields.html.haml +16 -0
  99. data/app/views/effective/subscriptions/_plan.html.haml +21 -0
  100. data/app/views/layouts/effective_orders_mailer_layout.html.haml +6 -8
  101. data/config/effective_orders.rb +41 -20
  102. data/config/routes.rb +48 -48
  103. data/db/migrate/01_create_effective_orders.rb.erb +19 -5
  104. data/lib/effective_orders.rb +78 -42
  105. data/lib/effective_orders/engine.rb +36 -82
  106. data/lib/effective_orders/version.rb +1 -1
  107. data/lib/generators/effective_orders/install_generator.rb +2 -2
  108. data/lib/generators/templates/effective_orders_mailer_preview.rb +39 -4
  109. data/lib/tasks/effective_orders_tasks.rake +42 -0
  110. data/spec/controllers/carts_controller_spec.rb +1 -1
  111. data/spec/controllers/moneris_orders_controller_spec.rb +4 -4
  112. data/spec/controllers/orders_controller_spec.rb +4 -4
  113. data/spec/controllers/stripe_orders_controller_spec.rb +2 -2
  114. data/spec/controllers/webhooks_controller_spec.rb +1 -1
  115. data/spec/dummy/config/initializers/effective_orders.rb +1 -7
  116. data/spec/dummy/db/schema.rb +1 -0
  117. data/spec/dummy/db/test.sqlite3 +0 -0
  118. data/spec/dummy/log/test.log +3 -0
  119. data/spec/models/acts_as_purchasable_spec.rb +0 -56
  120. data/spec/models/customer_spec.rb +3 -3
  121. data/spec/models/order_spec.rb +2 -2
  122. data/spec/spec_helper.rb +1 -1
  123. data/spec/support/factories.rb +2 -1
  124. metadata +37 -49
  125. data/active_admin/effective_carts.rb +0 -14
  126. data/active_admin/effective_orders.rb +0 -112
  127. data/app/assets/javascripts/effective_orders/providers/stripe_subscriptions.js.coffee +0 -28
  128. data/app/controllers/concerns/acts_as_active_admin_controller.rb +0 -69
  129. data/app/controllers/effective/subscriptions_controller.rb +0 -126
  130. data/app/models/effective/datatables/customers.rb +0 -40
  131. data/app/models/effective/datatables/order_items.rb +0 -101
  132. data/app/models/effective/datatables/orders.rb +0 -91
  133. data/app/models/inputs/price_field.rb +0 -63
  134. data/app/models/inputs/price_form_input.rb +0 -7
  135. data/app/models/inputs/price_formtastic_input.rb +0 -9
  136. data/app/models/inputs/price_input.rb +0 -19
  137. data/app/models/inputs/price_simple_form_input.rb +0 -8
  138. data/app/views/admin/orders/_form_mark_as_paid.html.haml +0 -33
  139. data/app/views/admin/orders/_order_payment_details.html.haml +0 -5
  140. data/app/views/admin/orders/mark_as_paid.html.haml +0 -7
  141. data/app/views/effective/orders/stripe/_subscription_fields.html.haml +0 -7
  142. data/app/views/effective/subscriptions/index.html.haml +0 -22
  143. data/app/views/effective/subscriptions/new.html.haml +0 -9
  144. data/app/views/effective/subscriptions/show.html.haml +0 -49
  145. data/db/upgrade/02_upgrade_effective_orders_from03x.rb.erb +0 -29
  146. data/db/upgrade/03_upgrade_effective_orders_from1x.rb.erb +0 -98
  147. data/db/upgrade/upgrade_price_column_on_table.rb.erb +0 -17
  148. data/lib/generators/effective_orders/upgrade_from03x_generator.rb +0 -31
  149. data/lib/generators/effective_orders/upgrade_from1x_generator.rb +0 -27
  150. data/lib/generators/effective_orders/upgrade_price_column_generator.rb +0 -30
@@ -7,90 +7,88 @@ module Effective
7
7
  end
8
8
 
9
9
  acts_as_addressable(
10
- :billing => { singular: true, use_full_name: EffectiveOrders.use_address_full_name },
11
- :shipping => { singular: true, use_full_name: EffectiveOrders.use_address_full_name }
10
+ billing: { singular: true, use_full_name: EffectiveOrders.use_address_full_name },
11
+ shipping: { singular: true, use_full_name: EffectiveOrders.use_address_full_name }
12
12
  )
13
13
 
14
- attr_accessor :save_billing_address, :save_shipping_address # save these addresses to the user if selected
15
14
  attr_accessor :terms_and_conditions # Yes, I agree to the terms and conditions
16
15
 
17
16
  # Settings in the /admin action forms
18
- attr_accessor :send_payment_request_to_buyer # Used by the /admin/orders/new form. Should the payment request email be sent after creating an order?
19
- attr_accessor :send_mark_as_paid_email_to_buyer # Used by the /admin/orders/mark_as_paid action
20
- attr_accessor :skip_buyer_validations # Enabled by the /admin/orders/create action
17
+ attr_accessor :send_payment_request_to_buyer # Set by Admin::Orders#new. Should the payment request email be sent after creating an order?
18
+ attr_accessor :send_mark_as_paid_email_to_buyer # Set by Admin::Orders#mark_as_paid
19
+ attr_accessor :skip_buyer_validations # Set by Admin::Orders#create
20
+ attr_accessor :skip_minimum_charge_validation # Set by Admin::Orders#show
21
21
 
22
- belongs_to :user, validate: false # This is the user who purchased the order. We validate it below.
23
- has_many :order_items, inverse_of: :order
22
+ belongs_to :user, validate: false # This is the buyer/user of the order. We validate it below.
23
+ has_many :order_items, -> { order(:id) }, inverse_of: :order, class_name: 'Effective::OrderItem'
24
24
 
25
- # structure do
26
- # purchase_state :string
27
- # purchased_at :datetime
25
+ accepts_nested_attributes_for :order_items, allow_destroy: false, reject_if: :all_blank
26
+ accepts_nested_attributes_for :user, allow_destroy: false, update_only: true
27
+
28
+ # Attributes
29
+ # purchase_state :string
30
+ # purchased_at :datetime
28
31
  #
29
- # note :text
32
+ # note_to_buyer :text
33
+ # note_internal :text
30
34
  #
31
- # payment :text # serialized hash containing all the payment details. see below.
35
+ # payment :text # serialized hash containing all the payment details. see below.
32
36
  #
33
- # payment_provider :string
34
- # payment_card :string
37
+ # payment_provider :string
38
+ # payment_card :string
35
39
  #
36
- # tax_rate :decimal, precision: 6, scale: 3
40
+ # tax_rate :decimal, precision: 6, scale: 3
37
41
  #
38
- # subtotal :integer
39
- # tax :integer
40
- # total :integer
42
+ # subtotal :integer
43
+ # tax :integer
44
+ # total :integer
41
45
  #
42
- # timestamps
43
- # end
46
+ # timestamps
44
47
 
45
- accepts_nested_attributes_for :order_items, allow_destroy: false, reject_if: :all_blank
46
- accepts_nested_attributes_for :user, allow_destroy: false, update_only: true
48
+ serialize :payment, Hash
47
49
 
48
50
  before_validation { assign_totals! }
49
- before_save { assign_totals! unless self[:total].present? } # Incase we save!(validate: false)
50
-
51
- unless EffectiveOrders.skip_user_validation
52
- validates :user_id, presence: true, unless: Proc.new { |order| order.skip_buyer_validations? }
53
- validates :user, associated: true, unless: Proc.new { |order| order.skip_buyer_validations? }
54
- end
55
-
56
- if EffectiveOrders.collect_note_required
57
- validates :note, presence: true, unless: Proc.new { |order| order.skip_buyer_validations? }
58
- end
59
51
 
60
- validates :tax_rate, presence: { message: "can't be determined based on billing address" }, unless: Proc.new { |order| order.skip_buyer_validations? }
61
- validates :tax, presence: true, unless: Proc.new { |order| order.skip_buyer_validations? }
52
+ # Order validations
53
+ validates :order_items, presence: { message: 'No items are present. Please add additional items.' }
54
+ validates :purchase_state, inclusion: { in: EffectiveOrders::PURCHASE_STATES.keys }
55
+ validates :subtotal, presence: true
62
56
 
63
- if EffectiveOrders.require_billing_address # An admin creating a new pending order should not be required to have addresses
64
- validates :billing_address, presence: true, unless: Proc.new { |order| (order.new_record? && order.pending?) || order.skip_buyer_validations? }
57
+ if EffectiveOrders.minimum_charge.to_i > 0
58
+ validates :total, presence: true, numericality: {
59
+ greater_than_or_equal_to: EffectiveOrders.minimum_charge.to_i,
60
+ message: "must be $#{'%0.2f' % (EffectiveOrders.minimum_charge.to_i / 100.0)} or more. Please add additional items."
61
+ }, unless: -> {
62
+ (total == 0 && EffectiveOrders.allow_free_orders) ||
63
+ (total < 0 && EffectiveOrders.allow_refunds && skip_minimum_charge_validation?)
64
+ }
65
65
  end
66
66
 
67
- if EffectiveOrders.require_shipping_address # An admin creating a new pending order should not be required to have addresses
68
- validates :shipping_address, presence: true, unless: Proc.new { |order| (order.new_record? && order.pending?) || order.skip_buyer_validations? }
69
- end
67
+ # User validations -- An admin skips these when working in the admin/ namespace
68
+ with_options unless: -> { skip_buyer_validations? } do |order|
69
+ order.validates :tax_rate, presence: { message: "can't be determined based on billing address" }
70
+ order.validates :tax, presence: true
70
71
 
71
- if ((minimum_charge = EffectiveOrders.minimum_charge.to_i) rescue nil).present?
72
- if EffectiveOrders.allow_free_orders
73
- validates :total, numericality: {
74
- greater_than_or_equal_to: minimum_charge,
75
- message: "A minimum order of #{EffectiveOrders.minimum_charge} is required. Please add additional items to your cart."
76
- }, unless: Proc.new { |order| order.total == 0 }
77
- else
78
- validates :total, numericality: {
79
- greater_than_or_equal_to: minimum_charge,
80
- message: "A minimum order of #{EffectiveOrders.minimum_charge} is required. Please add additional items to your cart."
81
- }
72
+ unless EffectiveOrders.skip_user_validation
73
+ order.validates :user_id, presence: true
74
+ order.validates :user, associated: true
82
75
  end
83
- end
84
76
 
85
- validates :purchase_state, inclusion: { in: [nil, EffectiveOrders::PURCHASED, EffectiveOrders::DECLINED, EffectiveOrders::PENDING] }
77
+ if EffectiveOrders.require_billing_address
78
+ order.validates :billing_address, presence: true
79
+ end
86
80
 
87
- validates :subtotal, presence: true
88
- validates :total, presence: true
81
+ if EffectiveOrders.require_shipping_address
82
+ order.validates :shipping_address, presence: true
83
+ end
89
84
 
90
- validates :order_items, presence: { message: 'No items are present. Please add one or more item to your cart.' }
91
- validates :order_items, associated: true
85
+ if EffectiveOrders.collect_note_required
86
+ order.validates :note, presence: true
87
+ end
88
+ end
92
89
 
93
- with_options if: Proc.new { |order| order.purchased? } do |order|
90
+ # When Purchased
91
+ with_options if: -> { purchased? } do |order|
94
92
  order.validates :purchased_at, presence: true
95
93
  order.validates :payment, presence: true
96
94
 
@@ -98,14 +96,17 @@ module Effective
98
96
  order.validates :payment_card, presence: true
99
97
  end
100
98
 
101
- serialize :payment, Hash
99
+ before_save { assign_totals! unless self[:total].present? } # Incase we save!(validate: false)
100
+ before_save :save_addresses
102
101
 
103
- default_scope -> { includes(:user).includes(order_items: :purchasable).order(created_at: :desc) }
102
+ scope :deep, -> { includes(:user).includes(order_items: :purchasable) }
103
+ scope :sorted, -> { order(created_at: :desc) }
104
104
 
105
- scope :purchased, -> { where(purchase_state: EffectiveOrders::PURCHASED) }
105
+ scope :purchased, -> { sorted.where(purchase_state: EffectiveOrders::PURCHASED) }
106
106
  scope :purchased_by, lambda { |user| purchased.where(user_id: user.try(:id)) }
107
107
  scope :declined, -> { where(purchase_state: EffectiveOrders::DECLINED) }
108
108
  scope :pending, -> { where(purchase_state: EffectiveOrders::PENDING) }
109
+
109
110
  scope :for_users, -> (users) { # Expects a Users relation, an Array of ids, or Array of users
110
111
  users = users.kind_of?(::ActiveRecord::Relation) ? users.pluck(:id) : Array(users)
111
112
  where(user_id: (users.first.kind_of?(Integer) ? users : users.map { |user| user.id }))
@@ -123,21 +124,12 @@ module Effective
123
124
  def initialize(*items, user: nil, billing_address: nil, shipping_address: nil)
124
125
  super() # Call super with no arguments
125
126
 
126
- # Set up defaults
127
- self.save_billing_address = true
128
- self.save_shipping_address = true
129
-
127
+ # Assign user
130
128
  self.user = user || (items.first.user if items.first.kind_of?(Effective::Cart))
131
129
 
132
- if billing_address
133
- self.billing_address = billing_address
134
- self.billing_address.full_name ||= billing_name
135
- end
136
-
137
- if shipping_address
138
- self.shipping_address = shipping_address
139
- self.shipping_address.full_name ||= billing_name
140
- end
130
+ # Assign addresses
131
+ self.billing_address = billing_address if billing_address
132
+ self.shipping_address = shipping_address if shipping_address
141
133
 
142
134
  add(items) if items.present?
143
135
  end
@@ -152,11 +144,20 @@ module Effective
152
144
  if item.kind_of?(Effective::Cart)
153
145
  item.cart_items.to_a
154
146
  elsif item.kind_of?(ActsAsPurchasable)
155
- Effective::CartItem.new(quantity: quantity.to_i).tap { |cart_item| cart_item.purchasable = item }
147
+ Effective::CartItem.new(quantity: quantity, purchasable: item)
148
+ elsif item.kind_of?(Effective::Order)
149
+ # Duplicate an existing order
150
+ self.note_to_buyer ||= item.note_to_buyer
151
+ self.note_internal ||= item.note_internal
152
+
153
+ item.order_items.select { |oi| oi.purchasable.kind_of?(Effective::Product) }.map do |oi|
154
+ product = Effective::Product.new(title: oi.purchasable.title, price: oi.purchasable.price, tax_exempt: oi.purchasable.tax_exempt)
155
+ Effective::CartItem.new(quantity: oi.quantity, purchasable: product)
156
+ end
156
157
  else
157
- raise ArgumentError.new('Effective::Order.add() expects one or more acts_as_purchasable objects, or an Effective::Cart')
158
+ raise 'add() expects one or more acts_as_purchasable objects, or an Effective::Cart'
158
159
  end
159
- end
160
+ end.compact
160
161
 
161
162
  # Make sure to reset stored aggregates
162
163
  self.total = nil
@@ -175,13 +176,12 @@ module Effective
175
176
 
176
177
  retval.size == 1 ? retval.first : retval
177
178
  end
178
- alias_method :add_to_order, :add
179
179
 
180
180
  def user=(user)
181
- return if user.nil?
182
-
183
181
  super
184
182
 
183
+ return unless user.present?
184
+
185
185
  # Copy user addresses into this order if they are present
186
186
  if user.respond_to?(:billing_address) && user.billing_address.present?
187
187
  self.billing_address = user.billing_address
@@ -191,64 +191,47 @@ module Effective
191
191
  self.shipping_address = user.shipping_address
192
192
  end
193
193
 
194
- # If our addresses are required, make sure they exist
195
- if EffectiveOrders.require_billing_address
196
- self.billing_address ||= Effective::Address.new()
197
- end
198
-
199
- if EffectiveOrders.require_shipping_address
200
- self.shipping_address ||= Effective::Address.new()
201
- end
202
-
203
- # Ensure the Full Name is assigned when an address exists
204
- if billing_address.present? && billing_address.full_name.blank?
205
- self.billing_address.full_name = billing_name
206
- end
207
-
208
- if shipping_address.present? && shipping_address.full_name.blank?
209
- self.shipping_address.full_name = billing_name
210
- end
194
+ user
211
195
  end
212
196
 
213
197
  # This is called from admin/orders#create
214
198
  # This is intended for use as an admin action only
215
199
  # It skips any address or bad user validations
216
- def create_as_pending
200
+ def create_as_pending!
201
+ return false unless new_record?
202
+
217
203
  self.purchase_state = EffectiveOrders::PENDING
218
204
 
219
205
  self.skip_buyer_validations = true
220
206
  self.addresses.clear if addresses.any? { |address| address.valid? == false }
221
207
 
222
- return false unless save
208
+ save!
223
209
 
224
210
  send_payment_request_to_buyer! if send_payment_request_to_buyer?
211
+
225
212
  true
226
213
  end
227
214
 
228
- # This is used for updating Subscription codes.
229
- # We want to update the underlying purchasable object of an OrderItem
230
- # Passing the order_item_attributes using rails default acts_as_nested creates a new object instead of updating the temporary one.
231
- # So we override this method to do the updates on the non-persisted OrderItem objects
232
- # Right now strong_paramaters only lets through stripe_coupon_id
233
- # {"0"=>{"class"=>"Effective::Subscription", "stripe_coupon_id"=>"50OFF", "id"=>"2"}}}
234
- def order_items_attributes=(order_item_attributes)
235
- if self.persisted? == false
236
- (order_item_attributes || {}).each do |_, atts|
237
- order_item = self.order_items.find { |oi| oi.purchasable.class.name == atts[:class] && oi.purchasable.id == atts[:id].to_i }
238
-
239
- if order_item
240
- order_item.purchasable.attributes = atts.except(:id, :class)
241
-
242
- # Recalculate the OrderItem based on the updated purchasable object
243
- order_item.title = order_item.purchasable.title
244
- order_item.price = order_item.purchasable.price
245
- order_item.tax_exempt = order_item.purchasable.tax_exempt
246
- order_item.seller_id = (order_item.purchasable.try(:seller).try(:id) rescue nil)
247
- end
248
- end
215
+ def to_s
216
+ if refund?
217
+ "Refund ##{to_param}"
218
+ elsif purchased?
219
+ "Receipt ##{to_param}"
220
+ elsif pending?
221
+ "Pending Order ##{to_param}"
222
+ else
223
+ "Order ##{to_param}"
249
224
  end
250
225
  end
251
226
 
227
+ def free?
228
+ total == 0
229
+ end
230
+
231
+ def refund?
232
+ total.to_i < 0
233
+ end
234
+
252
235
  def purchasables
253
236
  order_items.map { |order_item| order_item.purchasable }
254
237
  end
@@ -266,31 +249,27 @@ module Effective
266
249
  end
267
250
 
268
251
  def total
269
- self[:total] || [(subtotal + tax.to_i), 0].max
252
+ (self[:total] || (subtotal + tax.to_i)).to_i
270
253
  end
271
254
 
272
255
  def num_items
273
256
  order_items.map { |oi| oi.quantity }.sum
274
257
  end
275
258
 
276
- def save_billing_address?
277
- truthy?(self.save_billing_address)
278
- end
279
-
280
- def save_shipping_address?
281
- truthy?(self.save_shipping_address)
282
- end
283
-
284
259
  def send_payment_request_to_buyer?
285
- truthy?(self.send_payment_request_to_buyer)
260
+ truthy?(send_payment_request_to_buyer)
286
261
  end
287
262
 
288
263
  def send_mark_as_paid_email_to_buyer?
289
- truthy?(self.send_mark_as_paid_email_to_buyer)
264
+ truthy?(send_mark_as_paid_email_to_buyer)
290
265
  end
291
266
 
292
267
  def skip_buyer_validations?
293
- truthy?(self.skip_buyer_validations)
268
+ truthy?(skip_buyer_validations)
269
+ end
270
+
271
+ def skip_minimum_charge_validation?
272
+ truthy?(skip_minimum_charge_validation) || skip_buyer_validations?
294
273
  end
295
274
 
296
275
  def billing_name
@@ -309,6 +288,7 @@ module Effective
309
288
  return false if purchased?
310
289
 
311
290
  success = false
291
+ error = nil
312
292
 
313
293
  Effective::Order.transaction do
314
294
  begin
@@ -321,23 +301,27 @@ module Effective
321
301
 
322
302
  self.skip_buyer_validations = skip_buyer_validations
323
303
 
324
- save!(validate: validate)
304
+ run_purchasable_callbacks(:before_purchase)
325
305
 
326
- order_items.each { |item| (item.purchasable.purchased!(self, item) rescue nil) }
306
+ save!(validate: validate)
327
307
 
328
308
  success = true
329
309
  rescue => e
330
310
  self.purchase_state = purchase_state_was
331
311
  self.purchased_at = purchased_at_was
332
312
 
313
+ error = e.message
333
314
  raise ::ActiveRecord::Rollback
334
315
  end
335
316
  end
336
317
 
337
- send_order_receipts! if (success && email)
318
+ raise "Failed to purchase Effective::Order: #{error || errors.full_messages.to_sentence}" unless success
319
+
320
+ send_order_receipts! if email
338
321
 
339
- raise "Failed to purchase Effective::Order: #{self.errors.full_messages.to_sentence}" unless success
340
- success
322
+ run_purchasable_callbacks(:after_purchase)
323
+
324
+ true
341
325
  end
342
326
 
343
327
  def decline!(details: 'none', provider: 'none', card: 'none', validate: true)
@@ -346,6 +330,7 @@ module Effective
346
330
  raise EffectiveOrders::AlreadyPurchasedException.new('order already purchased') if purchased?
347
331
 
348
332
  success = false
333
+ error = nil
349
334
 
350
335
  Effective::Order.transaction do
351
336
  begin
@@ -358,16 +343,25 @@ module Effective
358
343
 
359
344
  save!(validate: validate)
360
345
 
361
- order_items.each { |item| (item.purchasable.declined!(self, item) rescue nil) }
362
-
363
346
  success = true
364
347
  rescue => e
348
+ self.purchase_state = purchase_state_was
349
+ self.purchased_at = purchased_at_was
350
+
351
+ error = e.message
365
352
  raise ::ActiveRecord::Rollback
366
353
  end
367
354
  end
368
355
 
369
- raise "Failed to decline! Effective::Order: #{self.errors.full_messages.to_sentence}" unless success
370
- success
356
+ raise "Failed to decline! Effective::Order: #{error || errors.full_messages.to_sentence}" unless success
357
+
358
+ run_purchasable_callbacks(:after_decline)
359
+
360
+ true
361
+ end
362
+
363
+ def abandoned?
364
+ purchase_state == EffectiveOrders::ABANDONED
371
365
  end
372
366
 
373
367
  def purchased?(provider = nil)
@@ -404,17 +398,17 @@ module Effective
404
398
  def send_order_receipt_to_seller!
405
399
  return false unless (EffectiveOrders.stripe_connect_enabled && purchased?(:stripe_connect))
406
400
 
407
- order_items.group_by(&:seller).each do |seller, order_items|
401
+ order_items.group_by { |oi| oi.seller }.each do |seller, order_items|
408
402
  send_email(:order_receipt_to_seller, to_param, seller, order_items)
409
403
  end
410
404
  end
411
405
 
412
406
  def send_payment_request_to_buyer!
413
- send_email(:payment_request_to_buyer, to_param) if !purchased?
407
+ send_email(:payment_request_to_buyer, to_param) unless purchased?
414
408
  end
415
409
 
416
410
  def send_pending_order_invoice_to_buyer!
417
- send_email(:pending_order_invoice_to_buyer, to_param) if !purchased?
411
+ send_email(:pending_order_invoice_to_buyer, to_param) unless purchased?
418
412
  end
419
413
 
420
414
  protected
@@ -430,8 +424,7 @@ module Effective
430
424
 
431
425
  def get_tax
432
426
  return nil unless tax_rate.present?
433
- amount = order_items.reject { |oi| oi.tax_exempt? }.map { |oi| (oi.subtotal * (tax_rate / 100.0)).round(0).to_i }.sum
434
- [amount, 0].max
427
+ order_items.reject { |oi| oi.tax_exempt? }.map { |oi| (oi.subtotal * (tax_rate / 100.0)).round(0).to_i }.sum
435
428
  end
436
429
 
437
430
  private
@@ -440,7 +433,27 @@ module Effective
440
433
  self.subtotal = order_items.map { |oi| oi.subtotal }.sum
441
434
  self.tax_rate = get_tax_rate()
442
435
  self.tax = get_tax()
443
- self.total = [subtotal + (tax || 0), 0].max
436
+ self.total = subtotal + (tax || 0)
437
+ end
438
+
439
+ def save_addresses
440
+ if user.respond_to?(:billing_address=) && billing_address.present?
441
+ user.billing_address = billing_address
442
+ user.billing_address.save
443
+ end
444
+
445
+ if user.respond_to?(:shipping_address=) && shipping_address.present?
446
+ user.shipping_address = shipping_address
447
+ user.shipping_address.save
448
+ end
449
+ end
450
+
451
+ def run_purchasable_callbacks(name)
452
+ begin
453
+ order_items.each { |oi| oi.purchasable.public_send(name, self, oi) if oi.purchasable.respond_to?(name) }
454
+ rescue => e
455
+ raise e unless Rails.env.production?
456
+ end
444
457
  end
445
458
 
446
459
  def send_email(email, *mailer_args)