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)



236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
# File 'app/models/effective/order.rb', line 236

def initialize(atts = nil, &block)
  super(status: :pending) # Initialize with status 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_checkout ⇒ Object

Set on the Checkout Step 1



41
42
43
# File 'app/models/effective/order.rb', line 41

def confirmed_checkout
  @confirmed_checkout
end

#send_mark_as_paid_email_to_buyer ⇒ Object

Set by Admin::Orders#mark_as_paid



45
46
47
# File 'app/models/effective/order.rb', line 45

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



44
45
46
# File 'app/models/effective/order.rb', line 44

def send_payment_request_to_buyer
  @send_payment_request_to_buyer
end

#skip_buyer_validations ⇒ Object

Set by Admin::Orders#create



46
47
48
# File 'app/models/effective/order.rb', line 46

def skip_buyer_validations
  @skip_buyer_validations
end

#terms_and_conditions ⇒ Object

Yes, I agree to the terms and conditions



40
41
42
# File 'app/models/effective/order.rb', line 40

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



295
296
297
298
299
300
301
302
303
304
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
334
335
336
337
338
339
340
341
342
343
# File 'app/models/effective/order.rb', line 295

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



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

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



593
594
595
596
597
598
599
600
601
602
# File 'app/models/effective/order.rb', line 593

def assign_confirmed_if_valid!
  return unless pending?

  assign_attributes(status: :confirmed)
  return true if valid?

  self.errors.clear
  assign_attributes(status: :pending)
  false
end

#billing_first_name ⇒ Object



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

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

#billing_last_name ⇒ Object



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

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

#confirm! ⇒ Object

Used by admin checkout only



587
588
589
590
# File 'app/models/effective/order.rb', line 587

def confirm!
  return false if purchased?
  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)


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

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

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



695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
# File 'app/models/effective/order.rb', line 695

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

  raise('order already purchased') if purchased?

  error = nil

  assign_attributes(
    skip_buyer_validations: true,

    status: :declined,
    purchased_at: nil,
    purchased_by: nil,

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

  Effective::Order.transaction do
    begin
      run_purchasable_callbacks(:before_decline)
      save!(validate: validate)
      run_purchasable_callbacks(:after_decline)
    rescue ActiveRecord::RecordInvalid => e
      self.status = status_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

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



684
685
686
687
688
689
690
691
692
693
# File 'app/models/effective/order.rb', line 684

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

  assign_attributes(payment_provider: provider)
  deferred!

  send_payment_request_to_buyer! if email

  true
end

#done? ⇒ Boolean

Returns:

  • (Boolean)


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

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

#duplicate ⇒ Object



426
427
428
# File 'app/models/effective/order.rb', line 426

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

#emails ⇒ Object

These are all the emails we send all notifications to



743
744
745
# File 'app/models/effective/order.rb', line 743

def emails
  ([purchased_by.try(:email)] + [email] + [user.try(:email)] + Array(organization.try(:billing_emails))).map(&:presence).compact.uniq
end

#emails_send_to ⇒ Object

Doesn't control anything. Purely for the flash messaging



748
749
750
# File 'app/models/effective/order.rb', line 748

def emails_send_to
  (emails + [cc.presence]).compact.uniq.to_sentence
end

#free? ⇒ Boolean

Returns:

  • (Boolean)


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

def free?
  total == 0
end

#in_progress? ⇒ Boolean

Returns:

  • (Boolean)


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

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

#label ⇒ Object



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

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!(current_user: nil) ⇒ 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



613
614
615
# File 'app/models/effective/order.rb', line 613

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

#num_items ⇒ Object



534
535
536
# File 'app/models/effective/order.rb', line 534

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

#payment ⇒ Object



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

def payment
  Hash(self[:payment])
end

#payment_method ⇒ Object

Visa - 1234



385
386
387
388
389
390
391
392
393
394
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 385

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

  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



572
573
574
575
576
577
578
579
580
581
582
583
584
# File 'app/models/effective/order.rb', line 572

def pending!
  return false if purchased?

  assign_attributes(status: :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_refund? ⇒ Boolean

Returns:

  • (Boolean)


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

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

  refund?
end

#purchasables ⇒ Object



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

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

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

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



618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
# File 'app/models/effective/order.rb', line 618

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

  raise('unable to purchase voided order') if voided?

  # Assign attributes
  assign_attributes(
    skip_buyer_validations: skip_buyer_validations,

    status: :purchased,
    purchased_at: (purchased_at.presence || Time.zone.now),
    purchased_by: (purchased_by.presence || current_user),

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

  if current_user&.email.present?
    assign_attributes(email: current_user.email)
  end

  # 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 ActiveRecord::RecordInvalid => 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)


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

def purchased?(provider = nil)
  return false if (status.to_sym != :purchased)
  return true if provider.nil? || payment_provider == provider.to_s
  false
end

#purchased_with_credit_card? ⇒ Boolean

Returns:

  • (Boolean)


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

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

#purchased_without_credit_card? ⇒ Boolean

Returns:

  • (Boolean)


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

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

#refund? ⇒ Boolean

Returns:

  • (Boolean)


523
524
525
# File 'app/models/effective/order.rb', line 523

def refund?
  total.to_i < 0
end

#remove(*items) ⇒ Object



269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
# File 'app/models/effective/order.rb', line 269

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

effective_reports



128
129
130
# File 'app/models/effective/order.rb', line 128

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)


560
561
562
# File 'app/models/effective/order.rb', line 560

def send_mark_as_paid_email_to_buyer?
  EffectiveResources.truthy?(send_mark_as_paid_email_to_buyer)
end

#send_order_receipt_to_admin! ⇒ Object



758
759
760
# File 'app/models/effective/order.rb', line 758

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)


538
539
540
541
# File 'app/models/effective/order.rb', line 538

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 Also known as: send_buyer_receipt!



762
763
764
# File 'app/models/effective/order.rb', line 762

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)


543
544
545
546
# File 'app/models/effective/order.rb', line 543

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



752
753
754
755
756
# File 'app/models/effective/order.rb', line 752

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



767
768
769
# File 'app/models/effective/order.rb', line 767

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)


548
549
550
551
552
553
# File 'app/models/effective/order.rb', line 548

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



771
772
773
# File 'app/models/effective/order.rb', line 771

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

#send_refund_notification! ⇒ Object



775
776
777
# File 'app/models/effective/order.rb', line 775

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

#send_refund_notification_to_admin? ⇒ Boolean

Returns:

  • (Boolean)


555
556
557
558
# File 'app/models/effective/order.rb', line 555

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

#skip_buyer_validations? ⇒ Boolean

Returns:

  • (Boolean)


564
565
566
# File 'app/models/effective/order.rb', line 564

def skip_buyer_validations?
  EffectiveResources.truthy?(skip_buyer_validations)
end

#skip_quickbooks! ⇒ Object



680
681
682
# File 'app/models/effective/order.rb', line 680

def skip_quickbooks!
  sync_quickbooks!(skip: true)
end

#subtotal ⇒ Object



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

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

#surcharge ⇒ Object



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

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

#surcharge_percent ⇒ Object



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

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

#surcharge_tax ⇒ Object



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

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



668
669
670
671
672
673
674
675
676
677
678
# File 'app/models/effective/order.rb', line 668

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

#tax ⇒ Object



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

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

#tax_rate ⇒ Object



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

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

#to_s ⇒ Object



362
363
364
# File 'app/models/effective/order.rb', line 362

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

#total ⇒ Object



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

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

#total_label ⇒ Object



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

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

#total_with_surcharge ⇒ Object



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

def total_with_surcharge
  get_total_with_surcharge()
end

#total_without_surcharge ⇒ Object



511
512
513
# File 'app/models/effective/order.rb', line 511

def total_without_surcharge
  get_total_without_surcharge()
end

#transaction_id ⇒ Object

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



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

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

#unvoid! ⇒ Object



737
738
739
740
# File 'app/models/effective/order.rb', line 737

def unvoid!
  raise('order must be voided to unvoid') unless voided?
  unvoided!(skip_buyer_validations: true)
end

#update_prices! ⇒ Object



345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
# File 'app/models/effective/order.rb', line 345

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

#update_purchasable_attributes ⇒ Object

Called by effective_memberships to update prices from purchasable fees Not called internally



606
607
608
# File 'app/models/effective/order.rb', line 606

def update_purchasable_attributes
  present_order_items.each { |oi| oi.update_purchasable_attributes }
end

#void! ⇒ Object



732
733
734
735
# File 'app/models/effective/order.rb', line 732

def void!
  raise('already voided') if voided?
  voided!(skip_buyer_validations: true)
end