effective_orders 2.2.4 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
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)