effective_orders 4.6.3 → 5.0.0

Sign up to get free protection for your applications and to get access to all the features.
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 +30 -26
  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 -45
  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 +2 -3
  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
 
@@ -72,10 +78,6 @@ module Effective
72
78
  self.state = EffectiveOrders::CONFIRMED if pending?
73
79
  end
74
80
 
75
- before_save(if: -> { state_was == EffectiveOrders::PURCHASED }) do
76
- raise EffectiveOrders::AlreadyPurchasedException.new('cannot unpurchase an order') unless purchased?
77
- end
78
-
79
81
  # Order validations
80
82
  validates :user_id, presence: true
81
83
  validates :email, presence: true, email: true # email and cc validators are from effective_resources
@@ -259,6 +261,12 @@ module Effective
259
261
  end
260
262
  end
261
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
+
262
270
  def pending?
263
271
  state == EffectiveOrders::PENDING
264
272
  end
@@ -314,15 +322,15 @@ module Effective
314
322
  end
315
323
 
316
324
  def send_payment_request_to_buyer?
317
- truthy?(send_payment_request_to_buyer) && !free? && !refund?
325
+ EffectiveResources.truthy?(send_payment_request_to_buyer) && !free? && !refund?
318
326
  end
319
327
 
320
328
  def send_mark_as_paid_email_to_buyer?
321
- truthy?(send_mark_as_paid_email_to_buyer)
329
+ EffectiveResources.truthy?(send_mark_as_paid_email_to_buyer)
322
330
  end
323
331
 
324
332
  def skip_buyer_validations?
325
- truthy?(skip_buyer_validations)
333
+ EffectiveResources.truthy?(skip_buyer_validations)
326
334
  end
327
335
 
328
336
  # This is called from admin/orders#create
@@ -330,8 +338,6 @@ module Effective
330
338
  # It skips any address or bad user validations
331
339
  # It's basically the same as save! on a new order, except it might send the payment request to buyer
332
340
  def pending!
333
- return false if purchased?
334
-
335
341
  self.state = EffectiveOrders::PENDING
336
342
  self.addresses.clear if addresses.any? { |address| address.valid? == false }
337
343
  save!
@@ -342,7 +348,6 @@ module Effective
342
348
 
343
349
  # Used by admin checkout only
344
350
  def confirm!
345
- return false if purchased?
346
351
  update!(state: EffectiveOrders::CONFIRMED)
347
352
  end
348
353
 
@@ -477,7 +482,7 @@ module Effective
477
482
  end
478
483
 
479
484
  def skip_qb_sync!
480
- defined?(EffectiveQbSync) ? EffectiveQbSync.skip_order!(self) : true
485
+ EffectiveOrders.use_effective_qb_sync ? EffectiveQbSync.skip_order!(self) : true
481
486
  end
482
487
 
483
488
  protected
@@ -544,22 +549,23 @@ module Effective
544
549
  order_items.each { |oi| oi.purchasable.public_send(name, self, oi) if oi.purchasable.respond_to?(name) }
545
550
  end
546
551
 
547
- 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
+
548
562
  begin
549
- Effective::OrdersMailer.public_send(email, *mailer_args).public_send(EffectiveOrders.mailer[:deliver_method])
563
+ EffectiveOrders.mailer_klass.send(email, *args).send(deliver_method)
550
564
  rescue => e
551
565
  raise if Rails.env.development? || Rails.env.test?
552
566
  end
553
567
  end
554
568
 
555
- def truthy?(value)
556
- if defined?(::ActiveRecord::ConnectionAdapters::Column::TRUE_VALUES) # Rails <5
557
- ::ActiveRecord::ConnectionAdapters::Column::TRUE_VALUES.include?(value)
558
- else
559
- ::ActiveRecord::Type::Boolean.new.cast(value)
560
- end
561
- end
562
-
563
569
  def payment_to_h(payment)
564
570
  if payment.respond_to?(:to_unsafe_h)
565
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
  #######################################