effective_orders 4.6.1 → 4.6.2

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 +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
  #######################################