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)



160
161
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
190
191
# File 'app/models/effective/order.rb', line 160

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.user if items.first.respond_to?(: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



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

def confirmed_checkout
  @confirmed_checkout
end

#send_mark_as_paid_email_to_buyer ⇒ Object

Set by Admin::Orders#mark_as_paid



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

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



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

def send_payment_request_to_buyer
  @send_payment_request_to_buyer
end

#skip_buyer_validations ⇒ Object

Set by Admin::Orders#create



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

def skip_buyer_validations
  @skip_buyer_validations
end

#terms_and_conditions ⇒ Object

Yes, I agree to the terms and conditions



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

def terms_and_conditions
  @terms_and_conditions
end

Instance Method Details

#abandoned? ⇒ Boolean

Returns:

  • (Boolean)


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

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



221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
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
268
269
270
271
# File 'app/models/effective/order.rb', line 221

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



482
483
484
485
486
487
488
489
490
491
# File 'app/models/effective/order.rb', line 482

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



369
370
371
# File 'app/models/effective/order.rb', line 369

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

#billing_last_name ⇒ Object



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

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

#confirm! ⇒ Object

Used by admin checkout only



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

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

#confirmed? ⇒ Boolean

Returns:

  • (Boolean)


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

def confirmed?
  state == EffectiveOrders::CONFIRMED
end

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



562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
# File 'app/models/effective/order.rb', line 562

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


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

def declined?
  state == EffectiveOrders::DECLINED
end

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



551
552
553
554
555
556
557
558
559
560
# File 'app/models/effective/order.rb', line 551

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)


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

def deferred?
  state == EffectiveOrders::DEFERRED
end

#duplicate ⇒ Object



360
361
362
# File 'app/models/effective/order.rb', line 360

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

#emails_send_to ⇒ Object

Doesn't control anything. Purely for the flash messaging



597
598
599
# File 'app/models/effective/order.rb', line 597

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

#free? ⇒ Boolean

Returns:

  • (Boolean)


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

def free?
  total == 0
end

#label ⇒ Object



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

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



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

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

#num_items ⇒ Object



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

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

#payment_method ⇒ Object

Visa - 1234



323
324
325
326
327
328
329
330
331
332
333
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
# File 'app/models/effective/order.rb', line 323

def payment_method
  return nil unless purchased?

  provider = payment_provider if ['cheque', 'phone'].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



464
465
466
467
468
469
470
471
472
473
# File 'app/models/effective/order.rb', line 464

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)


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

def pending?
  state == EffectiveOrders::PENDING
end

#purchasables ⇒ Object



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

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

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

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



500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
# File 'app/models/effective/order.rb', line 500

def purchase!(payment: 'none', provider: 'none', card: 'none', 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_provider: provider,
    payment_card: (card.presence || 'none'),
    purchased_at: Time.zone.now,
    payment: payment_to_h(payment)
  )

  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)


389
390
391
392
393
# File 'app/models/effective/order.rb', line 389

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)


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

def refund?
  total.to_i < 0
end

#remove(*items) ⇒ Object



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 193

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
  self.total = nil
  self.subtotal = nil
  self.tax = nil

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

#send_mark_as_paid_email_to_buyer? ⇒ Boolean

Returns:

  • (Boolean)


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

def send_mark_as_paid_email_to_buyer?
  EffectiveResources.truthy?(send_mark_as_paid_email_to_buyer)
end

#send_order_receipt_to_admin! ⇒ Object



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

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)


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

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



611
612
613
# File 'app/models/effective/order.rb', line 611

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)


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

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



601
602
603
604
605
# File 'app/models/effective/order.rb', line 601

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 refund?
end

#send_payment_request_to_buyer! ⇒ Object



615
616
617
# File 'app/models/effective/order.rb', line 615

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)


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

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



619
620
621
# File 'app/models/effective/order.rb', line 619

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

#send_refund_notification! ⇒ Object



623
624
625
# File 'app/models/effective/order.rb', line 623

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

#skip_buyer_validations? ⇒ Boolean

Returns:

  • (Boolean)


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

def skip_buyer_validations?
  EffectiveResources.truthy?(skip_buyer_validations)
end

#skip_qb_sync! ⇒ Object



627
628
629
# File 'app/models/effective/order.rb', line 627

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

#subtotal ⇒ Object



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

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

#sync_quickbooks!(skip:) ⇒ Object

We support two different Quickbooks synchronization gems: effective_qb_sync and effective_qb_online



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

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



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

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

#tax_rate ⇒ Object



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

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

#to_s ⇒ Object



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

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

#total ⇒ Object



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

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

#total_label ⇒ Object



308
309
310
311
312
313
314
315
316
317
318
319
320
# File 'app/models/effective/order.rb', line 308

def total_label
  if refund? && purchased?
    'Total Paid'
  elsif purchased?
    'Total Paid'
  elsif refund? && (pending? || confirmed?)
    'Total Due'
  elsif (pending? || confirmed?)
    'Total Due'
  else
    'Total'
  end
end

#transaction_id ⇒ Object

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



365
366
367
# File 'app/models/effective/order.rb', line 365

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



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

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.price = purchasable.price
    end
  end

  save!
end