effective_orders 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (146) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +856 -0
  4. data/Rakefile +24 -0
  5. data/app/assets/images/effective_orders/stripe_connect.png +0 -0
  6. data/app/assets/javascripts/effective_orders/shipping_address_toggle.js.coffee +30 -0
  7. data/app/assets/javascripts/effective_orders/stripe_charges.js.coffee +26 -0
  8. data/app/assets/javascripts/effective_orders/stripe_subscriptions.js.coffee +28 -0
  9. data/app/assets/javascripts/effective_orders.js +2 -0
  10. data/app/assets/stylesheets/effective_orders/_order.scss +30 -0
  11. data/app/assets/stylesheets/effective_orders.css.scss +1 -0
  12. data/app/controllers/admin/customers_controller.rb +15 -0
  13. data/app/controllers/admin/orders_controller.rb +22 -0
  14. data/app/controllers/effective/carts_controller.rb +70 -0
  15. data/app/controllers/effective/orders_controller.rb +191 -0
  16. data/app/controllers/effective/providers/moneris.rb +94 -0
  17. data/app/controllers/effective/providers/paypal.rb +29 -0
  18. data/app/controllers/effective/providers/stripe.rb +125 -0
  19. data/app/controllers/effective/providers/stripe_connect.rb +47 -0
  20. data/app/controllers/effective/subscriptions_controller.rb +123 -0
  21. data/app/controllers/effective/webhooks_controller.rb +86 -0
  22. data/app/helpers/effective_carts_helper.rb +90 -0
  23. data/app/helpers/effective_orders_helper.rb +108 -0
  24. data/app/helpers/effective_paypal_helper.rb +37 -0
  25. data/app/helpers/effective_stripe_helper.rb +63 -0
  26. data/app/mailers/effective/orders_mailer.rb +64 -0
  27. data/app/models/concerns/acts_as_purchasable.rb +134 -0
  28. data/app/models/effective/access_denied.rb +17 -0
  29. data/app/models/effective/cart.rb +65 -0
  30. data/app/models/effective/cart_item.rb +40 -0
  31. data/app/models/effective/customer.rb +61 -0
  32. data/app/models/effective/datatables/customers.rb +45 -0
  33. data/app/models/effective/datatables/orders.rb +53 -0
  34. data/app/models/effective/order.rb +247 -0
  35. data/app/models/effective/order_item.rb +69 -0
  36. data/app/models/effective/stripe_charge.rb +35 -0
  37. data/app/models/effective/subscription.rb +95 -0
  38. data/app/models/inputs/price_field.rb +63 -0
  39. data/app/models/inputs/price_form_input.rb +7 -0
  40. data/app/models/inputs/price_formtastic_input.rb +9 -0
  41. data/app/models/inputs/price_input.rb +19 -0
  42. data/app/models/inputs/price_simple_form_input.rb +8 -0
  43. data/app/models/validators/effective/sold_out_validator.rb +7 -0
  44. data/app/views/active_admin/effective_orders/orders/_show.html.haml +70 -0
  45. data/app/views/admin/customers/_actions.html.haml +2 -0
  46. data/app/views/admin/customers/index.html.haml +10 -0
  47. data/app/views/admin/orders/index.html.haml +7 -0
  48. data/app/views/admin/orders/show.html.haml +11 -0
  49. data/app/views/effective/carts/_cart.html.haml +33 -0
  50. data/app/views/effective/carts/show.html.haml +18 -0
  51. data/app/views/effective/orders/_checkout_step_1.html.haml +39 -0
  52. data/app/views/effective/orders/_checkout_step_2.html.haml +18 -0
  53. data/app/views/effective/orders/_my_purchases.html.haml +15 -0
  54. data/app/views/effective/orders/_order.html.haml +4 -0
  55. data/app/views/effective/orders/_order_header.html.haml +21 -0
  56. data/app/views/effective/orders/_order_items.html.haml +39 -0
  57. data/app/views/effective/orders/_order_payment_details.html.haml +11 -0
  58. data/app/views/effective/orders/_order_shipping.html.haml +19 -0
  59. data/app/views/effective/orders/_order_user_fields.html.haml +10 -0
  60. data/app/views/effective/orders/checkout.html.haml +3 -0
  61. data/app/views/effective/orders/declined.html.haml +10 -0
  62. data/app/views/effective/orders/moneris/_form.html.haml +34 -0
  63. data/app/views/effective/orders/my_purchases.html.haml +6 -0
  64. data/app/views/effective/orders/my_sales.html.haml +28 -0
  65. data/app/views/effective/orders/new.html.haml +4 -0
  66. data/app/views/effective/orders/paypal/_form.html.haml +5 -0
  67. data/app/views/effective/orders/purchased.html.haml +10 -0
  68. data/app/views/effective/orders/show.html.haml +17 -0
  69. data/app/views/effective/orders/stripe/_form.html.haml +8 -0
  70. data/app/views/effective/orders/stripe/_subscription_fields.html.haml +7 -0
  71. data/app/views/effective/orders_mailer/order_receipt_to_admin.html.haml +8 -0
  72. data/app/views/effective/orders_mailer/order_receipt_to_buyer.html.haml +8 -0
  73. data/app/views/effective/orders_mailer/order_receipt_to_seller.html.haml +30 -0
  74. data/app/views/effective/subscriptions/index.html.haml +16 -0
  75. data/app/views/effective/subscriptions/new.html.haml +10 -0
  76. data/app/views/effective/subscriptions/show.html.haml +49 -0
  77. data/config/routes.rb +57 -0
  78. data/db/migrate/01_create_effective_orders.rb.erb +91 -0
  79. data/db/upgrade/02_upgrade_effective_orders_from03x.rb.erb +29 -0
  80. data/db/upgrade/upgrade_price_column_on_table.rb.erb +17 -0
  81. data/lib/effective_orders/engine.rb +52 -0
  82. data/lib/effective_orders/version.rb +3 -0
  83. data/lib/effective_orders.rb +76 -0
  84. data/lib/generators/effective_orders/install_generator.rb +38 -0
  85. data/lib/generators/effective_orders/upgrade_from03x_generator.rb +34 -0
  86. data/lib/generators/effective_orders/upgrade_price_column_generator.rb +34 -0
  87. data/lib/generators/templates/README +1 -0
  88. data/lib/generators/templates/effective_orders.rb +210 -0
  89. data/spec/controllers/carts_controller_spec.rb +143 -0
  90. data/spec/controllers/moneris_orders_controller_spec.rb +245 -0
  91. data/spec/controllers/orders_controller_spec.rb +418 -0
  92. data/spec/controllers/stripe_orders_controller_spec.rb +127 -0
  93. data/spec/controllers/webhooks_controller_spec.rb +79 -0
  94. data/spec/dummy/README.rdoc +8 -0
  95. data/spec/dummy/Rakefile +6 -0
  96. data/spec/dummy/app/assets/javascripts/application.js +13 -0
  97. data/spec/dummy/app/assets/stylesheets/application.css +15 -0
  98. data/spec/dummy/app/controllers/application_controller.rb +5 -0
  99. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  100. data/spec/dummy/app/models/product.rb +17 -0
  101. data/spec/dummy/app/models/product_with_float_price.rb +17 -0
  102. data/spec/dummy/app/models/user.rb +28 -0
  103. data/spec/dummy/app/views/layouts/application.html.erb +14 -0
  104. data/spec/dummy/bin/bundle +3 -0
  105. data/spec/dummy/bin/rails +4 -0
  106. data/spec/dummy/bin/rake +4 -0
  107. data/spec/dummy/config/application.rb +31 -0
  108. data/spec/dummy/config/boot.rb +5 -0
  109. data/spec/dummy/config/database.yml +25 -0
  110. data/spec/dummy/config/environment.rb +5 -0
  111. data/spec/dummy/config/environments/development.rb +37 -0
  112. data/spec/dummy/config/environments/production.rb +83 -0
  113. data/spec/dummy/config/environments/test.rb +39 -0
  114. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  115. data/spec/dummy/config/initializers/cookies_serializer.rb +3 -0
  116. data/spec/dummy/config/initializers/devise.rb +254 -0
  117. data/spec/dummy/config/initializers/effective_addresses.rb +15 -0
  118. data/spec/dummy/config/initializers/effective_orders.rb +22 -0
  119. data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  120. data/spec/dummy/config/initializers/inflections.rb +16 -0
  121. data/spec/dummy/config/initializers/mime_types.rb +4 -0
  122. data/spec/dummy/config/initializers/session_store.rb +3 -0
  123. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  124. data/spec/dummy/config/locales/en.yml +23 -0
  125. data/spec/dummy/config/routes.rb +3 -0
  126. data/spec/dummy/config/secrets.yml +22 -0
  127. data/spec/dummy/config.ru +4 -0
  128. data/spec/dummy/db/schema.rb +142 -0
  129. data/spec/dummy/db/test.sqlite3 +0 -0
  130. data/spec/dummy/log/development.log +487 -0
  131. data/spec/dummy/log/test.log +347 -0
  132. data/spec/dummy/public/404.html +67 -0
  133. data/spec/dummy/public/422.html +67 -0
  134. data/spec/dummy/public/500.html +66 -0
  135. data/spec/dummy/public/favicon.ico +0 -0
  136. data/spec/helpers/effective_orders_helper_spec.rb +21 -0
  137. data/spec/models/acts_as_purchasable_spec.rb +107 -0
  138. data/spec/models/customer_spec.rb +71 -0
  139. data/spec/models/factories_spec.rb +13 -0
  140. data/spec/models/order_item_spec.rb +35 -0
  141. data/spec/models/order_spec.rb +323 -0
  142. data/spec/models/stripe_charge_spec.rb +39 -0
  143. data/spec/models/subscription_spec.rb +103 -0
  144. data/spec/spec_helper.rb +44 -0
  145. data/spec/support/factories.rb +118 -0
  146. metadata +387 -0
@@ -0,0 +1,64 @@
1
+ module Effective
2
+ class OrdersMailer < ActionMailer::Base
3
+ helper EffectiveOrdersHelper
4
+
5
+ default :from => EffectiveOrders.mailer[:default_from]
6
+
7
+ def order_receipt_to_admin(order)
8
+ @order = order
9
+ mail(:to => EffectiveOrders.mailer[:admin_email], :subject => receipt_to_admin_subject(order))
10
+ end
11
+
12
+ def order_receipt_to_buyer(order) # Buyer
13
+ @order = order
14
+ mail(:to => order.user.email, :subject => receipt_to_buyer_subject(order))
15
+ end
16
+
17
+ def order_receipt_to_seller(order, seller, order_items)
18
+ @order = order
19
+ @user = seller.user
20
+ @order_items = order_items
21
+
22
+ mail(:to => @user.email, :subject => receipt_to_seller_subject(order, order_items, seller.user))
23
+ end
24
+
25
+ private
26
+
27
+ def receipt_to_admin_subject(order)
28
+ string_or_callable = EffectiveOrders.mailer[:subject_for_admin_receipt]
29
+
30
+ if string_or_callable.respond_to?(:call) # This is a Proc or a function, not a string
31
+ string_or_callable = self.instance_exec(order, &string_or_callable)
32
+ end
33
+
34
+ prefix_subject(string_or_callable.presence || "Order ##{order.to_param} Receipt")
35
+ end
36
+
37
+ def receipt_to_buyer_subject(order)
38
+ string_or_callable = EffectiveOrders.mailer[:subject_for_buyer_receipt]
39
+
40
+ if string_or_callable.respond_to?(:call) # This is a Proc or a function, not a string
41
+ string_or_callable = self.instance_exec(order, &string_or_callable)
42
+ end
43
+
44
+ prefix_subject(string_or_callable.presence || "Order ##{order.to_param} Receipt")
45
+ end
46
+
47
+ def receipt_to_seller_subject(order, order_items, seller)
48
+ string_or_callable = EffectiveOrders.mailer[:subject_for_seller_receipt]
49
+
50
+ if string_or_callable.respond_to?(:call) # This is a Proc or a function, not a string
51
+ string_or_callable = self.instance_exec(order, order_items, seller, &string_or_callable)
52
+ end
53
+
54
+ prefix_subject(string_or_callable.presence || "#{order_items.count} of your products #{order_items.count > 1 ? 'have' : 'has'} been purchased")
55
+ end
56
+
57
+ def prefix_subject(text)
58
+ prefix = (EffectiveOrders.mailer[:subject_prefix].to_s rescue '')
59
+ prefix.present? ? (prefix.chomp(' ') + ' ' + text) : text
60
+ end
61
+
62
+ end
63
+ end
64
+
@@ -0,0 +1,134 @@
1
+ module ActsAsPurchasable
2
+ extend ActiveSupport::Concern
3
+
4
+ module ActiveRecord
5
+ def acts_as_purchasable(*options)
6
+ @acts_as_purchasable = options || []
7
+ include ::ActsAsPurchasable
8
+ end
9
+ end
10
+
11
+ included do
12
+ has_many :orders, :through => :order_items, :class_name => 'Effective::Order'
13
+ has_many :order_items, :as => :purchasable, :class_name => 'Effective::OrderItem'
14
+ has_many :cart_items, :as => :purchasable, :dependent => :delete_all, :class_name => 'Effective::CartItem'
15
+
16
+ validates_with Effective::SoldOutValidator, :on => :create
17
+
18
+ validates :price, :presence => true, :numericality => true
19
+ validates :tax_exempt, :inclusion => {:in => [true, false]}
20
+
21
+ # These are breaking on the check for quanitty_enabled?. More research is due
22
+ validates :quantity_purchased, :numericality => {:allow_nil => true}, :if => proc { |purchasable| (purchasable.quantity_enabled? rescue false) }
23
+ validates :quantity_max, :numericality => {:allow_nil => true}, :if => proc { |purchasable| (purchasable.quantity_enabled? rescue false) }
24
+
25
+ scope :purchased, -> { joins(:order_items).joins(:orders).where(:orders => {:purchase_state => EffectiveOrders::PURCHASED}).uniq }
26
+ scope :purchased_by, lambda { |user| joins(:order_items).joins(:orders).where(:orders => {:user_id => user.try(:id), :purchase_state => EffectiveOrders::PURCHASED}).uniq }
27
+ scope :sold, -> { purchased() }
28
+ scope :sold_by, lambda { |user| joins(:order_items).joins(:orders).where(:order_items => {:seller_id => user.try(:id)}).where(:orders => {:purchase_state => EffectiveOrders::PURCHASED}).uniq }
29
+
30
+ scope :not_purchased, -> { where('id NOT IN (?)', purchased.pluck(:id).presence || [0]) }
31
+ end
32
+
33
+ module ClassMethods
34
+ def after_purchase(&block)
35
+ send :define_method, :after_purchase do |order, order_item| self.instance_exec(order, order_item, &block) end
36
+ end
37
+
38
+ def after_decline(&block)
39
+ send :define_method, :after_decline do |order, order_item| self.instance_exec(order, order_item, &block) end
40
+ end
41
+ end
42
+
43
+ # Regular instance methods
44
+ def is_effectively_purchasable?
45
+ true
46
+ end
47
+
48
+ def price
49
+ self[:price] || 0
50
+ end
51
+
52
+ # If I have a column type of Integer, and I'm passed a non-Integer, convert it here
53
+ def price=(value)
54
+ integer_column = ((column_for_attribute('price').try(:type) rescue nil) == :integer) # Rails built in method to lookup datatype
55
+
56
+ if integer_column == false
57
+ super
58
+ elsif value.kind_of?(Integer)
59
+ super
60
+ elsif value.kind_of?(String) && !value.include?('.') # Looks like an integer
61
+ super
62
+ else # Could be Float, BigDecimal, or String like 9.99
63
+ super((value.to_f * 100.0).to_i)
64
+ end
65
+ end
66
+
67
+ def title
68
+ self[:title] || 'ActsAsPurchasable'
69
+ end
70
+
71
+ def tax_exempt
72
+ self[:tax_exempt] || false
73
+ end
74
+
75
+ def tax_rate
76
+ @tax_rate ||= (
77
+ self.instance_exec(self, &EffectiveOrders.tax_rate_method).to_f.tap do |rate|
78
+ raise ArgumentError.new("expected EffectiveOrders.tax_rate_method to return a value between 0 and 1. Received #{rate}. Please return 0.05 for 5% tax.") if (rate > 1.0 || rate < 0.0)
79
+ end
80
+ )
81
+ end
82
+
83
+ def seller
84
+ if EffectiveOrders.stripe_connect_enabled
85
+ raise 'acts_as_purchasable object requires the seller be defined to return the User selling this item. This is only a requirement when using StripeConnect.'
86
+ end
87
+ end
88
+
89
+ def purchased?
90
+ @is_purchased ||= orders.any? { |order| order.purchased? }
91
+ end
92
+
93
+ def purchased_by?(user)
94
+ orders.any? { |order| order.purchased? && order.user_id == user.id }
95
+ end
96
+
97
+ def purchased_orders
98
+ orders.select { |order| order.purchased? }
99
+ end
100
+
101
+ def quantity_enabled?
102
+ self.respond_to?(:quantity_enabled) ? quantity_enabled == true : false
103
+ end
104
+
105
+ def quantity_remaining
106
+ (quantity_max - quantity_purchased) rescue 0
107
+ end
108
+
109
+ def sold_out?
110
+ quantity_enabled? ? (quantity_remaining == 0) : false
111
+ end
112
+
113
+ def purchased!(order = nil, order_item = nil)
114
+ # begin
115
+ # self.quantity_purchased = (self.quantity_purchased + 1)
116
+ # rescue
117
+ # end
118
+
119
+ after_purchase(order, order_item) if self.respond_to?(:after_purchase)
120
+ self.save!
121
+ end
122
+
123
+ def declined!(order = nil, order_item = nil)
124
+ after_decline(order, order_item) if self.respond_to?(:after_decline)
125
+ self.save!
126
+ end
127
+
128
+ # Override me if this is a digital purchase.
129
+ def purchased_download_url
130
+ false
131
+ end
132
+
133
+ end
134
+
@@ -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
@@ -0,0 +1,65 @@
1
+ module Effective
2
+ class Cart < ActiveRecord::Base
3
+ self.table_name = EffectiveOrders.carts_table_name.to_s
4
+
5
+ belongs_to :user # This is optional. We want to let non-logged-in people have carts too
6
+ has_many :cart_items, :inverse_of => :cart, :dependent => :delete_all
7
+
8
+ structure do
9
+ timestamps
10
+ end
11
+
12
+ default_scope -> { includes(:cart_items => :purchasable) }
13
+
14
+ def add(item, quantity = 1)
15
+ raise 'expecting an acts_as_purchasable object' unless item.respond_to?(:is_effectively_purchasable?)
16
+
17
+ existing_item = cart_items.where(:purchasable_id => item.id, :purchasable_type => item.class.name).first
18
+
19
+ if item.quantity_enabled? && (quantity + (existing_item.quantity rescue 0)) > item.quantity_remaining
20
+ raise EffectiveOrders::SoldOutException, "#{item.title} is sold out"
21
+ return
22
+ end
23
+
24
+ if existing_item.present?
25
+ existing_item.update_attributes(:quantity => existing_item.quantity + quantity)
26
+ else
27
+ cart_items.create(:cart => self, :purchasable_id => item.id, :purchasable_type => item.class.name, :quantity => quantity)
28
+ end
29
+ end
30
+ alias_method :add_to_cart, :add
31
+
32
+ def remove(obj)
33
+ (cart_items.find(cart_item) || cart_item).try(:destroy)
34
+ end
35
+ alias_method :remove_from_cart, :remove
36
+
37
+ def includes?(item)
38
+ find(item).present?
39
+ end
40
+
41
+ def find(item)
42
+ cart_items.to_a.find { |cart_item| cart_item == item || cart_item.purchasable == item }
43
+ end
44
+
45
+ def size
46
+ cart_items.size
47
+ end
48
+
49
+ def empty?
50
+ size == 0
51
+ end
52
+
53
+ def subtotal
54
+ cart_items.map(&:subtotal).sum
55
+ end
56
+
57
+ def tax
58
+ cart_items.map(&:tax).sum
59
+ end
60
+
61
+ def total
62
+ cart_items.map(&:total).sum
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,40 @@
1
+ module Effective
2
+ class CartItem < ActiveRecord::Base
3
+ self.table_name = EffectiveOrders.cart_items_table_name.to_s
4
+
5
+ belongs_to :cart
6
+ belongs_to :purchasable, :polymorphic => true
7
+
8
+ structure do
9
+ quantity :integer, :validates => [:presence]
10
+ timestamps
11
+ end
12
+
13
+ validates_presence_of :purchasable
14
+
15
+ delegate :title, :tax_exempt, :tax_rate, :to => :purchasable
16
+
17
+ default_scope -> { order(:updated_at) }
18
+
19
+ def price
20
+ if (purchasable.price || 0).kind_of?(Integer)
21
+ purchasable.price || 0
22
+ else
23
+ ActiveSupport::Deprecation.warn('price is a non-integer. It should be an Integer representing the number of cents. Continuing with (price * 100.0).floor conversion') unless EffectiveOrders.silence_deprecation_warnings
24
+ (purchasable.price * 100.0).floor rescue 0
25
+ end
26
+ end
27
+
28
+ def subtotal
29
+ price * quantity
30
+ end
31
+
32
+ def tax
33
+ tax_exempt ? 0 : (subtotal * tax_rate).ceil
34
+ end
35
+
36
+ def total
37
+ subtotal + tax
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,61 @@
1
+ module Effective
2
+ class Customer < ActiveRecord::Base
3
+ self.table_name = EffectiveOrders.customers_table_name.to_s
4
+
5
+ attr_accessor :token # This is a convenience method so we have a place to store StripeConnect temporary access tokens
6
+
7
+ belongs_to :user
8
+ has_many :subscriptions, :inverse_of => :customer
9
+
10
+ structure do
11
+ stripe_customer_id :string # cus_xja7acoa03
12
+ stripe_active_card :string # **** **** **** 4242 Visa 05/12
13
+ stripe_connect_access_token :string # If using StripeConnect and this user is a connected Seller
14
+
15
+ timestamps
16
+ end
17
+
18
+ validates_presence_of :user
19
+ validates_uniqueness_of :user_id # Only 1 customer per user may exist
20
+
21
+ scope :customers, -> { where("#{EffectiveOrders.customers_table_name.to_s}.stripe_customer_id IS NOT NULL") }
22
+
23
+ class << self
24
+ def for_user(user)
25
+ if user.present?
26
+ Effective::Customer.where(:user_id => (user.try(:id) rescue user.to_i)).first_or_create
27
+ end
28
+ end
29
+ end
30
+
31
+ def stripe_customer
32
+ @stripe_customer ||= if stripe_customer_id.present?
33
+ ::Stripe::Customer.retrieve(stripe_customer_id)
34
+ else
35
+ ::Stripe::Customer.create(:email => user.email, :description => user.id.to_s).tap do |stripe_customer|
36
+ self.update_attributes(:stripe_customer_id => stripe_customer.id)
37
+ end
38
+ end
39
+ end
40
+
41
+ def update_card!(token)
42
+ if token.present? # Oh, so they want to use a new credit card...
43
+ stripe_customer.card = token # This sets the default_card to the new card
44
+
45
+ if stripe_customer.save && stripe_customer.default_card.present?
46
+ card = stripe_customer.cards.retrieve(stripe_customer.default_card)
47
+
48
+ self.stripe_active_card = "**** **** **** #{card.last4} #{card.type} #{card.exp_month}/#{card.exp_year}"
49
+ self.save!
50
+ else
51
+ raise Exception.new('unable to update stripe customer with new card')
52
+ end
53
+ end
54
+ end
55
+
56
+ def is_stripe_connect_seller?
57
+ stripe_connect_access_token.present?
58
+ end
59
+
60
+ end
61
+ end
@@ -0,0 +1,45 @@
1
+ if defined?(EffectiveDatatables)
2
+ module Effective
3
+ module Datatables
4
+ class Customers < Effective::Datatable
5
+ table_column :email, :column => 'users.email' do |user|
6
+ mail_to user.email, user.email
7
+ end
8
+
9
+ if EffectiveOrders.stripe_enabled
10
+ table_column :stripe_customer_id
11
+ table_column :stripe_active_card
12
+ end
13
+
14
+ if EffectiveOrders.stripe_connect_enabled
15
+ table_column :stripe_connect_access_token
16
+ end
17
+
18
+ table_column :subscription_types, :column => 'subscription_types'
19
+
20
+ table_column :actions, :sortable => false, :filter => false, :partial => '/admin/customers/actions'
21
+
22
+ def collection
23
+ Effective::Customer.customers.uniq
24
+ .joins(:user)
25
+ .joins(:subscriptions)
26
+ .select('customers.*')
27
+ .select('users.email AS email')
28
+ .select("array_to_string(array(#{Effective::Subscription.purchased.select('subscriptions.stripe_plan_id').where('subscriptions.customer_id = customers.id').to_sql}), ' ,') AS subscription_types")
29
+ .group('customers.id')
30
+ .group('subscriptions.stripe_plan_id')
31
+ .group('users.email')
32
+ end
33
+
34
+ def search_column(collection, table_column, search_term)
35
+ if table_column[:name] == 'subscription_types'
36
+ collection.where('subscriptions.stripe_plan_id ILIKE ?', "%#{search_term}%")
37
+ else
38
+ super
39
+ end
40
+ end
41
+
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,53 @@
1
+ if defined?(EffectiveDatatables)
2
+ module Effective
3
+ module Datatables
4
+ class Orders < Effective::Datatable
5
+ table_column :id do |order|
6
+ order.to_param
7
+ end
8
+
9
+ array_column :email, :label => 'Buyer', :if => Proc.new { attributes[:user_id].blank? } do |order|
10
+ link_to order.user.email, (edit_admin_user_path(order.user) rescue admin_user_path(order.user) rescue '#')
11
+ end
12
+
13
+ array_column :order_items do |order|
14
+ content_tag(:ul) do
15
+ order.order_items.map { |oi| content_tag(:li, oi.title) }.join().html_safe
16
+ end
17
+ end
18
+
19
+ table_column :purchased_at
20
+
21
+ array_column :total do |order|
22
+ price_to_currency(order.total)
23
+ end
24
+
25
+ table_column :actions, :sortable => false, :filter => false do |order|
26
+ content_tag(:span, :style => 'white-space: nowrap;') do
27
+ [
28
+ link_to('View', (datatables_admin_path? ? effective_orders.admin_order_path(order) : effective_orders.order_path(order))),
29
+ (link_to('Resend Receipt', effective_orders.resend_buyer_receipt_path(order), {'data-confirm' => 'This action will resend a copy of the original email receipt. Send receipt now?'}) if order.try(:purchased?))
30
+ ].compact.join(' - ').html_safe
31
+ end
32
+ end
33
+
34
+ def collection
35
+ if attributes[:user_id].present?
36
+ Effective::Order.purchased.where(:user_id => attributes[:user_id]).includes(:user).includes(:order_items)
37
+ else
38
+ Effective::Order.purchased.includes(:user).includes(:order_items)
39
+ end
40
+ end
41
+
42
+ def search_column(collection, table_column, search_term)
43
+ if table_column[:name] == 'id'
44
+ collection.where(:id => search_term)
45
+ else
46
+ super
47
+ end
48
+ end
49
+
50
+ end
51
+ end
52
+ end
53
+ end