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)



185
186
187
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
# File 'app/models/effective/order.rb', line 185

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

  return self unless atts.present?

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

    self.user = atts[:user] || items.first.try(:user)

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

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

    atts.except(:item, :items, :user, :billing_address, :shipping_address).each do |key, value|
      self.send("#{key}=", value)
    end

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

  self
end

Instance Attribute Details

#confirmed_checkoutObject

Set on the Checkout Step 1



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

def confirmed_checkout
  @confirmed_checkout
end

#send_mark_as_paid_email_to_buyerObject

Set by Admin::Orders#mark_as_paid



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

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



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

def send_payment_request_to_buyer
  @send_payment_request_to_buyer
end

#skip_buyer_validationsObject

Set by Admin::Orders#create



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

def skip_buyer_validations
  @skip_buyer_validations
end

#terms_and_conditionsObject

Yes, I agree to the terms and conditions



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

def terms_and_conditions
  @terms_and_conditions
end

Instance Method Details

#abandoned?Boolean

Returns:

  • (Boolean)


436
437
438
# File 'app/models/effective/order.rb', line 436

def abandoned?
  state == EffectiveOrders::ABANDONED
end

#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



244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
# File 'app/models/effective/order.rb', line 244

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
      self.cc ||= item.cc

      item.order_items.select { |oi| oi.purchasable.kind_of?(Effective::Product) }.map do |oi|
        purchasable = oi.purchasable

        product = Effective::Product.new(name: purchasable.purchasable_name, price: purchasable.price, tax_exempt: purchasable.tax_exempt)

        # Copy over any extended attributes that may have been created
        atts = purchasable.dup.attributes.except('name', 'price', 'tax_exempt', 'purchased_order_id').compact

        atts.each do |k, v|
          next unless product.respond_to?("#{k}=") && product.respond_to?(k)
          product.send("#{k}=", v) if product.send(k).blank?
        end

        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
  assign_attributes(subtotal: nil, tax_rate: nil, tax: nil, amount_owing: nil, surcharge_percent: nil, surcharge: nil, total: 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

#amount_owingObject



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

def amount_owing
  self[:amount_owing] || get_amount_owing()
end

#assign_confirmed_if_valid!Object

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



558
559
560
561
562
563
564
565
566
567
# File 'app/models/effective/order.rb', line 558

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_nameObject



380
381
382
# File 'app/models/effective/order.rb', line 380

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

#billing_last_nameObject



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

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

#confirm!Object

Used by admin checkout only



552
553
554
555
# File 'app/models/effective/order.rb', line 552

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

#confirmed?Boolean

Returns:

  • (Boolean)


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

def confirmed?
  state == EffectiveOrders::CONFIRMED
end

#custom_order?Boolean

A custom order is one that was created by an admin We allow custom orders to have their order items updated

Returns:

  • (Boolean)


410
411
412
# File 'app/models/effective/order.rb', line 410

def custom_order?
  order_items.all? { |oi| oi.purchasable_type == 'Effective::Product' }
end

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



647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
# File 'app/models/effective/order.rb', line 647

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

  raise('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
      run_purchasable_callbacks(:before_decline)
      save!(validate: validate)
      run_purchasable_callbacks(:after_decline)
    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?

  true
end

#declined?Boolean

Returns:

  • (Boolean)


432
433
434
# File 'app/models/effective/order.rb', line 432

def declined?
  state == EffectiveOrders::DECLINED
end

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



636
637
638
639
640
641
642
643
644
645
# File 'app/models/effective/order.rb', line 636

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)


396
397
398
# File 'app/models/effective/order.rb', line 396

def deferred?
  state == EffectiveOrders::DEFERRED
end

#done?Boolean

Returns:

  • (Boolean)


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

def done?
  persisted? && (purchased? || declined?)
end

#duplicateObject



371
372
373
# File 'app/models/effective/order.rb', line 371

def duplicate
  Effective::Order.new(self)
end

#emails_send_toObject

Doesn’t control anything. Purely for the flash messaging



682
683
684
# File 'app/models/effective/order.rb', line 682

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

#free?Boolean

Returns:

  • (Boolean)


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

def free?
  total == 0
end

#in_progress?Boolean

Returns:

  • (Boolean)


400
401
402
# File 'app/models/effective/order.rb', line 400

def in_progress?
  pending? || confirmed? || deferred?
end

#labelObject



315
316
317
318
319
320
321
322
323
324
325
326
327
# File 'app/models/effective/order.rb', line 315

def label
  if refund? && purchased?
    'Refund'
  elsif purchased?
    'Receipt'
  elsif refund? && (pending? || confirmed?)
    'Pending Refund'
  elsif (pending? || confirmed?)
    'Pending Order'
  else
    'Order'
  end
end

#mark_as_purchased!Object

Call this as a way to skip over non consequential orders And mark some purchasables purchased This is different than the Mark as Paid payment processor



572
573
574
# File 'app/models/effective/order.rb', line 572

def mark_as_purchased!
  purchase!(skip_buyer_validations: true, email: false, skip_quickbooks: true)
end

#num_itemsObject



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

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

#payment_methodObject

Visa - 1234



334
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
364
365
366
367
368
369
# File 'app/models/effective/order.rb', line 334

def payment_method
  return nil unless purchased?

  provider = payment_provider if ['cheque', 'etransfer', 'phone', 'credit card'].include?(payment_provider)

  # 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

  # Try again
  if card == 'none' && payment['card_type'].present?
    card = case payment['card_type'].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
  end

  last4 = if payment[:active_card] && payment[:active_card].include?('**** **** ****')
    payment[:active_card][15,4]
  end

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

  [provider.presence, card.presence, last4.presence].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



537
538
539
540
541
542
543
544
545
546
547
548
549
# File 'app/models/effective/order.rb', line 537

def pending!
  return false if purchased?

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

  if send_payment_request_to_buyer?
    after_commit { send_payment_request_to_buyer! }
  end

  true
end

#pending?Boolean

Returns:

  • (Boolean)


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

def pending?
  state == EffectiveOrders::PENDING
end

#pending_refund?Boolean

Returns:

  • (Boolean)


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

def pending_refund?
  return false if EffectiveOrders.buyer_purchases_refund?
  return false if purchased?

  refund?
end

#purchasablesObject



440
441
442
# File 'app/models/effective/order.rb', line 440

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

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

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



577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
# File 'app/models/effective/order.rb', line 577

def purchase!(payment: nil, provider: nil, card: nil, email: true, skip_buyer_validations: false, skip_quickbooks: false)
  return true if purchased?

  # Assign attributes
  assign_attributes(
    state: EffectiveOrders::PURCHASED,
    skip_buyer_validations: skip_buyer_validations,

    payment: payment_to_h(payment.presence || 'none'),
    purchased_at: (purchased_at.presence || Time.zone.now),

    payment_provider: (provider.presence || 'none'),
    payment_card: (card.presence || 'none')
  )

  # Updates surcharge and total based on payment_provider
  assign_order_charges()

  begin
    Effective::Order.transaction do
      run_purchasable_callbacks(:before_purchase)

      save!
      update_purchasables_purchased_order!

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

    raise(e)
  end

  send_order_receipts! if email
  after_commit { sync_quickbooks!(skip: skip_quickbooks) }

  true
end

#purchased?(provider = nil) ⇒ Boolean

Returns:

  • (Boolean)


414
415
416
417
418
# File 'app/models/effective/order.rb', line 414

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

#purchased_with_credit_card?Boolean

Returns:

  • (Boolean)


424
425
426
# File 'app/models/effective/order.rb', line 424

def purchased_with_credit_card?
  purchased? && EffectiveOrders.credit_card_payment_providers.include?(payment_provider)
end

#purchased_without_credit_card?Boolean

Returns:

  • (Boolean)


428
429
430
# File 'app/models/effective/order.rb', line 428

def purchased_without_credit_card?
  purchased? && EffectiveOrders.credit_card_payment_providers.exclude?(payment_provider)
end

#refund?Boolean

Returns:

  • (Boolean)


488
489
490
# File 'app/models/effective/order.rb', line 488

def refund?
  total.to_i < 0
end

#remove(*items) ⇒ Object



218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
# File 'app/models/effective/order.rb', line 218

def remove(*items)
  raise 'unable to alter a purchased order' if purchased?
  raise 'unable to alter a declined order' if declined?

  removed = items.map do |item|
    order_item = if item.kind_of?(Effective::OrderItem)
      order_items.find { |oi| oi == item }
    else
      order_items.find { |oi| oi.purchasable == item }
    end

    raise("Unable to find order item for #{item}") if order_item.blank?
    order_item
  end

  removed.each { |order_item| order_item.mark_for_destruction }

  # Make sure to reset stored aggregates
  assign_attributes(subtotal: nil, tax_rate: nil, tax: nil, amount_owing: nil, surcharge_percent: nil, surcharge: nil, total: nil)

  removed.length == 1 ? removed.first : removed
end

#reportable_scopesObject

effective_reports



90
91
92
# File 'app/models/effective/order.rb', line 90

def reportable_scopes
  { purchased: nil, not_purchased: nil, deferred: nil, refunds: nil, pending_refunds: nil }
end

#send_mark_as_paid_email_to_buyer?Boolean

Returns:

  • (Boolean)


525
526
527
# File 'app/models/effective/order.rb', line 525

def send_mark_as_paid_email_to_buyer?
  EffectiveResources.truthy?(send_mark_as_paid_email_to_buyer)
end

#send_order_receipt_to_admin!Object



692
693
694
# File 'app/models/effective/order.rb', line 692

def send_order_receipt_to_admin!
  EffectiveOrders.send_email(:order_receipt_to_admin, self) if purchased?
end

#send_order_receipt_to_admin?Boolean

Returns:

  • (Boolean)


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

def send_order_receipt_to_admin?
  return false if free? && !EffectiveOrders.send_order_receipts_when_free
  EffectiveOrders.send_order_receipt_to_admin
end

#send_order_receipt_to_buyer!Object



696
697
698
# File 'app/models/effective/order.rb', line 696

def send_order_receipt_to_buyer!
  EffectiveOrders.send_email(:order_receipt_to_buyer, self) if purchased?
end

#send_order_receipt_to_buyer?Boolean

Returns:

  • (Boolean)


508
509
510
511
# File 'app/models/effective/order.rb', line 508

def send_order_receipt_to_buyer?
  return false if free? && !EffectiveOrders.send_order_receipts_when_free
  EffectiveOrders.send_order_receipt_to_buyer
end

#send_order_receipts!Object



686
687
688
689
690
# File 'app/models/effective/order.rb', line 686

def send_order_receipts!
  send_order_receipt_to_admin! if send_order_receipt_to_admin?
  send_order_receipt_to_buyer! if send_order_receipt_to_buyer?
  send_refund_notification! if send_refund_notification_to_admin?
end

#send_payment_request_to_buyer!Object



700
701
702
# File 'app/models/effective/order.rb', line 700

def send_payment_request_to_buyer!
  EffectiveOrders.send_email(:payment_request_to_buyer, self) unless purchased?
end

#send_payment_request_to_buyer?Boolean

Returns:

  • (Boolean)


513
514
515
516
517
518
# File 'app/models/effective/order.rb', line 513

def send_payment_request_to_buyer?
  return false if free? && !EffectiveOrders.send_order_receipts_when_free
  return false if refund?

  EffectiveResources.truthy?(send_payment_request_to_buyer)
end

#send_pending_order_invoice_to_buyer!Object



704
705
706
# File 'app/models/effective/order.rb', line 704

def send_pending_order_invoice_to_buyer!
  EffectiveOrders.send_email(:pending_order_invoice_to_buyer, self) unless purchased?
end

#send_refund_notification!Object



708
709
710
# File 'app/models/effective/order.rb', line 708

def send_refund_notification!
  EffectiveOrders.send_email(:refund_notification_to_admin, self) if refund?
end

#send_refund_notification_to_admin?Boolean

Returns:

  • (Boolean)


520
521
522
523
# File 'app/models/effective/order.rb', line 520

def send_refund_notification_to_admin?
  return false unless refund?
  EffectiveOrders.send_refund_notification_to_admin
end

#skip_buyer_validations?Boolean

Returns:

  • (Boolean)


529
530
531
# File 'app/models/effective/order.rb', line 529

def skip_buyer_validations?
  EffectiveResources.truthy?(skip_buyer_validations)
end

#skip_quickbooks!Object



632
633
634
# File 'app/models/effective/order.rb', line 632

def skip_quickbooks!
  sync_quickbooks!(skip: true)
end

#subtotalObject



444
445
446
# File 'app/models/effective/order.rb', line 444

def subtotal
  self[:subtotal] || get_subtotal()
end

#surchargeObject



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

def surcharge
  self[:surcharge] || get_surcharge()
end

#surcharge_percentObject



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

def surcharge_percent
  self[:surcharge_percent] || get_surcharge_percent()
end

#surcharge_taxObject



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

def surcharge_tax
  self[:surcharge_tax] || get_surcharge_tax()
end

#sync_quickbooks!(skip:) ⇒ Object

We support two different Quickbooks synchronization gems: effective_qb_sync and effective_qb_online



620
621
622
623
624
625
626
627
628
629
630
# File 'app/models/effective/order.rb', line 620

def sync_quickbooks!(skip:)
  if EffectiveOrders.qb_online?
    skip ? EffectiveQbOnline.skip_order!(self) : EffectiveQbOnline.sync_order!(self)
  end

  if EffectiveOrders.qb_sync?
    skip ? EffectiveQbSync.skip_order!(self) : true # Nothing to do
  end

  true
end

#taxObject



452
453
454
# File 'app/models/effective/order.rb', line 452

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

#tax_rateObject



448
449
450
# File 'app/models/effective/order.rb', line 448

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

#to_sObject



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

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

#totalObject



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

def total
  self[:total] || get_total()
end

#total_labelObject



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

def total_label
  purchased? ? 'Total Paid' : 'Total Due'
end

#total_with_surchargeObject



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

def total_with_surcharge
  get_total_with_surcharge()
end

#total_without_surchargeObject



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

def total_without_surcharge
  get_total_without_surcharge()
end

#transaction_idObject

For moneris and moneris_checkout. Just a unique value. Must be 50 characters or fewer or will raise moneris error.



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

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

#update_prices!Object



294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
# File 'app/models/effective/order.rb', line 294

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

  present_order_items.each do |item|
    purchasable = item.purchasable

    if purchasable.blank? || purchasable.marked_for_destruction?
      item.mark_for_destruction
    else
      item.assign_purchasable_attributes
    end
  end

  save!
end

#was_purchased?Boolean

Returns:

  • (Boolean)


420
421
422
# File 'app/models/effective/order.rb', line 420

def was_purchased?
  state_was == EffectiveOrders::PURCHASED
end