effective_orders 4.6.1 → 4.6.2

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 +86 -11
  4. data/app/controllers/admin/customers_controller.rb +16 -5
  5. data/app/controllers/admin/order_items_controller.rb +9 -6
  6. data/app/controllers/admin/orders_controller.rb +81 -17
  7. data/app/controllers/effective/carts_controller.rb +6 -10
  8. data/app/controllers/effective/customers_controller.rb +2 -4
  9. data/app/controllers/effective/orders_controller.rb +23 -27
  10. data/app/controllers/effective/providers/cheque.rb +1 -3
  11. data/app/controllers/effective/providers/free.rb +1 -3
  12. data/app/controllers/effective/providers/mark_as_paid.rb +2 -4
  13. data/app/controllers/effective/providers/moneris.rb +1 -3
  14. data/app/controllers/effective/providers/paypal.rb +2 -3
  15. data/app/controllers/effective/providers/phone.rb +1 -3
  16. data/app/controllers/effective/providers/pretend.rb +1 -3
  17. data/app/controllers/effective/providers/refund.rb +1 -3
  18. data/app/controllers/effective/providers/stripe.rb +1 -3
  19. data/app/controllers/effective/subscripter_controller.rb +2 -4
  20. data/app/controllers/effective/webhooks_controller.rb +3 -12
  21. data/app/datatables/admin/effective_customers_datatable.rb +3 -7
  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 +96 -131
  25. data/app/models/concerns/acts_as_purchasable.rb +11 -0
  26. data/app/models/concerns/acts_as_subscribable.rb +6 -0
  27. data/app/models/effective/access_denied.rb +17 -0
  28. data/app/models/effective/cart.rb +5 -7
  29. data/app/models/effective/cart_item.rb +4 -7
  30. data/app/models/effective/customer.rb +6 -7
  31. data/app/models/effective/order.rb +42 -51
  32. data/app/models/effective/order_item.rb +8 -10
  33. data/app/models/effective/product.rb +6 -9
  34. data/app/models/effective/subscription.rb +12 -13
  35. data/app/views/admin/orders/_form.html.haml +9 -5
  36. data/app/views/admin/orders/_order_item_fields.html.haml +12 -8
  37. data/app/views/effective/orders/_checkout_step2.html.haml +2 -1
  38. data/app/views/effective/orders/_order_actions.html.haml +1 -1
  39. data/config/effective_orders.rb +32 -8
  40. data/config/routes.rb +17 -16
  41. data/db/migrate/01_create_effective_orders.rb.erb +0 -4
  42. data/lib/effective_orders.rb +76 -34
  43. data/lib/effective_orders/engine.rb +7 -0
  44. data/lib/effective_orders/version.rb +1 -1
  45. data/lib/generators/templates/effective_orders_mailer_preview.rb +13 -13
  46. data/lib/tasks/effective_orders_tasks.rake +2 -2
  47. metadata +2 -1
@@ -4,6 +4,17 @@ 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
+
7
18
  include ::ActsAsPurchasable
8
19
  end
9
20
  end
@@ -7,6 +7,12 @@ 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
+
10
16
  include ::ActsAsSubscribable
11
17
  (ActsAsSubscribable.descendants ||= []) << self
12
18
  end
@@ -0,0 +1,17 @@
1
+ unless defined?(Effective::AccessDenied)
2
+ module Effective
3
+ class AccessDenied < StandardError
4
+ attr_reader :action, :subject
5
+
6
+ def initialize(message = nil, action = nil, subject = nil)
7
+ @message = message
8
+ @action = action
9
+ @subject = subject
10
+ end
11
+
12
+ def to_s
13
+ @message || I18n.t(:'unauthorized.default', :default => 'Access Denied')
14
+ end
15
+ end
16
+ end
17
+ end
@@ -2,16 +2,14 @@ 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, polymorphic: true, optional: true # Optional. We want non-logged-in users to have carts too.
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'
6
7
 
7
- has_many :cart_items, -> { order(:id) }, inverse_of: :cart, dependent: :delete_all
8
8
  accepts_nested_attributes_for :cart_items
9
9
 
10
- effective_resource do
11
- cart_items_count :integer
12
-
13
- timestamps
14
- end
10
+ # Attributes
11
+ # cart_items_count :integer
12
+ # timestamps
15
13
 
16
14
  scope :deep, -> { includes(cart_items: :purchasable) }
17
15
 
@@ -2,15 +2,12 @@ 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
5
+ belongs_to :cart, counter_cache: true, class_name: 'Effective::Cart'
6
6
  belongs_to :purchasable, polymorphic: true
7
7
 
8
- effective_resource do
9
- unique :string
10
- quantity :integer
11
-
12
- timestamps
13
- end
8
+ # Attributes
9
+ # quantity :integer
10
+ # timestamps
14
11
 
15
12
  validates :purchasable, presence: true
16
13
  validates :quantity, presence: true
@@ -4,17 +4,16 @@ module Effective
4
4
 
5
5
  attr_accessor :stripe_customer
6
6
 
7
- belongs_to :user, polymorphic: true
7
+ belongs_to :user
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
- 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
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
15
15
 
16
- timestamps
17
- end
16
+ # timestamps
18
17
 
19
18
  scope :deep, -> { includes(subscriptions: :subscribable) }
20
19
 
@@ -31,41 +31,35 @@ 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, 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
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
40
36
 
41
37
  accepts_nested_attributes_for :order_items, allow_destroy: false, reject_if: :all_blank
42
38
  accepts_nested_attributes_for :user, allow_destroy: false, update_only: true
43
39
 
44
40
  # Attributes
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
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
69
63
 
70
64
  serialize :payment, Hash
71
65
 
@@ -78,6 +72,10 @@ module Effective
78
72
  self.state = EffectiveOrders::CONFIRMED if pending?
79
73
  end
80
74
 
75
+ before_save(if: -> { state_was == EffectiveOrders::PURCHASED }) do
76
+ raise Exception.new('cannot change state of purchased order') unless purchased?
77
+ end
78
+
81
79
  # Order validations
82
80
  validates :user_id, presence: true
83
81
  validates :email, presence: true, email: true # email and cc validators are from effective_resources
@@ -261,12 +259,6 @@ module Effective
261
259
  end
262
260
  end
263
261
 
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
-
270
262
  def pending?
271
263
  state == EffectiveOrders::PENDING
272
264
  end
@@ -322,15 +314,15 @@ module Effective
322
314
  end
323
315
 
324
316
  def send_payment_request_to_buyer?
325
- EffectiveResources.truthy?(send_payment_request_to_buyer) && !free? && !refund?
317
+ truthy?(send_payment_request_to_buyer) && !free? && !refund?
326
318
  end
327
319
 
328
320
  def send_mark_as_paid_email_to_buyer?
329
- EffectiveResources.truthy?(send_mark_as_paid_email_to_buyer)
321
+ truthy?(send_mark_as_paid_email_to_buyer)
330
322
  end
331
323
 
332
324
  def skip_buyer_validations?
333
- EffectiveResources.truthy?(skip_buyer_validations)
325
+ truthy?(skip_buyer_validations)
334
326
  end
335
327
 
336
328
  # This is called from admin/orders#create
@@ -482,7 +474,7 @@ module Effective
482
474
  end
483
475
 
484
476
  def skip_qb_sync!
485
- EffectiveOrders.use_effective_qb_sync ? EffectiveQbSync.skip_order!(self) : true
477
+ defined?(EffectiveQbSync) ? EffectiveQbSync.skip_order!(self) : true
486
478
  end
487
479
 
488
480
  protected
@@ -549,23 +541,22 @@ module Effective
549
541
  order_items.each { |oi| oi.purchasable.public_send(name, self, oi) if oi.purchasable.respond_to?(name) }
550
542
  end
551
543
 
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
-
544
+ def send_email(email, *mailer_args)
562
545
  begin
563
- EffectiveOrders.mailer_klass.send(email, *args).send(deliver_method)
546
+ Effective::OrdersMailer.public_send(email, *mailer_args).public_send(EffectiveOrders.mailer[:deliver_method])
564
547
  rescue => e
565
548
  raise if Rails.env.development? || Rails.env.test?
566
549
  end
567
550
  end
568
551
 
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
+
569
560
  def payment_to_h(payment)
570
561
  if payment.respond_to?(:to_unsafe_h)
571
562
  payment.to_unsafe_h.to_h
@@ -2,17 +2,15 @@ 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
5
+ belongs_to :order, class_name: 'Effective::Order'
6
6
  belongs_to :purchasable, polymorphic: true
7
7
 
8
- effective_resource do
9
- name :string
10
- quantity :integer
11
- price :integer
12
- tax_exempt :boolean
13
-
14
- timestamps
15
- end
8
+ # Attributes
9
+ # name :string
10
+ # quantity :integer
11
+ # price :integer, default: 0
12
+ # tax_exempt :boolean
13
+ # timestamps
16
14
 
17
15
  validates :purchasable, associated: true, presence: true
18
16
  accepts_nested_attributes_for :purchasable
@@ -26,7 +24,7 @@ module Effective
26
24
  scope :purchased_by, lambda { |user| where(order_id: Effective::Order.purchased_by(user)) }
27
25
 
28
26
  def to_s
29
- ((quantity || 0) > 1 ? "#{quantity}x #{name}" : name) || 'order item'
27
+ (quantity || 0) > 1 ? "#{quantity}x #{name}" : name
30
28
  end
31
29
 
32
30
  def purchased_download_url
@@ -4,15 +4,12 @@ module Effective
4
4
 
5
5
  acts_as_purchasable
6
6
 
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
7
+ # Attributes
8
+ # name :string
9
+ # price :integer, default: 0
10
+ # tax_exempt :boolean, default: false
11
+ #
12
+ # timestamps
16
13
 
17
14
  validates :name, presence: true
18
15
  validates :price, presence: true
@@ -4,21 +4,20 @@ module Effective
4
4
 
5
5
  attr_accessor :stripe_subscription
6
6
 
7
- belongs_to :customer, counter_cache: true
7
+ belongs_to :customer, class_name: 'Effective::Customer', counter_cache: true
8
8
  belongs_to :subscribable, polymorphic: true
9
9
 
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
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
22
21
 
23
22
  before_validation(if: -> { plan && (stripe_plan_id_changed? || new_record?) }) do
24
23
  self.name = plan[:name]
@@ -1,15 +1,19 @@
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
- - 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),
3
+ = f.select :user_id, @users || User.all.to_a.sort { |user1, user2| user1.to_s <=> user2.to_s },
6
4
  label: 'Buyer', required: true, hint: 'The user that should purchase this order.'
7
5
 
8
6
  = f.email_cc_field :cc, hint: "Cc the above on any emailed receipts or payment requests."
9
7
 
10
8
  %h2 Order Items
11
- = f.has_many :order_items do |fc|
12
- = render 'order_item_fields', f: fc
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'
13
17
 
14
18
  %hr
15
19
 
@@ -1,10 +1,14 @@
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
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
6
7
 
7
- - if EffectiveOrders.use_effective_qb_sync
8
- .col= pf.text_field :qb_item_name, label: 'Quickbooks Item'
8
+ - if defined?(EffectiveQbSync)
9
+ .col-md-2= pf.text_field :qb_item_name, maxlength: 255, label: 'Quickbooks Item'
9
10
 
10
- .col= pf.check_box :tax_exempt, label: "Tax&nbsp;Exempt", title: 'When checked, tax will not be applied to this item'
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')
@@ -30,7 +30,8 @@
30
30
  %p.my-4.text-center - or -
31
31
  = render partial: '/effective/orders/deferred/form', locals: provider_locals
32
32
 
33
- - if EffectiveResources.authorized?(controller, :admin, :effective_orders) && order.user != current_user
33
+ - if EffectiveOrders.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? && EffectiveResources.authorized?(controller, :admin, :effective_orders)
10
+ - if order.persisted? && EffectiveOrders.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,8 +10,38 @@ 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
+
13
34
  # Layout Settings
14
- # config.layout = { application: 'application', admin: 'admin' }
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
+ }
15
45
 
16
46
  # Filter the @orders on admin/orders#index screen
17
47
  # config.orders_collection_scope = Proc.new { |scope| scope.where(...) }
@@ -24,9 +54,6 @@ EffectiveOrders.setup do |config|
24
54
  # Use effective_obfuscation gem to change order.id into a seemingly random 10-digit number
25
55
  config.obfuscate_order_ids = false
26
56
 
27
- # Synchronize with Quickbooks
28
- config.use_effective_qb_sync = false
29
-
30
57
  # If set, the orders#new screen will render effective/orders/_order_note_fields to capture any Note info
31
58
  config.collect_note = false
32
59
  config.collect_note_required = false
@@ -86,9 +113,6 @@ EffectiveOrders.setup do |config|
86
113
 
87
114
  # subject_for_subscription_trialing: Proc.new { |subscribable| "Pending Order #{order.to_param}"}
88
115
 
89
- # Use this mailer class. You can extend.
90
- config.mailer_class_name = 'Effective::OrdersMailer'
91
-
92
116
  config.mailer = {
93
117
  send_order_receipt_to_admin: true,
94
118
  send_order_receipt_to_buyer: true,
@@ -131,7 +155,7 @@ EffectiveOrders.setup do |config|
131
155
  default_from: 'info@example.com',
132
156
  admin_email: 'admin@example.com', # Refund notifications will also be sent here
133
157
 
134
- deliver_method: nil # When nil, will try deliver_later and fallback to deliver_now
158
+ deliver_method: nil # When nil, will use deliver_later if active_job is configured, otherwise deliver_now
135
159
  }
136
160
 
137
161
  #######################################