effective_orders 4.6.0 → 5.0.0

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 (47) hide show
  1. checksums.yaml +4 -4
  2. data/MIT-LICENSE +1 -1
  3. data/README.md +11 -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 +17 -81
  7. data/app/controllers/effective/carts_controller.rb +10 -6
  8. data/app/controllers/effective/customers_controller.rb +4 -2
  9. data/app/controllers/effective/orders_controller.rb +27 -23
  10. data/app/controllers/effective/providers/cheque.rb +3 -1
  11. data/app/controllers/effective/providers/free.rb +3 -1
  12. data/app/controllers/effective/providers/mark_as_paid.rb +4 -2
  13. data/app/controllers/effective/providers/moneris.rb +3 -1
  14. data/app/controllers/effective/providers/paypal.rb +3 -2
  15. data/app/controllers/effective/providers/phone.rb +3 -1
  16. data/app/controllers/effective/providers/pretend.rb +3 -1
  17. data/app/controllers/effective/providers/refund.rb +3 -1
  18. data/app/controllers/effective/providers/stripe.rb +3 -1
  19. data/app/controllers/effective/subscripter_controller.rb +4 -2
  20. data/app/controllers/effective/webhooks_controller.rb +12 -3
  21. data/app/datatables/admin/effective_customers_datatable.rb +7 -3
  22. data/app/datatables/admin/effective_orders_datatable.rb +2 -2
  23. data/app/datatables/effective_orders_datatable.rb +1 -1
  24. data/app/mailers/effective/orders_mailer.rb +131 -96
  25. data/app/models/concerns/acts_as_purchasable.rb +0 -11
  26. data/app/models/concerns/acts_as_subscribable.rb +0 -6
  27. data/app/models/effective/cart.rb +7 -5
  28. data/app/models/effective/cart_item.rb +7 -4
  29. data/app/models/effective/customer.rb +7 -6
  30. data/app/models/effective/order.rb +51 -38
  31. data/app/models/effective/order_item.rb +10 -8
  32. data/app/models/effective/product.rb +9 -6
  33. data/app/models/effective/subscription.rb +13 -12
  34. data/app/views/admin/orders/_form.html.haml +5 -9
  35. data/app/views/admin/orders/_order_item_fields.html.haml +8 -12
  36. data/app/views/effective/orders/_checkout_step2.html.haml +1 -2
  37. data/app/views/effective/orders/_order_actions.html.haml +1 -1
  38. data/config/effective_orders.rb +8 -32
  39. data/config/routes.rb +16 -17
  40. data/db/migrate/01_create_effective_orders.rb.erb +4 -0
  41. data/lib/effective_orders.rb +34 -76
  42. data/lib/effective_orders/engine.rb +0 -7
  43. data/lib/effective_orders/version.rb +1 -1
  44. data/lib/generators/templates/effective_orders_mailer_preview.rb +13 -13
  45. data/lib/tasks/effective_orders_tasks.rake +2 -2
  46. metadata +1 -2
  47. 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,41 @@ 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
+
37
+ if defined?(EffectiveQbSync)
38
+ has_one :qb_order_item
39
+ end
36
40
 
37
41
  accepts_nested_attributes_for :order_items, allow_destroy: false, reject_if: :all_blank
38
42
  accepts_nested_attributes_for :user, allow_destroy: false, update_only: true
39
43
 
40
44
  # 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
45
+ effective_resource do
46
+ state :string
47
+ purchased_at :datetime
48
+
49
+ note :text # From buyer to admin
50
+ note_to_buyer :text # From admin to buyer
51
+ note_internal :text # Internal admin only
52
+
53
+ billing_name :string # name of buyer
54
+ email :string # same as user.email
55
+ cc :string # can be set by admin
56
+
57
+ payment :text # serialized hash containing all the payment details.
58
+ payment_provider :string
59
+ payment_card :string
60
+
61
+ tax_rate :decimal, precision: 6, scale: 3
62
+
63
+ subtotal :integer
64
+ tax :integer
65
+ total :integer
66
+
67
+ timestamps
68
+ end
63
69
 
64
70
  serialize :payment, Hash
65
71
 
@@ -255,6 +261,12 @@ module Effective
255
261
  end
256
262
  end
257
263
 
264
+ # first or build
265
+ def qb_item_name
266
+ raise('expected EffectiveQbSync gem') unless defined?(EffectiveQbSync)
267
+ (qb_order_item || build_qb_order_item(name: purchasable.qb_item_name)).name
268
+ end
269
+
258
270
  def pending?
259
271
  state == EffectiveOrders::PENDING
260
272
  end
@@ -310,15 +322,15 @@ module Effective
310
322
  end
311
323
 
312
324
  def send_payment_request_to_buyer?
313
- truthy?(send_payment_request_to_buyer) && !free? && !refund?
325
+ EffectiveResources.truthy?(send_payment_request_to_buyer) && !free? && !refund?
314
326
  end
315
327
 
316
328
  def send_mark_as_paid_email_to_buyer?
317
- truthy?(send_mark_as_paid_email_to_buyer)
329
+ EffectiveResources.truthy?(send_mark_as_paid_email_to_buyer)
318
330
  end
319
331
 
320
332
  def skip_buyer_validations?
321
- truthy?(skip_buyer_validations)
333
+ EffectiveResources.truthy?(skip_buyer_validations)
322
334
  end
323
335
 
324
336
  # This is called from admin/orders#create
@@ -470,7 +482,7 @@ module Effective
470
482
  end
471
483
 
472
484
  def skip_qb_sync!
473
- defined?(EffectiveQbSync) ? EffectiveQbSync.skip_order!(self) : true
485
+ EffectiveOrders.use_effective_qb_sync ? EffectiveQbSync.skip_order!(self) : true
474
486
  end
475
487
 
476
488
  protected
@@ -537,22 +549,23 @@ module Effective
537
549
  order_items.each { |oi| oi.purchasable.public_send(name, self, oi) if oi.purchasable.respond_to?(name) }
538
550
  end
539
551
 
540
- def send_email(email, *mailer_args)
552
+ def send_email(email, *args)
553
+ raise('expected args to be an Array') unless args.kind_of?(Array)
554
+
555
+ if defined?(Tenant)
556
+ tenant = Tenant.current || raise('expected a current tenant')
557
+ args << { tenant: tenant }
558
+ end
559
+
560
+ deliver_method = EffectiveOrders.mailer[:deliver_method] || EffectiveResources.deliver_method
561
+
541
562
  begin
542
- Effective::OrdersMailer.public_send(email, *mailer_args).public_send(EffectiveOrders.mailer[:deliver_method])
563
+ EffectiveOrders.mailer_klass.send(email, *args).send(deliver_method)
543
564
  rescue => e
544
565
  raise if Rails.env.development? || Rails.env.test?
545
566
  end
546
567
  end
547
568
 
548
- def truthy?(value)
549
- if defined?(::ActiveRecord::ConnectionAdapters::Column::TRUE_VALUES) # Rails <5
550
- ::ActiveRecord::ConnectionAdapters::Column::TRUE_VALUES.include?(value)
551
- else
552
- ::ActiveRecord::Type::Boolean.new.cast(value)
553
- end
554
- end
555
-
556
569
  def payment_to_h(payment)
557
570
  if payment.respond_to?(:to_unsafe_h)
558
571
  payment.to_unsafe_h.to_h
@@ -2,15 +2,17 @@ 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
+ effective_resource do
9
+ name :string
10
+ quantity :integer
11
+ price :integer
12
+ tax_exempt :boolean
13
+
14
+ timestamps
15
+ end
14
16
 
15
17
  validates :purchasable, associated: true, presence: true
16
18
  accepts_nested_attributes_for :purchasable
@@ -24,7 +26,7 @@ module Effective
24
26
  scope :purchased_by, lambda { |user| where(order_id: Effective::Order.purchased_by(user)) }
25
27
 
26
28
  def to_s
27
- (quantity || 0) > 1 ? "#{quantity}x #{name}" : name
29
+ ((quantity || 0) > 1 ? "#{quantity}x #{name}" : name) || 'order item'
28
30
  end
29
31
 
30
32
  def purchased_download_url
@@ -4,12 +4,15 @@ 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
+ effective_resource do
8
+ name :string
9
+ qb_item_name :string
10
+
11
+ price :integer
12
+ tax_exempt :boolean
13
+
14
+ timestamps
15
+ end
13
16
 
14
17
  validates :name, presence: true
15
18
  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'
@@ -30,8 +30,7 @@
30
30
  %p.my-4.text-center - or -
31
31
  = render partial: '/effective/orders/deferred/form', locals: provider_locals
32
32
 
33
- - if EffectiveOrders.authorized?(controller, :admin, :effective_orders) && order.user != current_user
33
+ - if EffectiveResources.authorized?(controller, :admin, :effective_orders) && order.user != current_user
34
34
  - if EffectiveOrders.mark_as_paid?
35
35
  .effective-order-admin-purchase-actions
36
36
  = render partial: '/effective/orders/mark_as_paid/form', locals: provider_locals
37
-
@@ -7,7 +7,7 @@
7
7
  class: 'btn btn-secondary',
8
8
  data: { confirm: "Send receipt to #{order.emails_send_to}?" }
9
9
 
10
- - if order.persisted? && EffectiveOrders.authorized?(controller, :admin, :effective_orders)
10
+ - if order.persisted? && EffectiveResources.authorized?(controller, :admin, :effective_orders)
11
11
  - if order.pending? || order.confirmed? || order.deferred?
12
12
  = link_to 'Email request for payment to buyer', effective_orders.send_payment_request_admin_order_path(order),
13
13
  class: 'btn btn-secondary',
@@ -10,38 +10,8 @@ EffectiveOrders.setup do |config|
10
10
  config.subscriptions_table_name = :subscriptions
11
11
  config.products_table_name = :products
12
12
 
13
- # Authorization Method
14
- #
15
- # This method is called by all controller actions with the appropriate action and resource
16
- # If the method returns false, an Effective::AccessDenied Error will be raised (see README.md for complete info)
17
- #
18
- # Use via Proc (and with CanCan):
19
- # config.authorization_method = Proc.new { |controller, action, resource| can?(action, resource) }
20
- #
21
- # Use via custom method:
22
- # config.authorization_method = :my_authorization_method
23
- #
24
- # And then in your application_controller.rb:
25
- #
26
- # def my_authorization_method(action, resource)
27
- # current_user.is?(:admin)
28
- # end
29
- #
30
- # Or disable the check completely:
31
- # config.authorization_method = false
32
- config.authorization_method = Proc.new { |controller, action, resource| authorize!(action, resource) } # CanCanCan
33
-
34
13
  # Layout Settings
35
- # Configure the Layout per controller, or all at once
36
-
37
- # config.layout = 'application' # All EffectiveOrders controllers will use this layout
38
- config.layout = {
39
- carts: 'application',
40
- orders: 'application',
41
- subscriptions: 'application',
42
- admin_customers: 'admin',
43
- admin_orders: 'admin'
44
- }
14
+ # config.layout = { application: 'application', admin: 'admin' }
45
15
 
46
16
  # Filter the @orders on admin/orders#index screen
47
17
  # config.orders_collection_scope = Proc.new { |scope| scope.where(...) }
@@ -54,6 +24,9 @@ EffectiveOrders.setup do |config|
54
24
  # Use effective_obfuscation gem to change order.id into a seemingly random 10-digit number
55
25
  config.obfuscate_order_ids = false
56
26
 
27
+ # Synchronize with Quickbooks
28
+ config.use_effective_qb_sync = false
29
+
57
30
  # If set, the orders#new screen will render effective/orders/_order_note_fields to capture any Note info
58
31
  config.collect_note = false
59
32
  config.collect_note_required = false
@@ -113,6 +86,9 @@ EffectiveOrders.setup do |config|
113
86
 
114
87
  # subject_for_subscription_trialing: Proc.new { |subscribable| "Pending Order #{order.to_param}"}
115
88
 
89
+ # Use this mailer class. You can extend.
90
+ config.mailer_class_name = 'Effective::OrdersMailer'
91
+
116
92
  config.mailer = {
117
93
  send_order_receipt_to_admin: true,
118
94
  send_order_receipt_to_buyer: true,
@@ -155,7 +131,7 @@ EffectiveOrders.setup do |config|
155
131
  default_from: 'info@example.com',
156
132
  admin_email: 'admin@example.com', # Refund notifications will also be sent here
157
133
 
158
- deliver_method: nil # When nil, will use deliver_later if active_job is configured, otherwise deliver_now
134
+ deliver_method: nil # When nil, will try deliver_later and fallback to deliver_now
159
135
  }
160
136
 
161
137
  #######################################