effective_orders 4.6.3 → 5.0.4

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 (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 +26 -23
  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 +4 -7
  24. data/app/datatables/effective_orders_datatable.rb +3 -7
  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 +58 -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
@@ -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'
@@ -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
-
@@ -3,11 +3,11 @@
3
3
  = link_to 'Print', '#', class: 'btn btn-primary print-button', data: { role: 'print-button' }, onClick: 'window.print(); false;'
4
4
 
5
5
  - if order.purchased?
6
- = link_to 'Email receipt to buyer', effective_orders.send_buyer_receipt_order_path(order),
6
+ = link_to 'E-mail Receipt', effective_orders.send_buyer_receipt_order_path(order),
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',
@@ -4,3 +4,7 @@
4
4
  = render_checkout(@order)
5
5
  - else
6
6
  = render @order
7
+
8
+ %hr
9
+
10
+ = link_to 'Continue', effective_orders.orders_path, class: 'btn btn-primary'
@@ -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
  #######################################
data/config/routes.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  EffectiveOrders::Engine.routes.draw do
2
- scope :module => 'effective' do
2
+ scope module: 'effective' do
3
3
  resources :orders, except: [:destroy] do
4
4
  member do
5
5
  get :purchased
@@ -7,33 +7,32 @@ EffectiveOrders::Engine.routes.draw do
7
7
  get :declined
8
8
  get :send_buyer_receipt
9
9
 
10
- post :free if EffectiveOrders.free?
11
- post :mark_as_paid if EffectiveOrders.mark_as_paid?
12
- post :cheque if EffectiveOrders.cheque?
13
- post :phone if EffectiveOrders.phone?
14
- post :pretend if EffectiveOrders.pretend?
15
- post :refund if EffectiveOrders.refund?
16
- post :stripe if EffectiveOrders.stripe?
10
+ post :free
11
+ post :mark_as_paid
12
+ post :cheque
13
+ post :phone
14
+ post :pretend
15
+ post :refund
16
+ post :stripe
17
17
  end
18
18
 
19
19
  collection do
20
20
  post :bulk_send_buyer_receipt
21
21
 
22
- post :moneris_postback if EffectiveOrders.moneris?
23
- post :paypal_postback if EffectiveOrders.paypal?
22
+ post :moneris_postback
23
+ post :paypal_postback
24
24
  end
25
25
  end
26
26
 
27
27
  post 'orders/:id', to: 'orders#update'
28
28
 
29
- if EffectiveOrders.subscriptions?
30
- match 'subscribe', to: 'subscripter#update', via: :post, as: :subscripter
31
-
32
- match 'customer/settings', to: 'customers#edit', as: :customer_settings, via: [:get]
33
- match 'customer/settings', to: 'customers#update', via: [:patch, :put, :post]
34
- match 'webhooks/stripe', to: 'webhooks#stripe', via: [:get, :post, :put]
35
- end
29
+ # Subscriptions
30
+ match 'subscribe', to: 'subscripter#update', via: :post, as: :subscripter
31
+ match 'customer/settings', to: 'customers#edit', as: :customer_settings, via: [:get]
32
+ match 'customer/settings', to: 'customers#update', via: [:patch, :put, :post]
33
+ match 'webhooks/stripe', to: 'webhooks#stripe', via: [:get, :post, :put]
36
34
 
35
+ # Carts
37
36
  match 'cart', to: 'carts#show', as: 'cart', via: :get
38
37
  match 'cart', to: 'carts#destroy', via: :delete
39
38
 
@@ -2,6 +2,7 @@ class CreateEffectiveOrders < ActiveRecord::Migration[4.2]
2
2
  def self.up
3
3
  create_table <%= @orders_table_name %> do |t|
4
4
  t.integer :user_id
5
+ t.string :user_type
5
6
 
6
7
  t.integer :parent_id
7
8
  t.string :parent_type
@@ -53,6 +54,8 @@ class CreateEffectiveOrders < ActiveRecord::Migration[4.2]
53
54
 
54
55
  create_table <%= @carts_table_name %> do |t|
55
56
  t.integer :user_id
57
+ t.string :user_type
58
+
56
59
  t.integer :cart_items_count, :default => 0
57
60
  t.timestamps
58
61
  end
@@ -76,6 +79,7 @@ class CreateEffectiveOrders < ActiveRecord::Migration[4.2]
76
79
 
77
80
  create_table <%= @customers_table_name %> do |t|
78
81
  t.integer :user_id
82
+ t.string :user_type
79
83
 
80
84
  t.string :stripe_customer_id
81
85
  t.string :payment_method_id
@@ -1,94 +1,47 @@
1
1
  require 'effective_addresses'
2
+ require 'effective_resources'
2
3
  require 'effective_orders/engine'
3
4
  require 'effective_orders/version'
4
5
 
5
6
  module EffectiveOrders
6
- PENDING = 'pending'.freeze # New orders are created in a pending state
7
- CONFIRMED = 'confirmed'.freeze # Once the order has passed checkout step 1
8
- DEFERRED = 'deferred'.freeze # Deferred providers. Cheque or Phone was selected.
9
- PURCHASED = 'purchased'.freeze # Purchased by provider
10
- DECLINED = 'declined'.freeze # Declined by provider
11
-
7
+ # Order states
8
+ PENDING = 'pending' # New orders are created in a pending state
9
+ CONFIRMED = 'confirmed' # Once the order has passed checkout step 1
10
+ DEFERRED = 'deferred' # Deferred providers. Cheque or Phone was selected.
11
+ PURCHASED = 'purchased' # Purchased by provider
12
+ DECLINED = 'declined' # Declined by provider
12
13
  STATES = { PENDING => PENDING, CONFIRMED => CONFIRMED, DEFERRED => DEFERRED, PURCHASED => PURCHASED, DECLINED => DECLINED }
13
14
 
14
15
  # Subscription statuses (as per stripe)
15
- ACTIVE = 'active'.freeze
16
- PAST_DUE = 'past_due'.freeze
17
- TRIALING = 'trialing'.freeze
18
- CANCELED = 'canceled'.freeze
16
+ ACTIVE = 'active'
17
+ PAST_DUE = 'past_due'
18
+ TRIALING = 'trialing'
19
+ CANCELED = 'canceled'
19
20
 
20
21
  STATUSES = { ACTIVE => ACTIVE, PAST_DUE => PAST_DUE, CANCELED => CANCELED, TRIALING => TRIALING }
21
22
 
22
- # The following are all valid config keys
23
- mattr_accessor :orders_table_name
24
- mattr_accessor :order_items_table_name
25
- mattr_accessor :carts_table_name
26
- mattr_accessor :cart_items_table_name
27
- mattr_accessor :customers_table_name
28
- mattr_accessor :subscriptions_table_name
29
- mattr_accessor :products_table_name
30
-
31
- mattr_accessor :authorization_method
32
-
33
- mattr_accessor :layout
34
- mattr_accessor :mailer
35
-
36
- mattr_accessor :orders_collection_scope
37
- mattr_accessor :order_tax_rate_method
38
-
39
- mattr_accessor :obfuscate_order_ids
40
- mattr_accessor :billing_address
41
- mattr_accessor :shipping_address
42
- mattr_accessor :use_address_full_name
43
-
44
- mattr_accessor :collect_note
45
- mattr_accessor :collect_note_required
46
- mattr_accessor :collect_note_message
47
-
48
- mattr_accessor :terms_and_conditions
49
- mattr_accessor :terms_and_conditions_label
50
-
51
- mattr_accessor :minimum_charge
52
-
53
- # Features
54
- mattr_accessor :free_enabled
55
- mattr_accessor :mark_as_paid_enabled
56
- mattr_accessor :pretend_enabled
57
- mattr_accessor :pretend_message
58
-
59
- # Payment processors. false or Hash
60
- mattr_accessor :cheque
61
- mattr_accessor :moneris
62
- mattr_accessor :paypal
63
- mattr_accessor :phone
64
- mattr_accessor :refund
65
- mattr_accessor :stripe
66
- mattr_accessor :subscriptions # Stripe subscriptions
67
- mattr_accessor :trial # Trial mode
68
-
69
- def self.setup
70
- yield self
71
- end
72
-
73
- def self.authorized?(controller, action, resource)
74
- @_exceptions ||= [Effective::AccessDenied, (CanCan::AccessDenied if defined?(CanCan)), (Pundit::NotAuthorizedError if defined?(Pundit))].compact
75
-
76
- return !!authorization_method unless authorization_method.respond_to?(:call)
77
- controller = controller.controller if controller.respond_to?(:controller)
78
-
79
- begin
80
- !!(controller || self).instance_exec((controller || self), action, resource, &authorization_method)
81
- rescue *@_exceptions
82
- false
83
- end
23
+ def self.config_keys
24
+ [
25
+ :orders_table_name, :order_items_table_name, :carts_table_name, :cart_items_table_name,
26
+ :customers_table_name, :subscriptions_table_name, :products_table_name,
27
+ :layout, :mailer_class_name, :mailer,
28
+ :orders_collection_scope, :order_tax_rate_method,
29
+ :obfuscate_order_ids, :use_effective_qb_sync,
30
+ :billing_address, :shipping_address, :use_address_full_name,
31
+ :collect_note, :collect_note_required, :collect_note_message,
32
+ :terms_and_conditions, :terms_and_conditions_label, :minimum_charge,
33
+
34
+ # Features
35
+ :free_enabled, :mark_as_paid_enabled, :pretend_enabled, :pretend_message,
36
+ # Payment processors. false or Hash
37
+ :cheque, :moneris, :paypal, :phone, :refund, :stripe, :subscriptions, :trial
38
+ ]
84
39
  end
85
40
 
86
- def self.authorize!(controller, action, resource)
87
- raise Effective::AccessDenied.new('Access Denied', action, resource) unless authorized?(controller, action, resource)
88
- end
41
+ include EffectiveGem
89
42
 
90
43
  def self.permitted_params
91
- [
44
+ @permitted_params ||= [
92
45
  :cc, :note, :terms_and_conditions, :confirmed_checkout,
93
46
  billing_address: EffectiveAddresses.permitted_params,
94
47
  shipping_address: EffectiveAddresses.permitted_params,
@@ -169,6 +122,11 @@ module EffectiveOrders
169
122
  [('cheque' if cheque?), ('phone' if phone?)].compact
170
123
  end
171
124
 
125
+ def self.mailer_klass
126
+ name = mailer_class_name.presence || 'Effective::OrdersMailer'
127
+ name.safe_constantize || raise("unable to constantize mailer class. check config.mailer_class_name")
128
+ end
129
+
172
130
  def self.can_skip_checkout_step1?
173
131
  return false if require_billing_address
174
132
  return false if require_shipping_address