Class: Effective::Order

Inherits:
ActiveRecord::Base
  • Object
show all
Defined in:
app/models/effective/order.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(*items, user: nil, billing_address: nil, shipping_address: nil) ⇒ Order

items can be an Effective::Cart, a single acts_as_purchasable, or an array of acts_as_purchasables



119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
# File 'app/models/effective/order.rb', line 119

def initialize(*items, user: nil, billing_address: nil, shipping_address: nil)
  super() # Call super with no arguments

  # Set up defaults
  self.save_billing_address = true
  self.save_shipping_address = true
  self.shipping_address_same_as_billing = true

  self.user = user || (items.first.user if items.first.kind_of?(Effective::Cart))

  if billing_address
    self.billing_address = billing_address
    self.billing_address.full_name ||= billing_name
  end

  if shipping_address
    self.shipping_address = shipping_address
    self.shipping_address.full_name ||= billing_name
  end

  add(items) if items.present?
end

Instance Attribute Details

#save_billing_address ⇒ Object

save these addresses to the user if selected



14
15
16
# File 'app/models/effective/order.rb', line 14

def save_billing_address
  @save_billing_address
end

#save_shipping_address ⇒ Object

save these addresses to the user if selected



14
15
16
# File 'app/models/effective/order.rb', line 14

def save_shipping_address
  @save_shipping_address
end

#send_payment_request_to_buyer ⇒ Object

Used by the /admin/orders/new form. Should the payment request email be sent after creating an order?



15
16
17
# File 'app/models/effective/order.rb', line 15

def send_payment_request_to_buyer
  @send_payment_request_to_buyer
end

#shipping_address_same_as_billing ⇒ Object

save these addresses to the user if selected



14
15
16
# File 'app/models/effective/order.rb', line 14

def shipping_address_same_as_billing
  @shipping_address_same_as_billing
end

#skip_buyer_validations ⇒ Object

Enabled by the /admin/orders/create action



16
17
18
# File 'app/models/effective/order.rb', line 16

def skip_buyer_validations
  @skip_buyer_validations
end

Instance Method Details

#add(*items, quantity: 1) ⇒ Object Also known as: add_to_order

add(Product.first) => returns an Effective::OrderItem add(Product.first, current_cart) => returns an array of Effective::OrderItems



144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
# File 'app/models/effective/order.rb', line 144

def add(*items, quantity: 1)
  raise 'unable to alter a purchased order' if purchased?
  raise 'unable to alter a declined order' if declined?

  cart_items = items.flatten.flat_map do |item|
    if item.kind_of?(Effective::Cart)
      item.cart_items.to_a
    elsif item.kind_of?(ActsAsPurchasable)
      Effective::CartItem.new(quantity: quantity.to_i).tap { |cart_item| cart_item.purchasable = item }
    else
      raise ArgumentError.new('Effective::Order.add() expects one or more acts_as_purchasable objects, or an Effective::Cart')
    end
  end

  # Make sure to reset stored aggregates
  self.total = nil
  self.subtotal = nil
  self.tax = nil

  retval = cart_items.map do |item|
    order_items.build(
      title: item.title,
      quantity: item.quantity,
      price: item.price,
      tax_exempt: item.tax_exempt || false,
      seller_id: (item.purchasable.try(:seller).try(:id) rescue nil)
    ).tap { |order_item| order_item.purchasable = item.purchasable }
  end

  retval.size == 1 ? retval.first : retval
end

#billing_name ⇒ Object



293
294
295
296
297
298
299
300
301
# File 'app/models/effective/order.rb', line 293

def billing_name
  name ||= billing_address.try(:full_name).presence
  name ||= user.try(:full_name).presence
  name ||= (user.try(:first_name).to_s + ' ' + user.try(:last_name).to_s).presence
  name ||= user.try(:email).presence
  name ||= user.to_s
  name ||= "User #{user.try(:id)}"
  name
end

#create_as_pending ⇒ Object

This is called from admin/orders#create This is intended for use as an admin action only It skips any address or bad user validations



213
214
215
216
217
218
219
220
221
222
223
# File 'app/models/effective/order.rb', line 213

def create_as_pending
  self.purchase_state = EffectiveOrders::PENDING

  self.skip_buyer_validations = true
  self.addresses.clear if addresses.any? { |address| address.valid? == false }

  return false unless save

  send_payment_request_to_buyer! if send_payment_request_to_buyer?
  true
end

#decline!(details: 'none', provider: 'admin', card: 'none', validate: true) ⇒ Object



335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
# File 'app/models/effective/order.rb', line 335

def decline!(details: 'none', provider: 'admin', card: 'none', validate: true)
  return false if declined?

  raise EffectiveOrders::AlreadyPurchasedException.new('order already purchased') if purchased?

  success = false

  Effective::Order.transaction do
    begin
      self.purchase_state = EffectiveOrders::DECLINED
      self.purchased_at = nil

      self.payment = details.kind_of?(Hash) ? details : { details: details.to_s }
      self.payment_provider = provider.to_s
      self.payment_card = card.to_s.presence || 'none'

      save!(validate: validate)

      order_items.each { |item| (item.purchasable.declined!(self, item) rescue nil) }

      success = true
    rescue => e
      raise ::ActiveRecord::Rollback
    end
  end

  raise "Failed to decline! Effective::Order: #{self.errors.full_messages.to_sentence}" unless success
  success
end

#declined? ⇒ Boolean

Returns:

  • (Boolean)


374
375
376
# File 'app/models/effective/order.rb', line 374

def declined?
  purchase_state == EffectiveOrders::DECLINED
end

#num_items ⇒ Object



269
270
271
# File 'app/models/effective/order.rb', line 269

def num_items
  order_items.map { |oi| oi.quantity }.sum
end

#order_items_attributes=(order_item_attributes) ⇒ Object

This is used for updating Subscription codes. We want to update the underlying purchasable object of an OrderItem Passing the order_item_attributes using rails default acts_as_nested creates a new object instead of updating the temporary one. So we override this method to do the updates on the non-persisted OrderItem objects Right now strong_paramaters only lets through stripe_coupon_id "stripe_coupon_id"=>"50OFF", "id"=>"2"}}



231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
# File 'app/models/effective/order.rb', line 231

def order_items_attributes=(order_item_attributes)
  if self.persisted? == false
    (order_item_attributes || {}).each do |_, atts|
      order_item = self.order_items.find { |oi| oi.purchasable.class.name == atts[:class] && oi.purchasable.id == atts[:id].to_i }

      if order_item
        order_item.purchasable.attributes = atts.except(:id, :class)

        # Recalculate the OrderItem based on the updated purchasable object
        order_item.title = order_item.purchasable.title
        order_item.price = order_item.purchasable.price
        order_item.tax_exempt = order_item.purchasable.tax_exempt
        order_item.seller_id = (order_item.purchasable.try(:seller).try(:id) rescue nil)
      end
    end
  end
end

#pending? ⇒ Boolean

Returns:

  • (Boolean)


378
379
380
# File 'app/models/effective/order.rb', line 378

def pending?
  purchase_state == EffectiveOrders::PENDING
end

#purchasables ⇒ Object



249
250
251
# File 'app/models/effective/order.rb', line 249

def purchasables
  order_items.map { |order_item| order_item.purchasable }
end

#purchase!(details: 'none', provider: 'admin', card: 'none', validate: true, email: true, skip_buyer_validations: false) ⇒ Object

Effective::Order.new(Product.first, user: User.first).purchase!(details: 'manual purchase') order.purchase!(details: value)



305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
# File 'app/models/effective/order.rb', line 305

def purchase!(details: 'none', provider: 'admin', card: 'none', validate: true, email: true, skip_buyer_validations: false)
  return false if purchased?

  success = false

  Effective::Order.transaction do
    begin
      self.purchase_state = EffectiveOrders::PURCHASED
      self.purchased_at ||= Time.zone.now

      self.payment = details.kind_of?(Hash) ? details : { details: details.to_s }
      self.payment_provider = provider.to_s
      self.payment_card = card.to_s.presence || 'none'

      save!(validate: validate)

      order_items.each { |item| (item.purchasable.purchased!(self, item) rescue nil) }

      success = true
    rescue => e
      raise ::ActiveRecord::Rollback
    end
  end

  send_order_receipts! if (success && email)

  raise "Failed to purchase Effective::Order: #{self.errors.full_messages.to_sentence}" unless success
  success
end

#purchased?(provider = nil) ⇒ Boolean

Returns:

  • (Boolean)


365
366
367
368
369
370
371
372
# File 'app/models/effective/order.rb', line 365

def purchased?(provider = nil)
  return false if (purchase_state != EffectiveOrders::PURCHASED)
  return true if provider.nil? || payment_provider == provider.to_s

  unless EffectiveOrder.payment_providers.include?(provider.to_s)
    raise "Unknown provider #{provider}. Known providers are #{EffectiveOrders.payment_providers}"
  end
end

#save_billing_address? ⇒ Boolean

Returns:

  • (Boolean)


273
274
275
# File 'app/models/effective/order.rb', line 273

def save_billing_address?
  ::ActiveRecord::ConnectionAdapters::Column::TRUE_VALUES.include?(self.save_billing_address)
end

#save_shipping_address? ⇒ Boolean

Returns:

  • (Boolean)


277
278
279
# File 'app/models/effective/order.rb', line 277

def save_shipping_address?
  ::ActiveRecord::ConnectionAdapters::Column::TRUE_VALUES.include?(self.save_shipping_address)
end

#send_order_receipt_to_admin! ⇒ Object



388
389
390
# File 'app/models/effective/order.rb', line 388

def send_order_receipt_to_admin!
  send_email(:order_receipt_to_admin, to_param) if purchased?
end

#send_order_receipt_to_buyer! ⇒ Object



392
393
394
# File 'app/models/effective/order.rb', line 392

def send_order_receipt_to_buyer!
  send_email(:order_receipt_to_buyer, to_param) if purchased?
end

#send_order_receipt_to_seller! ⇒ Object



396
397
398
399
400
401
402
# File 'app/models/effective/order.rb', line 396

def send_order_receipt_to_seller!
  return false unless (EffectiveOrders.stripe_connect_enabled && purchased?(:stripe_connect))

  order_items.group_by(&:seller).each do |seller, order_items|
    send_email(:order_receipt_to_seller, to_param, seller, order_items)
  end
end

#send_order_receipts! ⇒ Object



382
383
384
385
386
# File 'app/models/effective/order.rb', line 382

def send_order_receipts!
  send_order_receipt_to_admin! if EffectiveOrders.mailer[:send_order_receipt_to_admin]
  send_order_receipt_to_buyer! if EffectiveOrders.mailer[:send_order_receipt_to_buyer]
  send_order_receipt_to_seller! if EffectiveOrders.mailer[:send_order_receipt_to_seller]
end

#send_payment_request_to_buyer! ⇒ Object



404
405
406
# File 'app/models/effective/order.rb', line 404

def send_payment_request_to_buyer!
  send_email(:payment_request_to_buyer, to_param) if !purchased?
end

#send_payment_request_to_buyer? ⇒ Boolean

Returns:

  • (Boolean)


281
282
283
# File 'app/models/effective/order.rb', line 281

def send_payment_request_to_buyer?
  ::ActiveRecord::ConnectionAdapters::Column::TRUE_VALUES.include?(self.send_payment_request_to_buyer)
end

#send_pending_order_invoice_to_buyer! ⇒ Object



408
409
410
# File 'app/models/effective/order.rb', line 408

def send_pending_order_invoice_to_buyer!
  send_email(:pending_order_invoice_to_buyer, to_param) if !purchased?
end

#shipping_address_same_as_billing? ⇒ Boolean

Returns:

  • (Boolean)


285
286
287
# File 'app/models/effective/order.rb', line 285

def shipping_address_same_as_billing?
  ::ActiveRecord::ConnectionAdapters::Column::TRUE_VALUES.include?(self.shipping_address_same_as_billing)
end

#skip_buyer_validations? ⇒ Boolean

Returns:

  • (Boolean)


289
290
291
# File 'app/models/effective/order.rb', line 289

def skip_buyer_validations?
  ::ActiveRecord::ConnectionAdapters::Column::TRUE_VALUES.include?(self.skip_buyer_validations)
end

#subtotal ⇒ Object



261
262
263
# File 'app/models/effective/order.rb', line 261

def subtotal
  self[:subtotal] || order_items.map { |oi| oi.subtotal }.sum
end

#tax ⇒ Object



257
258
259
# File 'app/models/effective/order.rb', line 257

def tax
  self[:tax] || get_tax()
end

#tax_rate ⇒ Object



253
254
255
# File 'app/models/effective/order.rb', line 253

def tax_rate
  self[:tax_rate] || get_tax_rate()
end

#total ⇒ Object



265
266
267
# File 'app/models/effective/order.rb', line 265

def total
  self[:total] || [(subtotal + tax.to_i), 0].max
end

#user=(user) ⇒ Object



177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
# File 'app/models/effective/order.rb', line 177

def user=(user)
  return if user.nil?

  super

  # Copy user addresses into this order if they are present
  if user.respond_to?(:billing_address) && user.billing_address.present?
    self.billing_address = user.billing_address
  end

  if user.respond_to?(:shipping_address) && user.shipping_address.present?
    self.shipping_address = user.shipping_address
  end

  # If our addresses are required, make sure they exist
  if EffectiveOrders.require_billing_address
    self.billing_address ||= Effective::Address.new()
  end

  if EffectiveOrders.require_shipping_address
    self.shipping_address ||= Effective::Address.new()
  end

  # Ensure the Full Name is assigned when an address exists
  if billing_address.present? && billing_address.full_name.blank?
    self.billing_address.full_name = billing_name
  end

  if shipping_address.present? && shipping_address.full_name.blank?
    self.shipping_address.full_name = billing_name
  end
end