effective_orders 4.6.2 → 5.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/MIT-LICENSE +1 -1
  3. data/README.md +14 -86
  4. data/app/controllers/admin/customers_controller.rb +5 -16
  5. data/app/controllers/admin/order_items_controller.rb +6 -9
  6. data/app/controllers/admin/orders_controller.rb +18 -82
  7. data/app/controllers/effective/carts_controller.rb +10 -6
  8. data/app/controllers/effective/concerns/purchase.rb +12 -19
  9. data/app/controllers/effective/customers_controller.rb +4 -2
  10. data/app/controllers/effective/orders_controller.rb +29 -26
  11. data/app/controllers/effective/providers/cheque.rb +3 -1
  12. data/app/controllers/effective/providers/free.rb +3 -2
  13. data/app/controllers/effective/providers/mark_as_paid.rb +5 -4
  14. data/app/controllers/effective/providers/moneris.rb +3 -1
  15. data/app/controllers/effective/providers/paypal.rb +3 -2
  16. data/app/controllers/effective/providers/phone.rb +3 -1
  17. data/app/controllers/effective/providers/pretend.rb +4 -3
  18. data/app/controllers/effective/providers/refund.rb +4 -3
  19. data/app/controllers/effective/providers/stripe.rb +4 -3
  20. data/app/controllers/effective/subscripter_controller.rb +4 -2
  21. data/app/controllers/effective/webhooks_controller.rb +12 -3
  22. data/app/datatables/admin/effective_customers_datatable.rb +7 -3
  23. data/app/datatables/admin/effective_orders_datatable.rb +2 -2
  24. data/app/datatables/effective_orders_datatable.rb +1 -3
  25. data/app/helpers/effective_orders_helper.rb +1 -7
  26. data/app/mailers/effective/orders_mailer.rb +131 -96
  27. data/app/models/concerns/acts_as_purchasable.rb +0 -11
  28. data/app/models/concerns/acts_as_subscribable.rb +0 -6
  29. data/app/models/effective/cart.rb +7 -5
  30. data/app/models/effective/cart_item.rb +7 -4
  31. data/app/models/effective/customer.rb +7 -6
  32. data/app/models/effective/order.rb +61 -61
  33. data/app/models/effective/order_item.rb +20 -8
  34. data/app/models/effective/product.rb +11 -6
  35. data/app/models/effective/subscription.rb +13 -12
  36. data/app/views/admin/orders/_form.html.haml +5 -9
  37. data/app/views/admin/orders/_order_item_fields.html.haml +8 -12
  38. data/app/views/effective/orders/_checkout_step2.html.haml +1 -2
  39. data/app/views/effective/orders/_order_actions.html.haml +2 -2
  40. data/app/views/effective/orders/show.html.haml +4 -0
  41. data/config/effective_orders.rb +8 -32
  42. data/config/routes.rb +16 -17
  43. data/db/migrate/01_create_effective_orders.rb.erb +4 -0
  44. data/lib/effective_orders.rb +34 -76
  45. data/lib/effective_orders/engine.rb +0 -7
  46. data/lib/effective_orders/version.rb +1 -1
  47. data/lib/generators/templates/effective_orders_mailer_preview.rb +13 -13
  48. data/lib/tasks/effective_orders_tasks.rake +2 -2
  49. metadata +2 -3
  50. data/app/models/effective/access_denied.rb +0 -17
@@ -4,17 +4,6 @@ module ActsAsPurchasable
4
4
  module Base
5
5
  def acts_as_purchasable(*options)
6
6
  @acts_as_purchasable = options || []
7
-
8
- # if table_exists?
9
- # instance = new()
10
- # raise 'must respond_to price' unless instance.respond_to?(:price)
11
- # raise 'must respond_to purchased_order_id' unless instance.respond_to?(:purchased_order_id)
12
-
13
- # if defined?(EffectiveQbSync)
14
- # raise 'must respond to qb_item_name' unless instance.respond_to?(:qb_item_name)
15
- # end
16
- # end
17
-
18
7
  include ::ActsAsPurchasable
19
8
  end
20
9
  end
@@ -7,12 +7,6 @@ module ActsAsSubscribable
7
7
  def acts_as_subscribable(*options)
8
8
  @acts_as_subscribable = options || []
9
9
 
10
- # if table_exists?
11
- # instance = new()
12
- # raise 'must respond to trialing_until' unless instance.respond_to?(:trialing_until) || !EffectiveOrders.trial?
13
- # raise 'must respond to subscription_status' unless instance.respond_to?(:subscription_status)
14
- # end
15
-
16
10
  include ::ActsAsSubscribable
17
11
  (ActsAsSubscribable.descendants ||= []) << self
18
12
  end
@@ -2,14 +2,16 @@ module Effective
2
2
  class Cart < ActiveRecord::Base
3
3
  self.table_name = EffectiveOrders.carts_table_name.to_s
4
4
 
5
- belongs_to :user, optional: true # Optional. We want non-logged-in users to have carts too.
6
- has_many :cart_items, -> { order(:id) }, dependent: :delete_all, class_name: 'Effective::CartItem'
5
+ belongs_to :user, polymorphic: true, optional: true # Optional. We want non-logged-in users to have carts too.
7
6
 
7
+ has_many :cart_items, -> { order(:id) }, inverse_of: :cart, dependent: :delete_all
8
8
  accepts_nested_attributes_for :cart_items
9
9
 
10
- # Attributes
11
- # cart_items_count :integer
12
- # timestamps
10
+ effective_resource do
11
+ cart_items_count :integer
12
+
13
+ timestamps
14
+ end
13
15
 
14
16
  scope :deep, -> { includes(cart_items: :purchasable) }
15
17
 
@@ -2,12 +2,15 @@ module Effective
2
2
  class CartItem < ActiveRecord::Base
3
3
  self.table_name = EffectiveOrders.cart_items_table_name.to_s
4
4
 
5
- belongs_to :cart, counter_cache: true, class_name: 'Effective::Cart'
5
+ belongs_to :cart, counter_cache: true
6
6
  belongs_to :purchasable, polymorphic: true
7
7
 
8
- # Attributes
9
- # quantity :integer
10
- # timestamps
8
+ effective_resource do
9
+ unique :string
10
+ quantity :integer
11
+
12
+ timestamps
13
+ end
11
14
 
12
15
  validates :purchasable, presence: true
13
16
  validates :quantity, presence: true
@@ -4,16 +4,17 @@ module Effective
4
4
 
5
5
  attr_accessor :stripe_customer
6
6
 
7
- belongs_to :user
7
+ belongs_to :user, polymorphic: true
8
8
  has_many :subscriptions, -> { includes(:subscribable) }, class_name: 'Effective::Subscription', foreign_key: 'customer_id'
9
9
  accepts_nested_attributes_for :subscriptions
10
10
 
11
- # Attributes
12
- # stripe_customer_id :string # cus_xja7acoa03
13
- # payment_method_id :string # Last payment method used
14
- # active_card :string # **** **** **** 4242 Visa 05/12
11
+ effective_resource do
12
+ stripe_customer_id :string # cus_xja7acoa03
13
+ payment_method_id :string # Last payment method used
14
+ active_card :string # **** **** **** 4242 Visa 05/12
15
15
 
16
- # timestamps
16
+ timestamps
17
+ end
17
18
 
18
19
  scope :deep, -> { includes(subscriptions: :subscribable) }
19
20
 
@@ -31,35 +31,37 @@ module Effective
31
31
  # If we want to use orders in a has_many way
32
32
  belongs_to :parent, polymorphic: true, optional: true
33
33
 
34
- belongs_to :user, validate: false # This is the buyer/user of the order. We validate it below.
35
- has_many :order_items, -> { order(:id) }, inverse_of: :order, class_name: 'Effective::OrderItem', dependent: :delete_all
34
+ belongs_to :user, polymorphic: true, validate: false # This is the buyer/user of the order. We validate it below.
35
+ has_many :order_items, -> { order(:id) }, inverse_of: :order, dependent: :delete_all
36
36
 
37
37
  accepts_nested_attributes_for :order_items, allow_destroy: false, reject_if: :all_blank
38
38
  accepts_nested_attributes_for :user, allow_destroy: false, update_only: true
39
39
 
40
40
  # Attributes
41
- # state :string
42
- # purchased_at :datetime
43
- #
44
- # note :text # From buyer to admin
45
- # note_to_buyer :text # From admin to buyer
46
- # note_internal :text # Internal admin only
47
- #
48
- # billing_name :string # name of buyer
49
- # email :string # same as user.email
50
- # cc :string # can be set by admin
51
- #
52
- # payment :text # serialized hash containing all the payment details.
53
- # payment_provider :string
54
- # payment_card :string
55
- #
56
- # tax_rate :decimal, precision: 6, scale: 3
57
- #
58
- # subtotal :integer
59
- # tax :integer
60
- # total :integer
61
- #
62
- # timestamps
41
+ effective_resource do
42
+ state :string
43
+ purchased_at :datetime
44
+
45
+ note :text # From buyer to admin
46
+ note_to_buyer :text # From admin to buyer
47
+ note_internal :text # Internal admin only
48
+
49
+ billing_name :string # name of buyer
50
+ email :string # same as user.email
51
+ cc :string # can be set by admin
52
+
53
+ payment :text # serialized hash containing all the payment details.
54
+ payment_provider :string
55
+ payment_card :string
56
+
57
+ tax_rate :decimal, precision: 6, scale: 3
58
+
59
+ subtotal :integer
60
+ tax :integer
61
+ total :integer
62
+
63
+ timestamps
64
+ end
63
65
 
64
66
  serialize :payment, Hash
65
67
 
@@ -73,7 +75,7 @@ module Effective
73
75
  end
74
76
 
75
77
  before_save(if: -> { state_was == EffectiveOrders::PURCHASED }) do
76
- raise Exception.new('cannot change state of purchased order') unless purchased?
78
+ raise EffectiveOrders::AlreadyPurchasedException.new('cannot unpurchase an order') unless purchased?
77
79
  end
78
80
 
79
81
  # Order validations
@@ -314,15 +316,15 @@ module Effective
314
316
  end
315
317
 
316
318
  def send_payment_request_to_buyer?
317
- truthy?(send_payment_request_to_buyer) && !free? && !refund?
319
+ EffectiveResources.truthy?(send_payment_request_to_buyer) && !free? && !refund?
318
320
  end
319
321
 
320
322
  def send_mark_as_paid_email_to_buyer?
321
- truthy?(send_mark_as_paid_email_to_buyer)
323
+ EffectiveResources.truthy?(send_mark_as_paid_email_to_buyer)
322
324
  end
323
325
 
324
326
  def skip_buyer_validations?
325
- truthy?(skip_buyer_validations)
327
+ EffectiveResources.truthy?(skip_buyer_validations)
326
328
  end
327
329
 
328
330
  # This is called from admin/orders#create
@@ -330,6 +332,8 @@ module Effective
330
332
  # It skips any address or bad user validations
331
333
  # It's basically the same as save! on a new order, except it might send the payment request to buyer
332
334
  def pending!
335
+ return false if purchased?
336
+
333
337
  self.state = EffectiveOrders::PENDING
334
338
  self.addresses.clear if addresses.any? { |address| address.valid? == false }
335
339
  save!
@@ -340,6 +344,7 @@ module Effective
340
344
 
341
345
  # Used by admin checkout only
342
346
  def confirm!
347
+ return false if purchased?
343
348
  update!(state: EffectiveOrders::CONFIRMED)
344
349
  end
345
350
 
@@ -357,38 +362,31 @@ module Effective
357
362
 
358
363
  # Effective::Order.new(items: Product.first, user: User.first).purchase!(email: false)
359
364
  def purchase!(payment: 'none', provider: 'none', card: 'none', email: true, skip_buyer_validations: false)
360
- return false if purchased?
361
- error = nil
362
-
363
- assign_attributes(
364
- state: EffectiveOrders::PURCHASED,
365
- payment: payment_to_h(payment),
366
- payment_provider: provider,
367
- payment_card: (card.presence || 'none'),
368
- skip_buyer_validations: skip_buyer_validations
369
- )
365
+ # Assign attributes
366
+ self.state = EffectiveOrders::PURCHASED
367
+ self.skip_buyer_validations = skip_buyer_validations
370
368
 
369
+ self.payment_provider ||= provider
370
+ self.payment_card ||= (card.presence || 'none')
371
371
  self.purchased_at ||= Time.zone.now
372
+ self.payment = payment_to_h(payment) if self.payment.blank?
372
373
 
373
- Effective::Order.transaction do
374
- begin
374
+ begin
375
+ Effective::Order.transaction do
375
376
  run_purchasable_callbacks(:before_purchase)
376
377
  save!
377
378
  update_purchasables_purchased_order!
378
- rescue => e
379
- self.state = state_was
380
- self.purchased_at = nil
381
-
382
- error = e.message
383
- raise ::ActiveRecord::Rollback
384
379
  end
385
- end
380
+ rescue => e
381
+ Effective::Order.transaction do
382
+ save!(validate: false)
383
+ update_purchasables_purchased_order!
384
+ end
386
385
 
387
- raise "Failed to purchase order: #{error || errors.full_messages.to_sentence}" unless error.nil?
386
+ raise(e)
387
+ end
388
388
 
389
389
  run_purchasable_callbacks(:after_purchase)
390
-
391
- send_refund_notification! if email && refund?
392
390
  send_order_receipts! if email
393
391
 
394
392
  true
@@ -451,6 +449,7 @@ module Effective
451
449
  def send_order_receipts!
452
450
  send_order_receipt_to_admin! if EffectiveOrders.mailer[:send_order_receipt_to_admin]
453
451
  send_order_receipt_to_buyer! if EffectiveOrders.mailer[:send_order_receipt_to_buyer]
452
+ send_refund_notification! if refund?
454
453
  end
455
454
 
456
455
  def send_order_receipt_to_admin!
@@ -474,7 +473,7 @@ module Effective
474
473
  end
475
474
 
476
475
  def skip_qb_sync!
477
- defined?(EffectiveQbSync) ? EffectiveQbSync.skip_order!(self) : true
476
+ EffectiveOrders.use_effective_qb_sync ? EffectiveQbSync.skip_order!(self) : true
478
477
  end
479
478
 
480
479
  protected
@@ -541,22 +540,23 @@ module Effective
541
540
  order_items.each { |oi| oi.purchasable.public_send(name, self, oi) if oi.purchasable.respond_to?(name) }
542
541
  end
543
542
 
544
- def send_email(email, *mailer_args)
543
+ def send_email(email, *args)
544
+ raise('expected args to be an Array') unless args.kind_of?(Array)
545
+
546
+ if defined?(Tenant)
547
+ tenant = Tenant.current || raise('expected a current tenant')
548
+ args << { tenant: tenant }
549
+ end
550
+
551
+ deliver_method = EffectiveOrders.mailer[:deliver_method] || EffectiveResources.deliver_method
552
+
545
553
  begin
546
- Effective::OrdersMailer.public_send(email, *mailer_args).public_send(EffectiveOrders.mailer[:deliver_method])
554
+ EffectiveOrders.mailer_klass.send(email, *args).send(deliver_method)
547
555
  rescue => e
548
556
  raise if Rails.env.development? || Rails.env.test?
549
557
  end
550
558
  end
551
559
 
552
- def truthy?(value)
553
- if defined?(::ActiveRecord::ConnectionAdapters::Column::TRUE_VALUES) # Rails <5
554
- ::ActiveRecord::ConnectionAdapters::Column::TRUE_VALUES.include?(value)
555
- else
556
- ::ActiveRecord::Type::Boolean.new.cast(value)
557
- end
558
- end
559
-
560
560
  def payment_to_h(payment)
561
561
  if payment.respond_to?(:to_unsafe_h)
562
562
  payment.to_unsafe_h.to_h
@@ -2,15 +2,21 @@ module Effective
2
2
  class OrderItem < ActiveRecord::Base
3
3
  self.table_name = EffectiveOrders.order_items_table_name.to_s
4
4
 
5
- belongs_to :order, class_name: 'Effective::Order'
5
+ belongs_to :order
6
6
  belongs_to :purchasable, polymorphic: true
7
7
 
8
- # Attributes
9
- # name :string
10
- # quantity :integer
11
- # price :integer, default: 0
12
- # tax_exempt :boolean
13
- # timestamps
8
+ if defined?(EffectiveQbSync)
9
+ has_one :qb_order_item
10
+ end
11
+
12
+ effective_resource do
13
+ name :string
14
+ quantity :integer
15
+ price :integer
16
+ tax_exempt :boolean
17
+
18
+ timestamps
19
+ end
14
20
 
15
21
  validates :purchasable, associated: true, presence: true
16
22
  accepts_nested_attributes_for :purchasable
@@ -24,7 +30,7 @@ module Effective
24
30
  scope :purchased_by, lambda { |user| where(order_id: Effective::Order.purchased_by(user)) }
25
31
 
26
32
  def to_s
27
- (quantity || 0) > 1 ? "#{quantity}x #{name}" : name
33
+ ((quantity || 0) > 1 ? "#{quantity}x #{name}" : name) || 'order item'
28
34
  end
29
35
 
30
36
  def purchased_download_url
@@ -59,5 +65,11 @@ module Effective
59
65
  end
60
66
  end
61
67
 
68
+ # first or build
69
+ def qb_item_name
70
+ raise('expected EffectiveQbSync gem') unless defined?(EffectiveQbSync)
71
+ (qb_order_item || build_qb_order_item(name: purchasable.qb_item_name)).name
72
+ end
73
+
62
74
  end
63
75
  end
@@ -4,12 +4,17 @@ module Effective
4
4
 
5
5
  acts_as_purchasable
6
6
 
7
- # Attributes
8
- # name :string
9
- # price :integer, default: 0
10
- # tax_exempt :boolean, default: false
11
- #
12
- # timestamps
7
+ # belongs_to :purchased_order_id
8
+
9
+ effective_resource do
10
+ name :string
11
+ qb_item_name :string
12
+
13
+ price :integer
14
+ tax_exempt :boolean
15
+
16
+ timestamps
17
+ end
13
18
 
14
19
  validates :name, presence: true
15
20
  validates :price, presence: true
@@ -4,20 +4,21 @@ module Effective
4
4
 
5
5
  attr_accessor :stripe_subscription
6
6
 
7
- belongs_to :customer, class_name: 'Effective::Customer', counter_cache: true
7
+ belongs_to :customer, counter_cache: true
8
8
  belongs_to :subscribable, polymorphic: true
9
9
 
10
- # Attributes
11
- # stripe_plan_id :string
12
- # stripe_subscription_id :string
13
- # name :string
14
- # description :string
15
- # interval :string
16
- # quantity :integer
17
- #
18
- # status :string
19
- #
20
- # timestamps
10
+ effective_resource do
11
+ stripe_plan_id :string
12
+ stripe_subscription_id :string
13
+ name :string
14
+ description :string
15
+ interval :string
16
+ quantity :integer
17
+
18
+ status :string
19
+
20
+ timestamps
21
+ end
21
22
 
22
23
  before_validation(if: -> { plan && (stripe_plan_id_changed? || new_record?) }) do
23
24
  self.name = plan[:name]
@@ -1,19 +1,15 @@
1
1
  = effective_form_with(model: [:admin, order], url: (order.persisted? ? effective_orders.admin_order_path(order) : effective_orders.admin_orders_path)) do |f|
2
2
  - if f.object.new_record?
3
- = f.select :user_id, @users || User.all.to_a.sort { |user1, user2| user1.to_s <=> user2.to_s },
3
+ - user_collection = current_user.class.respond_to?(:sorted) ? current_user.class.sorted : current_user.class.all
4
+
5
+ = f.select :user_id, (@users || user_collection),
4
6
  label: 'Buyer', required: true, hint: 'The user that should purchase this order.'
5
7
 
6
8
  = f.email_cc_field :cc, hint: "Cc the above on any emailed receipts or payment requests."
7
9
 
8
10
  %h2 Order Items
9
- .order_items
10
- - f.object.order_items.build unless f.object.order_items.present?
11
-
12
- = f.fields_for :order_items, f.object.order_items do |order_item|
13
- = render 'order_item_fields', f: order_item
14
-
15
- .links
16
- = link_to_add_association (icon('plus') + 'Add item'), f, :order_items, class: 'btn btn-secondary', partial: 'order_item_fields'
11
+ = f.has_many :order_items do |fc|
12
+ = render 'order_item_fields', f: fc
17
13
 
18
14
  %hr
19
15
 
@@ -1,14 +1,10 @@
1
- .nested-fields.order-item
2
- .row.align-items-center
3
- = f.fields_for :purchasable, (f.object.purchasable || Effective::Product.new) do |pf|
4
- .col-md-2= f.number_field :quantity, input_html: { value: f.object.quantity || 1, min: 1 }
5
- .col-md-4= pf.text_field :name, maxlength: 255
6
- .col-md-2= pf.price_field :price
1
+ .row.align-items-center
2
+ = f.fields_for :purchasable, (f.object.purchasable || Effective::Product.new) do |pf|
3
+ .col= f.number_field :quantity, input_html: { value: f.object.quantity || 1, min: 1 }
4
+ .col= pf.text_field :name
5
+ .col= pf.price_field :price
7
6
 
8
- - if defined?(EffectiveQbSync)
9
- .col-md-2= pf.text_field :qb_item_name, maxlength: 255, label: 'Quickbooks Item'
7
+ - if EffectiveOrders.use_effective_qb_sync
8
+ .col= pf.text_field :qb_item_name, label: 'Quickbooks Item'
10
9
 
11
- .col-md-2.mt-4= pf.check_box :tax_exempt, label: "Tax&nbsp;Exempt", title: 'When checked, tax will not be applied to this item'
12
- .col-md-1
13
- = link_to_remove_association(f, 'data-confirm': 'Remove?') do
14
- = icon('trash-2', class: 'text-danger')
10
+ .col= pf.check_box :tax_exempt, label: "Tax&nbsp;Exempt", title: 'When checked, tax will not be applied to this item'