effective_orders 4.6.2 → 5.0.3

Sign up to get free protection for your applications and to get access to all the features.
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'