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(atts = nil, &block) ⇒ Order

Effective::Order.new(items: Product.first) Effective::Order.new(items: [Product.first, Product.second], user: User.first) Effective::Order.new(items: Product.first, user: User.first, billing_address: Effective::Address.new, shipping_address: Effective::Address.new)



162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
# File 'app/models/effective/order.rb', line 162

def initialize(atts = nil, &block)
  super(state: EffectiveOrders::PENDING) # Initialize with state: PENDING

  return unless atts.present?

  if atts.kind_of?(Hash)
    items = Array(atts.delete(:item)) + Array(atts.delete(:items))

    self.user = atts.delete(:user) || (items.first.user if items.first.respond_to?(:user))

    if (address = atts.delete(:billing_address)).present?
      self.billing_address = address
      self.billing_address.full_name ||= user.to_s.presence
    end

    if (address = atts.delete(:shipping_address)).present?
      self.shipping_address = address
      self.shipping_address.full_name ||= user.to_s.presence
    end

    atts.each { |key, value| self.send("#{key}=", value) }

    add(items) if items.present?
  else # Attributes are not a Hash
    self.user = atts.user if atts.respond_to?(:user)
    add(atts)
  end
end

Instance Attribute Details

#confirmed_checkoutObject

Set on the Checkout Step 1



24
25
26
# File 'app/models/effective/order.rb', line 24

def confirmed_checkout
  @confirmed_checkout
end

#send_mark_as_paid_email_to_buyerObject

Set by Admin::Orders#mark_as_paid



28
29
30
# File 'app/models/effective/order.rb', line 28

def send_mark_as_paid_email_to_buyer
  @send_mark_as_paid_email_to_buyer
end

#send_payment_request_to_buyerObject

Settings in the /admin action forms



27
28
29
# File 'app/models/effective/order.rb', line 27

def send_payment_request_to_buyer
  @send_payment_request_to_buyer
end

#skip_buyer_validationsObject

Set by Admin::Orders#create



29
30
31
# File 'app/models/effective/order.rb', line 29

def skip_buyer_validations
  @skip_buyer_validations
end

#terms_and_conditionsObject

Yes, I agree to the terms and conditions



23
24
25
# File 'app/models/effective/order.rb', line 23

def terms_and_conditions
  @terms_and_conditions
end

Instance Method Details

#add(*items, quantity: 1) ⇒ Object

Items can be an Effective::Cart, an Effective::order, a single acts_as_purchasable, or multiple acts_as_purchasables add(Product.first) => returns an Effective::OrderItem add(Product.first, current_cart) => returns an array of Effective::OrderItems



194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
# File 'app/models/effective/order.rb', line 194

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, purchasable: item)
    elsif item.kind_of?(Effective::Order)
      # Duplicate an existing order
      self.note_to_buyer ||= item.note_to_buyer
      self.note_internal ||= item.note_internal

      item.order_items.select { |oi| oi.purchasable.kind_of?(Effective::Product) }.map do |oi|
        product = Effective::Product.new(name: oi.purchasable.purchasable_name, price: oi.purchasable.price, tax_exempt: oi.purchasable.tax_exempt)
        Effective::CartItem.new(quantity: oi.quantity, purchasable: product)
      end
    else
      raise 'add() expects one or more acts_as_purchasable objects, or an Effective::Cart'
    end
  end.compact

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

  retval = cart_items.map do |item|
    order_items.build(
      name: item.name,
      quantity: item.quantity,
      price: item.price,
      tax_exempt: (item.tax_exempt || false),
    ).tap { |order_item| order_item.purchasable = item.purchasable }
  end

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

#assign_confirmed_if_valid!Object

This lets us skip to the confirmed workflow for an admin…



355
356
357
358
359
360
361
362
363
364
# File 'app/models/effective/order.rb', line 355

def assign_confirmed_if_valid!
  return unless pending?

  self.state = EffectiveOrders::CONFIRMED
  return true if valid?

  self.errors.clear
  self.state = EffectiveOrders::PENDING
  false
end

#confirm!Object

Used by admin checkout only



350
351
352
# File 'app/models/effective/order.rb', line 350

def confirm!
  update!(state: EffectiveOrders::CONFIRMED)
end

#confirmed?Boolean

Returns:

  • (Boolean)


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

def confirmed?
  state == EffectiveOrders::CONFIRMED
end

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



420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
# File 'app/models/effective/order.rb', line 420

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

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

  error = nil

  assign_attributes(
    state: EffectiveOrders::DECLINED,
    purchased_at: nil,
    payment: payment_to_h(payment),
    payment_provider: provider,
    payment_card: (card.presence || 'none'),
    skip_buyer_validations: true
  )

  Effective::Order.transaction do
    begin
      save!(validate: validate)
    rescue => e
      self.state = state_was

      error = e.message
      raise ::ActiveRecord::Rollback
    end
  end

  raise "Failed to decline order: #{error || errors.full_messages.to_sentence}" unless error.nil?

  run_purchasable_callbacks(:after_decline)

  true
end

#declined?Boolean

Returns:

  • (Boolean)


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

def declined?
  state == EffectiveOrders::DECLINED
end

#defer!(provider: 'none', email: true) ⇒ Object



405
406
407
408
409
410
411
412
413
414
415
416
417
418
# File 'app/models/effective/order.rb', line 405

def defer!(provider: 'none', email: true)
  return false if purchased?

  assign_attributes(
    state: EffectiveOrders::DEFERRED,
    payment_provider: provider
  )

  save!

  send_payment_request_to_buyer! if email

  true
end

#deferred?Boolean

Returns:

  • (Boolean)


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

def deferred?
  state == EffectiveOrders::DEFERRED
end

#emails_send_toObject

Doesn’t control anything. Purely for the flash messaging



455
456
457
# File 'app/models/effective/order.rb', line 455

def emails_send_to
  [email, cc.presence].compact.to_sentence
end

#free?Boolean

Returns:

  • (Boolean)


312
313
314
# File 'app/models/effective/order.rb', line 312

def free?
  total == 0
end

#num_itemsObject



320
321
322
# File 'app/models/effective/order.rb', line 320

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

#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 It’s basically the same as save! on a new order, except it might send the payment request to buyer



340
341
342
343
344
345
346
347
# File 'app/models/effective/order.rb', line 340

def pending!
  self.state = EffectiveOrders::PENDING
  self.addresses.clear if addresses.any? { |address| address.valid? == false }
  save!

  send_payment_request_to_buyer! if send_payment_request_to_buyer?
  true
end

#pending?Boolean

Returns:

  • (Boolean)


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

def pending?
  state == EffectiveOrders::PENDING
end

#purchasablesObject



292
293
294
# File 'app/models/effective/order.rb', line 292

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

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

Effective::Order.new(items: Product.first, user: User.first).purchase!(email: false)



367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
# File 'app/models/effective/order.rb', line 367

def purchase!(payment: 'none', provider: 'none', card: 'none', email: true, skip_buyer_validations: false)
  return false if purchased?
  error = nil

  assign_attributes(
    state: EffectiveOrders::PURCHASED,
    payment: payment_to_h(payment),
    payment_provider: provider,
    payment_card: (card.presence || 'none'),
    skip_buyer_validations: skip_buyer_validations
  )

  self.purchased_at ||= Time.zone.now

  Effective::Order.transaction do
    begin
      run_purchasable_callbacks(:before_purchase)
      save!
      update_purchasables_purchased_order!
    rescue => e
      self.state = state_was
      self.purchased_at = nil

      error = e.message
      raise ::ActiveRecord::Rollback
    end
  end

  raise "Failed to purchase order: #{error || errors.full_messages.to_sentence}" unless error.nil?

  run_purchasable_callbacks(:after_purchase)

  send_refund_notification! if email && refund?
  send_order_receipts! if email

  true
end

#purchased?(provider = nil) ⇒ Boolean

Returns:

  • (Boolean)


282
283
284
285
286
# File 'app/models/effective/order.rb', line 282

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

#qb_item_nameObject

first or build



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

def qb_item_name
  raise('expected EffectiveQbSync gem') unless defined?(EffectiveQbSync)
  (qb_order_item || build_qb_order_item(name: purchasable.qb_item_name)).name
end

#refund?Boolean

Returns:

  • (Boolean)


316
317
318
# File 'app/models/effective/order.rb', line 316

def refund?
  total.to_i < 0
end

#send_mark_as_paid_email_to_buyer?Boolean

Returns:

  • (Boolean)


328
329
330
# File 'app/models/effective/order.rb', line 328

def send_mark_as_paid_email_to_buyer?
  EffectiveResources.truthy?(send_mark_as_paid_email_to_buyer)
end

#send_order_receipt_to_admin!Object



464
465
466
# File 'app/models/effective/order.rb', line 464

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

#send_order_receipt_to_buyer!Object



468
469
470
# File 'app/models/effective/order.rb', line 468

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

#send_order_receipts!Object



459
460
461
462
# File 'app/models/effective/order.rb', line 459

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]
end

#send_payment_request_to_buyer!Object



472
473
474
# File 'app/models/effective/order.rb', line 472

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

#send_payment_request_to_buyer?Boolean

Returns:

  • (Boolean)


324
325
326
# File 'app/models/effective/order.rb', line 324

def send_payment_request_to_buyer?
  EffectiveResources.truthy?(send_payment_request_to_buyer) && !free? && !refund?
end

#send_pending_order_invoice_to_buyer!Object



476
477
478
# File 'app/models/effective/order.rb', line 476

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

#send_refund_notification!Object



480
481
482
# File 'app/models/effective/order.rb', line 480

def send_refund_notification!
  send_email(:refund_notification_to_admin, to_param) if purchased? && refund?
end

#skip_buyer_validations?Boolean

Returns:

  • (Boolean)


332
333
334
# File 'app/models/effective/order.rb', line 332

def skip_buyer_validations?
  EffectiveResources.truthy?(skip_buyer_validations)
end

#skip_qb_sync!Object



484
485
486
# File 'app/models/effective/order.rb', line 484

def skip_qb_sync!
  EffectiveOrders.use_effective_qb_sync ? EffectiveQbSync.skip_order!(self) : true
end

#subtotalObject



296
297
298
# File 'app/models/effective/order.rb', line 296

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

#taxObject



304
305
306
# File 'app/models/effective/order.rb', line 304

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

#tax_rateObject



300
301
302
# File 'app/models/effective/order.rb', line 300

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

#to_sObject



252
253
254
255
256
257
258
259
260
261
262
# File 'app/models/effective/order.rb', line 252

def to_s
  if refund?
    "Refund ##{to_param}"
  elsif purchased?
    "Receipt ##{to_param}"
  elsif pending?
    "Pending Order ##{to_param}"
  else
    "Order ##{to_param}"
  end
end

#totalObject



308
309
310
# File 'app/models/effective/order.rb', line 308

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

#update_prices!Object



235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
# File 'app/models/effective/order.rb', line 235

def update_prices!
  raise('already purchased') if purchased?
  raise('must be pending or confirmed') unless pending? || confirmed?

  order_items.each do |item|
    purchasable = item.purchasable

    if purchasable.blank? || purchasable.marked_for_destruction?
      item.mark_for_destruction
    else
      item.price = purchasable.price
    end
  end

  save!
end