effective_orders 2.2.4 → 3.0.0

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 (150) hide show
  1. checksums.yaml +4 -4
  2. data/MIT-LICENSE +1 -1
  3. data/README.md +124 -84
  4. data/app/assets/javascripts/effective_orders/customers.js.coffee +39 -0
  5. data/app/assets/javascripts/effective_orders/providers/{stripe_charges.js.coffee → stripe.js.coffee} +15 -13
  6. data/app/assets/javascripts/effective_orders/subscriptions.js.coffee +73 -0
  7. data/app/assets/stylesheets/effective_orders.scss +2 -1
  8. data/app/assets/stylesheets/effective_orders/_order.scss +16 -8
  9. data/app/assets/stylesheets/effective_orders/_subscriptions.scss +14 -0
  10. data/app/controllers/admin/customers_controller.rb +11 -8
  11. data/app/controllers/admin/order_items_controller.rb +4 -8
  12. data/app/controllers/admin/orders_controller.rb +133 -87
  13. data/app/controllers/effective/carts_controller.rb +18 -8
  14. data/app/controllers/effective/concerns/purchase.rb +39 -0
  15. data/app/controllers/effective/customers_controller.rb +43 -0
  16. data/app/controllers/effective/orders_controller.rb +73 -119
  17. data/app/controllers/effective/providers/app_checkout.rb +3 -1
  18. data/app/controllers/effective/providers/ccbill.rb +4 -6
  19. data/app/controllers/effective/providers/cheque.rb +20 -11
  20. data/app/controllers/effective/providers/free.rb +33 -0
  21. data/app/controllers/effective/providers/mark_as_paid.rb +33 -0
  22. data/app/controllers/effective/providers/moneris.rb +9 -17
  23. data/app/controllers/effective/providers/paypal.rb +4 -6
  24. data/app/controllers/effective/providers/pretend.rb +4 -4
  25. data/app/controllers/effective/providers/refund.rb +39 -0
  26. data/app/controllers/effective/providers/stripe.rb +19 -40
  27. data/app/controllers/effective/providers/stripe_connect.rb +2 -6
  28. data/app/controllers/effective/webhooks_controller.rb +44 -95
  29. data/app/datatables/effective_customers_datatable.rb +21 -29
  30. data/app/datatables/effective_order_items_datatable.rb +77 -79
  31. data/app/datatables/effective_orders_datatable.rb +67 -57
  32. data/app/helpers/effective_carts_helper.rb +17 -14
  33. data/app/helpers/effective_orders_helper.rb +40 -56
  34. data/app/helpers/effective_paypal_helper.rb +3 -3
  35. data/app/helpers/effective_stripe_helper.rb +47 -18
  36. data/app/helpers/effective_subscriptions_helper.rb +79 -0
  37. data/app/mailers/effective/orders_mailer.rb +125 -2
  38. data/app/models/concerns/acts_as_purchasable.rb +23 -33
  39. data/app/models/concerns/acts_as_subscribable.rb +68 -0
  40. data/app/models/concerns/acts_as_subscribable_buyer.rb +22 -0
  41. data/app/models/effective/cart.rb +53 -24
  42. data/app/models/effective/cart_item.rb +6 -12
  43. data/app/models/effective/customer.rb +51 -54
  44. data/app/models/effective/order.rb +160 -147
  45. data/app/models/effective/order_item.rb +18 -21
  46. data/app/models/effective/product.rb +7 -7
  47. data/app/models/effective/providers/ccbill_postback.rb +1 -1
  48. data/app/models/effective/providers/stripe_charge.rb +8 -19
  49. data/app/models/effective/subscripter.rb +230 -0
  50. data/app/models/effective/subscription.rb +27 -76
  51. data/app/models/effective/tax_rate_calculator.rb +10 -7
  52. data/app/views/admin/customers/_actions.html.haml +1 -2
  53. data/app/views/admin/customers/index.html.haml +1 -1
  54. data/app/views/admin/customers/show.html.haml +6 -0
  55. data/app/views/admin/orders/_actions.html.haml +9 -7
  56. data/app/views/admin/orders/_form.html.haml +11 -7
  57. data/app/views/admin/orders/_order_actions.html.haml +2 -1
  58. data/app/views/admin/orders/_order_item_fields.html.haml +1 -1
  59. data/app/views/admin/orders/edit.html.haml +4 -0
  60. data/app/views/admin/orders/index.html.haml +1 -4
  61. data/app/views/admin/orders/new.html.haml +1 -1
  62. data/app/views/admin/orders/show.html.haml +5 -6
  63. data/app/views/effective/carts/_cart.html.haml +2 -2
  64. data/app/views/effective/carts/show.html.haml +2 -2
  65. data/app/views/effective/customers/_customer.html.haml +152 -0
  66. data/app/views/effective/customers/_fields.html.haml +12 -0
  67. data/app/views/effective/customers/_form.html.haml +13 -0
  68. data/app/views/effective/customers/edit.html.haml +3 -0
  69. data/app/views/effective/orders/_checkout_step1.html.haml +8 -15
  70. data/app/views/effective/orders/_checkout_step2.html.haml +34 -21
  71. data/app/views/effective/orders/_order.html.haml +8 -9
  72. data/app/views/effective/orders/_order_actions.html.haml +7 -8
  73. data/app/views/effective/orders/_order_header.html.haml +1 -1
  74. data/app/views/effective/orders/_order_items.html.haml +11 -5
  75. data/app/views/effective/orders/_order_note.html.haml +4 -7
  76. data/app/views/effective/orders/_orders_table.html.haml +26 -26
  77. data/app/views/effective/orders/app_checkout/_form.html.haml +2 -2
  78. data/app/views/effective/orders/ccbill/_form.html.haml +1 -1
  79. data/app/views/effective/orders/cheque/_form.html.haml +3 -1
  80. data/app/views/effective/orders/declined.html.haml +1 -1
  81. data/app/views/effective/orders/{checkout_step1.html.haml → edit.html.haml} +0 -0
  82. data/app/views/effective/orders/free/_form.html.haml +4 -0
  83. data/app/views/effective/orders/index.html.haml +2 -4
  84. data/app/views/effective/orders/mark_as_paid/_form.html.haml +32 -0
  85. data/app/views/effective/orders/moneris/_form.html.haml +6 -6
  86. data/app/views/effective/orders/{checkout_step2.html.haml → new.html.haml} +1 -1
  87. data/app/views/effective/orders/paypal/_form.html.haml +2 -2
  88. data/app/views/effective/orders/pretend/_form.html.haml +2 -2
  89. data/app/views/effective/orders/purchased.html.haml +3 -0
  90. data/app/views/effective/orders/refund/_form.html.haml +32 -0
  91. data/app/views/effective/orders/show.html.haml +4 -1
  92. data/app/views/effective/orders/stripe/_form.html.haml +5 -5
  93. data/app/views/effective/orders_mailer/subscription_canceled.html.haml +9 -0
  94. data/app/views/effective/orders_mailer/subscription_payment_failed.html.haml +9 -0
  95. data/app/views/effective/orders_mailer/subscription_payment_succeeded.html.haml +9 -0
  96. data/app/views/effective/orders_mailer/subscription_trial_expired.html.haml +5 -0
  97. data/app/views/effective/orders_mailer/subscription_trial_expiring.html.haml +7 -0
  98. data/app/views/effective/subscriptions/_fields.html.haml +16 -0
  99. data/app/views/effective/subscriptions/_plan.html.haml +21 -0
  100. data/app/views/layouts/effective_orders_mailer_layout.html.haml +6 -8
  101. data/config/effective_orders.rb +41 -20
  102. data/config/routes.rb +48 -48
  103. data/db/migrate/01_create_effective_orders.rb.erb +19 -5
  104. data/lib/effective_orders.rb +78 -42
  105. data/lib/effective_orders/engine.rb +36 -82
  106. data/lib/effective_orders/version.rb +1 -1
  107. data/lib/generators/effective_orders/install_generator.rb +2 -2
  108. data/lib/generators/templates/effective_orders_mailer_preview.rb +39 -4
  109. data/lib/tasks/effective_orders_tasks.rake +42 -0
  110. data/spec/controllers/carts_controller_spec.rb +1 -1
  111. data/spec/controllers/moneris_orders_controller_spec.rb +4 -4
  112. data/spec/controllers/orders_controller_spec.rb +4 -4
  113. data/spec/controllers/stripe_orders_controller_spec.rb +2 -2
  114. data/spec/controllers/webhooks_controller_spec.rb +1 -1
  115. data/spec/dummy/config/initializers/effective_orders.rb +1 -7
  116. data/spec/dummy/db/schema.rb +1 -0
  117. data/spec/dummy/db/test.sqlite3 +0 -0
  118. data/spec/dummy/log/test.log +3 -0
  119. data/spec/models/acts_as_purchasable_spec.rb +0 -56
  120. data/spec/models/customer_spec.rb +3 -3
  121. data/spec/models/order_spec.rb +2 -2
  122. data/spec/spec_helper.rb +1 -1
  123. data/spec/support/factories.rb +2 -1
  124. metadata +37 -49
  125. data/active_admin/effective_carts.rb +0 -14
  126. data/active_admin/effective_orders.rb +0 -112
  127. data/app/assets/javascripts/effective_orders/providers/stripe_subscriptions.js.coffee +0 -28
  128. data/app/controllers/concerns/acts_as_active_admin_controller.rb +0 -69
  129. data/app/controllers/effective/subscriptions_controller.rb +0 -126
  130. data/app/models/effective/datatables/customers.rb +0 -40
  131. data/app/models/effective/datatables/order_items.rb +0 -101
  132. data/app/models/effective/datatables/orders.rb +0 -91
  133. data/app/models/inputs/price_field.rb +0 -63
  134. data/app/models/inputs/price_form_input.rb +0 -7
  135. data/app/models/inputs/price_formtastic_input.rb +0 -9
  136. data/app/models/inputs/price_input.rb +0 -19
  137. data/app/models/inputs/price_simple_form_input.rb +0 -8
  138. data/app/views/admin/orders/_form_mark_as_paid.html.haml +0 -33
  139. data/app/views/admin/orders/_order_payment_details.html.haml +0 -5
  140. data/app/views/admin/orders/mark_as_paid.html.haml +0 -7
  141. data/app/views/effective/orders/stripe/_subscription_fields.html.haml +0 -7
  142. data/app/views/effective/subscriptions/index.html.haml +0 -22
  143. data/app/views/effective/subscriptions/new.html.haml +0 -9
  144. data/app/views/effective/subscriptions/show.html.haml +0 -49
  145. data/db/upgrade/02_upgrade_effective_orders_from03x.rb.erb +0 -29
  146. data/db/upgrade/03_upgrade_effective_orders_from1x.rb.erb +0 -98
  147. data/db/upgrade/upgrade_price_column_on_table.rb.erb +0 -17
  148. data/lib/generators/effective_orders/upgrade_from03x_generator.rb +0 -31
  149. data/lib/generators/effective_orders/upgrade_from1x_generator.rb +0 -27
  150. data/lib/generators/effective_orders/upgrade_price_column_generator.rb +0 -30
@@ -12,10 +12,14 @@ module ActsAsPurchasable
12
12
  end
13
13
 
14
14
  included do
15
- has_many :orders, through: :order_items, class_name: 'Effective::Order'
16
- has_many :order_items, as: :purchasable, class_name: 'Effective::OrderItem'
17
15
  has_many :cart_items, as: :purchasable, dependent: :delete_all, class_name: 'Effective::CartItem'
18
16
 
17
+ has_many :order_items, as: :purchasable, class_name: 'Effective::OrderItem'
18
+ has_many :orders, -> { order(:id) }, through: :order_items, class_name: 'Effective::Order'
19
+
20
+ has_many :purchased_orders, -> { where(purchase_state: EffectiveOrders::PURCHASED).order(:purchased_at) },
21
+ through: :order_items, class_name: 'Effective::Order', source: :order
22
+
19
23
  validates_with Effective::SoldOutValidator, on: :create
20
24
 
21
25
  # Database max integer value is 2147483647. So let's round that down and use a max/min of $20 million (2000000000)
@@ -24,8 +28,8 @@ module ActsAsPurchasable
24
28
  validates :tax_exempt, inclusion: { in: [true, false] }
25
29
 
26
30
  # These are breaking on the check for quanitty_enabled?. More research is due
27
- validates :quantity_purchased, numericality: {allow_nil: true}, if: proc { |purchasable| (purchasable.quantity_enabled? rescue false) }
28
- validates :quantity_max, numericality: {allow_nil: true}, if: proc { |purchasable| (purchasable.quantity_enabled? rescue false) }
31
+ validates :quantity_purchased, numericality: { allow_nil: true }, if: proc { |purchasable| (purchasable.quantity_enabled? rescue false) }
32
+ validates :quantity_max, numericality: { allow_nil: true }, if: proc { |purchasable| (purchasable.quantity_enabled? rescue false) }
29
33
 
30
34
  scope :purchased, -> { joins(order_items: :order).where(orders: {purchase_state: EffectiveOrders::PURCHASED}).distinct }
31
35
  scope :purchased_by, lambda { |user| joins(order_items: :order).where(orders: {user_id: user.try(:id), purchase_state: EffectiveOrders::PURCHASED}).distinct }
@@ -37,6 +41,10 @@ module ActsAsPurchasable
37
41
  end
38
42
 
39
43
  module ClassMethods
44
+ def before_purchase(&block)
45
+ send :define_method, :before_purchase do |order, order_item| self.instance_exec(order, order_item, &block) end
46
+ end
47
+
40
48
  def after_purchase(&block)
41
49
  send :define_method, :after_purchase do |order, order_item| self.instance_exec(order, order_item, &block) end
42
50
  end
@@ -54,16 +62,12 @@ module ActsAsPurchasable
54
62
 
55
63
  # If I have a column type of Integer, and I'm passed a non-Integer, convert it here
56
64
  def price=(value)
57
- integer_column = ((column_for_attribute('price').try(:type) rescue nil) == :integer) # Rails built in method to lookup datatype
58
-
59
- if integer_column == false
60
- super
61
- elsif value.kind_of?(Integer)
65
+ if value.kind_of?(Integer)
62
66
  super
63
67
  elsif value.kind_of?(String) && !value.include?('.') # Looks like an integer
64
68
  super
65
- else # Could be Float, BigDecimal, or String like 9.99
66
- super((value.to_f * 100.0).to_i)
69
+ else
70
+ raise 'expected price to be an Integer representing the number of cents.'
67
71
  end
68
72
  end
69
73
 
@@ -77,28 +81,24 @@ module ActsAsPurchasable
77
81
 
78
82
  def seller
79
83
  if EffectiveOrders.stripe_connect_enabled
80
- 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.'
84
+ 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.'
81
85
  end
82
86
  end
83
87
 
84
- def purchased?
85
- @is_purchased ||= orders.any? { |order| order.purchased? }
88
+ def purchased_order
89
+ @purchased_order ||= purchased_orders.first
86
90
  end
87
91
 
88
- def purchased_at
89
- @purchased_at ||= orders.map { |order| order.purchased_at if order.purchased? }.compact.sort.first
92
+ def purchased?
93
+ @is_purchased ||= purchased_order.present?
90
94
  end
91
95
 
92
96
  def purchased_by?(user)
93
- orders.any? { |order| order.purchased? && order.user_id == user.id }
97
+ purchased_orders.any? { |order| order.user_id == user.id }
94
98
  end
95
99
 
96
- def purchased_orders
97
- orders.select { |order| order.purchased? }
98
- end
99
-
100
- def purchased_order
101
- purchased_orders.first
100
+ def purchased_at
101
+ purchased_order.try(:purchased_at)
102
102
  end
103
103
 
104
104
  def quantity_enabled?
@@ -113,16 +113,6 @@ module ActsAsPurchasable
113
113
  quantity_enabled? ? (quantity_remaining == 0) : false
114
114
  end
115
115
 
116
- def purchased!(order = nil, order_item = nil)
117
- after_purchase(order, order_item) if self.respond_to?(:after_purchase)
118
- save!
119
- end
120
-
121
- def declined!(order = nil, order_item = nil)
122
- after_decline(order, order_item) if self.respond_to?(:after_decline)
123
- save!
124
- end
125
-
126
116
  # Override me if this is a digital purchase.
127
117
  def purchased_download_url
128
118
  false
@@ -0,0 +1,68 @@
1
+ module ActsAsSubscribable
2
+ extend ActiveSupport::Concern
3
+
4
+ mattr_accessor :descendants
5
+
6
+ module ActiveRecord
7
+ def acts_as_subscribable(*options)
8
+ @acts_as_subscribable = options || []
9
+ include ::ActsAsSubscribable
10
+ (ActsAsSubscribable.descendants ||= []) << self
11
+ end
12
+ end
13
+
14
+ included do
15
+ has_one :subscription, as: :subscribable, class_name: 'Effective::Subscription'
16
+ has_one :customer, through: :subscription, class_name: 'Effective::Customer'
17
+
18
+ validates :subscripter, associated: true
19
+
20
+ scope :subscribed, -> { where(id: joins(:subscription)) } # All resources with a subscription
21
+ scope :trialing, -> { where.not(id: joins(:subscription)) } # All resources without a subscription
22
+ end
23
+
24
+ module ClassMethods
25
+ end
26
+
27
+ def subscripter
28
+ @_effective_subscripter ||= Effective::Subscripter.new(subscribable: self, user: buyer)
29
+ end
30
+
31
+ def subscripter=(atts)
32
+ subscripter.assign_attributes(atts)
33
+ end
34
+
35
+ def subscribed?(stripe_plan_id = nil)
36
+ case stripe_plan_id
37
+ when nil
38
+ subscription.present? # Subscribed to any subscription?
39
+ when (EffectiveOrders.stripe_plans['trial'] || {})[:id]
40
+ subscription.blank? || subscription.new_record? || subscription.stripe_plan_id == stripe_plan_id
41
+ else
42
+ subscription && subscription.persisted? && subscription.errors.blank? && subscription.stripe_plan_id == stripe_plan_id
43
+ end
44
+ end
45
+
46
+ def subscription_active?
47
+ (trialing? && !trial_expired?) || (subscribed? && subscription.active?)
48
+ end
49
+
50
+ def trialing?
51
+ !subscribed?
52
+ end
53
+
54
+ def trial_expired?
55
+ trialing? && Time.zone.now > trial_expires_at
56
+ end
57
+
58
+ def trial_expires_at
59
+ # The rake task send_trial_expiring_emails depends on this beginning_of_day
60
+ ((created_at || Time.zone.now) + EffectiveOrders.subscription[:trial_period]).beginning_of_day
61
+ end
62
+
63
+ def buyer
64
+ raise 'acts_as_subscribable object requires the buyer be defined to return the User buying this item.'
65
+ end
66
+
67
+ end
68
+
@@ -0,0 +1,22 @@
1
+ module ActsAsSubscribableBuyer
2
+ extend ActiveSupport::Concern
3
+
4
+ module ActiveRecord
5
+ def acts_as_subscribable_buyer(*options)
6
+ include ::ActsAsSubscribableBuyer
7
+ end
8
+ end
9
+
10
+ included do
11
+ has_one :customer, class_name: 'Effective::Customer'
12
+
13
+ before_save(if: -> { persisted? && email_changed? && customer.present? }) do
14
+ Rails.logger.info "STRIPE CUSTOMER EMAIL UPDATE: #{customer.stripe_customer_id}"
15
+ customer.stripe_customer.email = email
16
+ customer.stripe_customer.description = to_s
17
+ throw :abort unless (customer.stripe_customer.save rescue false)
18
+ end
19
+ end
20
+
21
+ end
22
+
@@ -2,58 +2,87 @@ 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 # 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
5
+ belongs_to :user # Optional. We want non-logged-in users to have carts too.
6
+ has_many :cart_items, -> { includes(:purchasable).order(:updated_at) }, dependent: :delete_all, class_name: 'Effective::CartItem'
7
7
 
8
- # structure do
9
- # timestamps
10
- # end
8
+ accepts_nested_attributes_for :cart_items
11
9
 
12
- default_scope -> { includes(:cart_items => :purchasable) }
10
+ # Attributes
11
+ # cart_items_count :integer
12
+ # timestamps
13
13
 
14
- def add(item, quantity: 1)
14
+ scope :deep, -> { includes(cart_items: :purchasable) }
15
+
16
+ # cart.add(@product, unique: -> (a, b) { a.kind_of?(Product) && b.kind_of?(Product) && a.category == b.category })
17
+ # cart.add(@product, unique: :category)
18
+ # cart.add(@product, unique: false) # Add as many as you want
19
+ def add(item, quantity: 1, unique: true)
15
20
  raise 'expecting an acts_as_purchasable object' unless item.kind_of?(ActsAsPurchasable)
16
21
 
17
- existing_item = cart_items.where(purchasable_id: item.id, purchasable_type: item.class.name).first
22
+ existing = (
23
+ if unique.kind_of?(Proc)
24
+ cart_items.find { |cart_item| instance_exec(item, cart_item.purchasable, &unique) }
25
+ elsif unique.kind_of?(Symbol) || (unique.kind_of?(String) && unique != 'true')
26
+ raise "expected item to respond to unique #{unique}" unless item.respond_to?(unique)
27
+ cart_items.find { |cart_item| cart_item.purchasable.respond_to?(unique) && item.send(unique) == cart_item.purchasable.send(unique) }
28
+ elsif unique.present?
29
+ find(item)
30
+ end
31
+ )
18
32
 
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
33
+ if existing
34
+ if unique || (existing.unique.present?)
35
+ existing.assign_attributes(purchasable: item, quantity: quantity, unique: existing.unique)
36
+ else
37
+ existing.quantity = existing.quantity + quantity
38
+ end
22
39
  end
23
40
 
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)
41
+ if item.quantity_enabled? && (existing ? existing.quantity : quantity) > item.quantity_remaining
42
+ raise EffectiveOrders::SoldOutException, "#{item.title} is sold out"
28
43
  end
44
+
45
+ existing ||= cart_items.build(purchasable: item, quantity: quantity, unique: (unique.to_s if unique.kind_of?(Symbol) || unique.kind_of?(String) || unique == true))
46
+ save!
47
+ end
48
+
49
+ def clear!
50
+ cart_items.each { |cart_item| cart_item.mark_for_destruction }
51
+ save!
29
52
  end
30
- alias_method :add_to_cart, :add
31
53
 
32
- def remove(obj)
33
- (cart_items.find(cart_item) || cart_item).try(:destroy)
54
+ def remove(item)
55
+ find(item).try(:mark_for_destruction)
56
+ save!
34
57
  end
35
- alias_method :remove_from_cart, :remove
36
58
 
37
59
  def includes?(item)
38
60
  find(item).present?
39
61
  end
40
62
 
41
63
  def find(item)
42
- cart_items.to_a.find { |cart_item| cart_item == item || cart_item.purchasable == item }
64
+ cart_items.find { |cart_item| cart_item == item || cart_item.purchasable == item }
65
+ end
66
+
67
+ def purchasables
68
+ cart_items.map { |cart_item| cart_item.purchasable }
43
69
  end
44
70
 
45
71
  def size
46
- cart_items.size
72
+ cart_items_count || cart_items.length
73
+ end
74
+
75
+ def present?
76
+ size > 0
47
77
  end
48
78
 
49
- def empty?
50
- size == 0
79
+ def blank?
80
+ size <= 0
51
81
  end
52
82
 
53
83
  def subtotal
54
84
  cart_items.map { |ci| ci.subtotal }.sum
55
85
  end
56
- alias_method :total, :subtotal
57
86
 
58
87
  end
59
88
  end
@@ -2,26 +2,21 @@ 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
6
- belongs_to :purchasable, :polymorphic => true
5
+ belongs_to :cart, counter_cache: true, class_name: 'Effective::Cart'
6
+ belongs_to :purchasable, polymorphic: true
7
7
 
8
- # structure do
9
- # quantity :integer
10
- #
11
- # timestamps
12
- # end
8
+ # Attributes
9
+ # quantity :integer
10
+ # timestamps
13
11
 
14
12
  validates :purchasable, presence: true
15
13
  validates :quantity, presence: true
16
14
 
17
- default_scope -> { order(:updated_at) }
18
-
19
15
  def price
20
16
  if (purchasable.price || 0).kind_of?(Integer)
21
17
  purchasable.price || 0
22
18
  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).round(0).to_i conversion') unless EffectiveOrders.silence_deprecation_warnings
24
- (purchasable.price * 100.0).round(0).to_i rescue 0
19
+ raise 'expected price to be an Integer representing the number of cents.'
25
20
  end
26
21
  end
27
22
 
@@ -36,7 +31,6 @@ module Effective
36
31
  def subtotal
37
32
  price * quantity
38
33
  end
39
- alias_method :total, :subtotal
40
34
 
41
35
  end
42
36
  end
@@ -2,83 +2,80 @@ module Effective
2
2
  class Customer < ActiveRecord::Base
3
3
  self.table_name = EffectiveOrders.customers_table_name.to_s
4
4
 
5
- attr_accessor :token # This is a convenience method so we have a place to store StripeConnect temporary access tokens
5
+ attr_accessor :stripe_token # This is a convenience method so we have a place to store StripeConnect temporary access tokens
6
+ attr_accessor :stripe_customer, :stripe_subscription
6
7
 
7
8
  belongs_to :user
8
- has_many :subscriptions, :inverse_of => :customer
9
+ has_many :subscriptions, class_name: 'Effective::Subscription', foreign_key: 'customer_id'
10
+ has_many :subscribables, through: :subscriptions, source: :subscribable
9
11
 
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
12
+ accepts_nested_attributes_for :subscriptions
13
+
14
+ # Attributes
15
+ # stripe_customer_id :string # cus_xja7acoa03
16
+ # active_card :string # **** **** **** 4242 Visa 05/12
17
+
18
+ # stripe_subscription_id :string # Each user gets one stripe subscription object, which can contain many items
19
+ # status :string
20
+
21
+ # stripe_connect_access_token :string # If using StripeConnect and this user is a connected Seller
14
22
  #
15
- # timestamps
16
- # end
23
+ # timestamps
24
+
25
+ scope :deep, -> { includes(subscriptions: :subscribable) }
26
+
27
+ before_validation do
28
+ subscriptions.each { |subscription| subscription.status = status }
29
+ end
17
30
 
18
31
  validates :user, presence: true
19
- validates :user_id, uniqueness: true
32
+ validates :stripe_customer_id, presence: true
33
+ validates :status, if: -> { stripe_subscription_id.present? }, inclusion: { in: %w(active past_due) }
20
34
 
21
- scope :customers, -> { where("#{EffectiveOrders.customers_table_name.to_s}.stripe_customer_id IS NOT NULL") }
35
+ def self.for_user(user)
36
+ Effective::Customer.where(user: user).first_or_initialize
37
+ end
22
38
 
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
39
+ def to_s
40
+ user.to_s.presence || 'New Customer'
29
41
  end
30
42
 
31
43
  def stripe_customer
32
44
  @stripe_customer ||= if stripe_customer_id.present?
45
+ Rails.logger.info "STRIPE CUSTOMER RETRIEVE: #{stripe_customer_id}"
33
46
  ::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
47
  end
39
48
  end
40
49
 
41
- def update_card!(token)
42
- if token.present? # Oh, so they want to use a new credit card...
43
- if stripe_customer.respond_to?(:cards)
44
- stripe_customer.card = token # This sets the default_card to the new card
45
- elsif stripe_customer.respond_to?(:sources)
46
- stripe_customer.source = token
47
- end
48
-
49
- if stripe_customer.save && default_card.present?
50
- card = cards.retrieve(default_card)
51
-
52
- self.stripe_active_card = "**** **** **** #{card.last4} #{card.brand} #{card.exp_month}/#{card.exp_year}"
53
- self.save!
54
- else
55
- raise Exception.new('unable to update stripe customer with new card')
56
- end
50
+ def stripe_subscription
51
+ @stripe_subscription ||= if stripe_subscription_id.present?
52
+ Rails.logger.info "STRIPE SUBSCRIPTION RETRIEVE: #{stripe_subscription_id}"
53
+ ::Stripe::Subscription.retrieve(stripe_subscription_id)
57
54
  end
58
55
  end
59
56
 
60
- def is_stripe_connect_seller?
61
- stripe_connect_access_token.present?
57
+ def upcoming_invoice
58
+ @upcoming_invoice ||= if stripe_customer_id.present? && stripe_subscription_id.present?
59
+ Rails.logger.info "STRIPE UPCOMING INVOICE RETRIEVE: #{stripe_customer_id}"
60
+ ::Stripe::Invoice.upcoming(customer: stripe_customer_id) rescue nil
61
+ end
62
62
  end
63
63
 
64
- private
65
-
66
- def default_card
67
- case
68
- when stripe_customer.respond_to?(:default_card)
69
- stripe_customer.default_card
70
- when stripe_customer.respond_to?(:default_source)
71
- stripe_customer.default_source
72
- end
64
+ def token_required?
65
+ active_card.blank? || (active_card.present? && stripe_subscription_id.present? && status != 'active')
73
66
  end
74
67
 
75
- def cards
76
- case
77
- when stripe_customer.respond_to?(:cards)
78
- stripe_customer.cards
79
- when stripe_customer.respond_to?(:sources)
80
- stripe_customer.sources
81
- end
68
+ def payment_status
69
+ if status == 'past_due'
70
+ 'We ran into an error processing your last payment. Please update or confirm your card details to continue.'
71
+ elsif active_card.present? && token_required?
72
+ 'Please update or confirm your card details to continue.'
73
+ elsif active_card.present?
74
+ 'Thanks for your support! The card we have on file is'
75
+ else
76
+ 'No credit card on file. Please add a card.'
77
+ end.html_safe
82
78
  end
79
+
83
80
  end
84
81
  end