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)



156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
# File 'app/models/effective/order.rb', line 156

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_checkout ⇒ Object

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_buyer ⇒ Object

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_buyer ⇒ Object

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_validations ⇒ Object

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_conditions ⇒ Object

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



188
189
190
191
192
193
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
# File 'app/models/effective/order.rb', line 188

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...



383
384
385
386
387
388
389
390
391
392
# File 'app/models/effective/order.rb', line 383

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

#billing_first_name ⇒ Object



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

def billing_first_name
  billing_name.to_s.split(' ').first
end

#billing_last_name ⇒ Object



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

def billing_last_name
  Array(billing_name.to_s.split(' ')[1..-1]).join(' ')
end

#confirm! ⇒ Object

Used by admin checkout only



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

def confirm!
  return false if purchased?
  update!(state: EffectiveOrders::CONFIRMED)
end

#confirmed? ⇒ Boolean

Returns:

  • (Boolean)


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

def confirmed?
  state == EffectiveOrders::CONFIRMED
end

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



441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
# File 'app/models/effective/order.rb', line 441

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)


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

def declined?
  state == EffectiveOrders::DECLINED
end

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



426
427
428
429
430
431
432
433
434
435
436
437
438
439
# File 'app/models/effective/order.rb', line 426

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)


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

def deferred?
  state == EffectiveOrders::DEFERRED
end

#emails_send_to ⇒ Object

Doesn't control anything. Purely for the flash messaging



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

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

#free? ⇒ Boolean

Returns:

  • (Boolean)


337
338
339
# File 'app/models/effective/order.rb', line 337

def free?
  total == 0
end

#label ⇒ Object



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

def label
  if refund?
    "Refund"
  elsif purchased?
    "Receipt"
  elsif pending?
    "Pending Order"
  else
    "Order"
  end
end

#num_items ⇒ Object



345
346
347
# File 'app/models/effective/order.rb', line 345

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

#payment_method ⇒ Object

Visa - 1234



263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
# File 'app/models/effective/order.rb', line 263

def payment_method
  return nil unless purchased?

  # Normalize payment card
  card = case payment_card.to_s.downcase.gsub(' ', '').strip
    when '' then nil
    when 'v', 'visa' then 'Visa'
    when 'm', 'mc', 'master', 'mastercard' then 'MasterCard'
    when 'a', 'ax', 'american', 'americanexpress' then 'American Express'
    when 'd', 'discover' then 'Discover'
    else payment_card.to_s
  end unless payment_provider == 'free'

  # stripe, moneris, moneris_checkout
  last4 = (payment[:active_card] || payment['f4l4'] || payment['first6last4']).to_s.last(4)

  [card, '-', last4].compact.join(' ')
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



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

def pending!
  return false if purchased?

  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)


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

def pending?
  state == EffectiveOrders::PENDING
end

#purchasables ⇒ Object



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

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)



395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
# File 'app/models/effective/order.rb', line 395

def purchase!(payment: 'none', provider: 'none', card: 'none', email: true, skip_buyer_validations: false)
  # Assign attributes
  self.state = EffectiveOrders::PURCHASED
  self.skip_buyer_validations = skip_buyer_validations

  self.payment_provider ||= provider
  self.payment_card ||= (card.presence || 'none')
  self.purchased_at ||= Time.zone.now
  self.payment = payment_to_h(payment) if self.payment.blank?

  begin
    Effective::Order.transaction do
      run_purchasable_callbacks(:before_purchase)
      save!
      update_purchasables_purchased_order!
    end
  rescue => e
    Effective::Order.transaction do
      save!(validate: false)
      update_purchasables_purchased_order!
    end

    raise(e)
  end

  run_purchasable_callbacks(:after_purchase)
  send_order_receipts! if email

  true
end

#purchased?(provider = nil) ⇒ Boolean

Returns:

  • (Boolean)


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

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

#refund? ⇒ Boolean

Returns:

  • (Boolean)


341
342
343
# File 'app/models/effective/order.rb', line 341

def refund?
  total.to_i < 0
end

#send_mark_as_paid_email_to_buyer? ⇒ Boolean

Returns:

  • (Boolean)


353
354
355
# File 'app/models/effective/order.rb', line 353

def send_mark_as_paid_email_to_buyer?
  EffectiveResources.truthy?(send_mark_as_paid_email_to_buyer)
end

#send_order_receipt_to_admin! ⇒ Object



486
487
488
# File 'app/models/effective/order.rb', line 486

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

#send_order_receipt_to_buyer! ⇒ Object



490
491
492
# File 'app/models/effective/order.rb', line 490

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

#send_order_receipts! ⇒ Object



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

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_refund_notification! if refund?
end

#send_payment_request_to_buyer! ⇒ Object



494
495
496
# File 'app/models/effective/order.rb', line 494

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)


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

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

#send_pending_order_invoice_to_buyer! ⇒ Object



498
499
500
# File 'app/models/effective/order.rb', line 498

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

#send_refund_notification! ⇒ Object



502
503
504
# File 'app/models/effective/order.rb', line 502

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

#skip_buyer_validations? ⇒ Boolean

Returns:

  • (Boolean)


357
358
359
# File 'app/models/effective/order.rb', line 357

def skip_buyer_validations?
  EffectiveResources.truthy?(skip_buyer_validations)
end

#skip_qb_sync! ⇒ Object



506
507
508
# File 'app/models/effective/order.rb', line 506

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

#subtotal ⇒ Object



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

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

#tax ⇒ Object



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

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

#tax_rate ⇒ Object



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

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

#to_s ⇒ Object



246
247
248
# File 'app/models/effective/order.rb', line 246

def to_s
  [label, ' #', to_param].join
end

#total ⇒ Object



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

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

#transaction_id ⇒ Object

For moneris and moneris_checkout. Just a unique value.



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

def transaction_id
  [to_param, billing_name.to_s.parameterize.presence, Time.zone.now.to_i].compact.join('-')
end

#update_prices! ⇒ Object



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

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