effective_orders 4.0.6 → 4.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 3dc3ed0ca01bf36b49326b92714290faff406bb8
4
- data.tar.gz: eb4eec1d4d0d58c97e3554a96357147b5b65ab90
3
+ metadata.gz: f15a457703f171efde10a8a750dcd7b8c7092e3f
4
+ data.tar.gz: 182e31f6db68aac1ff8f6a50fdf405f5e3307ae5
5
5
  SHA512:
6
- metadata.gz: c29cc4865ee5d538a801640dfca5986c8cff2924cc06767fece44a959cfbe2d17cdb82a55df6535e2af386317bd909b154646cee9cc1e8809a22937a24216775
7
- data.tar.gz: 2eb4b69106c3080f17e46badefc133749ddd1e33359b926ff7798d8b83af219db256faf7586682da4aeb9e0066a1c6a8e1ca890eda82c026614b73bc1c9714b3
6
+ metadata.gz: 0ed4b21b9c7d9bd6ad1ac9615874b1735eb69826bd5db89ab18df7f96889a8282edcd1ca37b363e6e4f0dcc0001c608a83df431b8e6ab7361d5633ca1f2b3326
7
+ data.tar.gz: d59aa7963b0436533fbf99fbe9e1d6ee79dbdbf8278b7d361fde6e16187023e935436492a7b04a1d4df80d682d7b4725e4e6ebb2fe4233c40ad993a9861cc691
data/README.md CHANGED
@@ -20,7 +20,7 @@ Please check out [Effective Orders 3.x](https://github.com/code-and-effect/effec
20
20
 
21
21
  ## Getting Started
22
22
 
23
- Please first install the [effective_addresses](https://github.com/code-and-effect/effective_addresses), [effective_datatables](https://github.com/code-and-effect/effective_datatables) and [effective_form_inputs](https://github.com/code-and-effect/effective_form_inputs) gems.
23
+ Please first install the [effective_addresses](https://github.com/code-and-effect/effective_addresses), [effective_datatables](https://github.com/code-and-effect/effective_datatables) and [effective_bootstrap](https://github.com/code-and-effect/effective_bootstrap) gems.
24
24
 
25
25
  Add to your Gemfile:
26
26
 
@@ -13,6 +13,30 @@ stripeSubscriptionHandler = (key, form) ->
13
13
  form.find("input[name$='[stripe_token]']").val('' + token['id'])
14
14
  form.addClass('stripe-success').submit() # Submits the form. As this is a remote form, submits via JS
15
15
 
16
+ # This updates the form whenever a quantity change is made
17
+ $(document).on 'change keyup', '.effective-orders-subscripter-plan-quantity', (event) ->
18
+ $obj = $(event.currentTarget)
19
+ $plan = $obj.closest('.effective-orders-stripe-plan')
20
+ return unless $plan.length == 1
21
+
22
+ # Assign the quantity to each quantity field
23
+ $plan.closest('form').find(".effective-orders-stripe-plan:not([data-plan-id='#{$plan.data('id')}'])").find("input[name$='[quantity]']").val($obj.val())
24
+
25
+ quantity = $obj.val() || 0
26
+
27
+ # Assign all totals
28
+ $plan.closest('form').find(".effective-orders-stripe-plan").each ->
29
+ plan = $(this)
30
+ amount = parseInt(plan.data('amount'))
31
+ interval = plan.data('plan-interval')
32
+
33
+ total = (quantity * amount)
34
+ total = (total / 12) if interval == 'year'
35
+
36
+ total = '$' + (total / 100.0).toFixed(2)
37
+
38
+ plan.find('#effective_subscripter_total_amount').text(total)
39
+
16
40
  # Hijack submit and get a stripe token
17
41
  $(document).on 'click', ".effective-orders-stripe-token-required[type='submit'],[data-choose-stripe-plan-id]", (event) ->
18
42
  $obj = $(event.currentTarget)
@@ -44,8 +68,4 @@ $(document).on 'click', ".effective-orders-stripe-token-required[type='submit'],
44
68
  name: stripe.name
45
69
  email: stripe.email
46
70
  description: plan.name
47
- amount: plan.amount
48
- panelLabel: "{{amount}}/#{plan.interval} Go!"
49
-
50
- $(document).on 'change', "input[name='effective_subscripter[stripe_plan_id]']", (event) ->
51
- $(event.currentTarget).closest('form').find(".effective-orders-stripe-token-required[type='submit']").click()
71
+ panelLabel: 'Update Plan'
@@ -4,18 +4,12 @@ module Effective
4
4
 
5
5
  include Effective::CrudController
6
6
 
7
- submit :save, 'Save', redirect: :back, success: -> { 'Successfully updated card.' }
7
+ submit :save, 'Save', success: -> { 'Successfully updated card.' }
8
8
  page_title 'Customer Settings'
9
9
 
10
10
  def resource
11
- @customer ||= Effective::Customer.where(user: current_user).first!
12
- @subscripter ||= Effective::Subscripter.new(customer: @customer, user: @customer.user)
13
- end
14
-
15
- # I don't want save_resource to wrap my save in a transaction
16
- def save_resource(resource, action, to_assign)
17
- resource.assign_attributes(to_assign)
18
- resource.save!
11
+ @customer = Effective::Customer.where(user: current_user).first!
12
+ @subscripter ||= Effective::Subscripter.new(customer: @customer, current_user: current_user)
19
13
  end
20
14
 
21
15
  # StrongParameters
@@ -7,12 +7,7 @@ module Effective
7
7
  submit :save, 'Save', redirect: :back, success: -> { 'Successfully updated plan.' }
8
8
 
9
9
  def resource
10
- @subscripter ||= Effective::Subscripter.new(user: current_user)
11
- end
12
-
13
- # I don't want save_resource to wrap my save in a transaction
14
- def save_resource(resource, action = :save, &block)
15
- resource.save!
10
+ @subscripter ||= Effective::Subscripter.new(current_user: current_user)
16
11
  end
17
12
 
18
13
  # StrongParameters
@@ -42,17 +42,13 @@ module Effective
42
42
  # when 'charge.failed' # Card declined. 4000 0000 0000 0341
43
43
 
44
44
  when 'invoice.payment_succeeded'
45
- customer.update_attributes!(status: EffectiveOrders::ACTIVE)
46
-
45
+ subscriptions.each { |subscription| subscription.update!(status: EffectiveOrders::ACTIVE) }
47
46
  send_email(:subscription_payment_succeeded, customer)
48
47
  when 'invoice.payment_failed'
49
- customer.update_attributes!(status: EffectiveOrders::PAST_DUE)
50
-
48
+ subscriptions.each { |subscription| subscription.update!(status: EffectiveOrders::PAST_DUE) }
51
49
  send_email(:subscription_payment_failed, customer)
52
50
  when 'customer.subscription.deleted'
53
- customer.update_attributes!(stripe_subscription_id: nil, status: nil, active_card: nil)
54
- customer.subscriptions.delete_all
55
-
51
+ subscriptions.each { |subscription| subscription.destroy! }
56
52
  send_email(:subscription_canceled, customer)
57
53
  when 'customer.subscription.created'
58
54
  send_email(:subscription_created, customer)
@@ -79,6 +75,15 @@ module Effective
79
75
  )
80
76
  end
81
77
 
78
+ def subscriptions
79
+ customer.subscriptions.select { |subscription| stripe_plan_ids.include?(subscription.stripe_plan_id) }
80
+ end
81
+
82
+ def stripe_plan_ids
83
+ return unless @event.respond_to?(:data)
84
+ @stripe_plan_ids ||= @event.data.object.lines.map { |line| line.plan.id }
85
+ end
86
+
82
87
  def send_email(email, *mailer_args)
83
88
  Effective::OrdersMailer.public_send(email, *mailer_args).public_send(EffectiveOrders.mailer[:deliver_method])
84
89
  Effective::OrdersMailer.public_send(:subscription_event_to_admin, email.to_s, *mailer_args).public_send(EffectiveOrders.mailer[:deliver_method])
@@ -1,45 +1,10 @@
1
1
  module EffectiveSubscriptionsHelper
2
2
 
3
- def render_plan(plan, subscribed: true)
4
- render(render_plan_partial(plan), plan: plan, subscribed: subscribed)
5
- end
6
-
7
- def render_plan_partial(plan)
8
- if lookup_context.template_exists?("effective/subscriptions/#{plan[:id].downcase}", [], true)
9
- "effective/subscriptions/#{plan[:id].downcase}" # Render the app's views/effective/subscriptions/_gold.html.haml
10
- elsif lookup_context.template_exists?("effective/subscriptions/#{plan[:name].downcase}", [], true)
11
- "effective/subscriptions/#{plan[:name].downcase}" # Render the app's views/effective/subscriptions/_gold.html.haml
12
- else
13
- 'effective/subscriptions/plan' # Render effective_orders default plan panel
14
- end
15
- end
16
-
17
- def stripe_plans_collection(form)
18
- raise 'expected an Effective::FormBuilder object' unless form.class.name == 'Effective::FormBuilder'
19
-
20
- subscripter = form.object
21
- raise 'form object must be a subscripter object' unless subscripter.class.name == 'Effective::Subscripter'
22
-
23
- plans = EffectiveOrders.stripe_plans.values.sort do |x, y|
3
+ def stripe_plans_collection(subscripter = nil)
4
+ EffectiveOrders.stripe_plans.values.sort do |x, y|
24
5
  amount = (x[:amount] <=> y[:amount])
25
6
  (amount != 0) ? amount : x[:name] <=> y[:name]
26
7
  end
27
-
28
- if (existing = subscripter.customer.stripe_subscription_interval).present?
29
- plans.select! { |plan| plan[:interval] == existing }
30
- end
31
-
32
- plans.map do |plan|
33
- content = render(partial: render_plan_partial(plan), locals: {
34
- f: form,
35
- plan: plan,
36
- selected: Array(form.object.stripe_plan_id).include?(plan[:id]),
37
- subscribable: form.object.subscribable,
38
- subscribed: form.object.subscribable.subscribed?(plan[:id])
39
- })
40
-
41
- [content, plan[:id]]
42
- end
43
8
  end
44
9
 
45
10
  def subscribable_form_with(subscribable)
@@ -29,7 +29,10 @@ module ActsAsPurchasable
29
29
  through: :order_items, class_name: 'Effective::Order', source: :order
30
30
 
31
31
  # Database max integer value is 2147483647. So let's round that down and use a max/min of $20 million (2000000000)
32
- validates :price, presence: true, numericality: { less_than_or_equal_to: 2000000000, message: 'maximum price is $20,000,000' }
32
+ validates :price, presence: true
33
+ validates :price, numericality: { less_than_or_equal_to: 2000000000, message: 'maximum price is $20,000,000' }
34
+ validates :price, numericality: { greater_than_or_equal_to: -2000000000, message: 'minimum price is -$20,000,000' }
35
+
33
36
  validates :tax_exempt, inclusion: { in: [true, false] }
34
37
 
35
38
  with_options(if: -> { quantity_enabled? }) do
@@ -44,7 +44,9 @@ module ActsAsSubscribable
44
44
  end
45
45
 
46
46
  def subscripter
47
- @_effective_subscripter ||= Effective::Subscripter.new(subscribable: self, user: subscribable_buyer)
47
+ @_effective_subscripter ||= begin
48
+ Effective::Subscripter.new(subscribable: self, user: subscribable_buyer, quantity: (subscription&.quantity || 0), stripe_plan_id: subscription&.stripe_plan_id)
49
+ end
48
50
  end
49
51
 
50
52
  def subscribed?(stripe_plan_id = nil)
@@ -76,5 +78,8 @@ module ActsAsSubscribable
76
78
  raise 'acts_as_subscribable object requires the subscribable_buyer method be defined to return the User buying this item.'
77
79
  end
78
80
 
79
- end
81
+ def subscribable_quantity_used
82
+ raise 'acts_as_subscribable object requires the subscribable_quantity_used method be defined to determine how many are in use.'
83
+ end
80
84
 
85
+ end
@@ -2,7 +2,7 @@ module Effective
2
2
  class Customer < ActiveRecord::Base
3
3
  self.table_name = EffectiveOrders.customers_table_name.to_s
4
4
 
5
- attr_accessor :stripe_customer, :stripe_subscription
5
+ attr_accessor :stripe_customer
6
6
 
7
7
  belongs_to :user
8
8
  has_many :subscriptions, -> { includes(:subscribable) }, class_name: 'Effective::Subscription', foreign_key: 'customer_id'
@@ -12,21 +12,12 @@ module Effective
12
12
  # stripe_customer_id :string # cus_xja7acoa03
13
13
  # active_card :string # **** **** **** 4242 Visa 05/12
14
14
 
15
- # stripe_subscription_id :string # Each user gets one stripe subscription object, which can contain many items
16
- # stripe_subscription_interval :string
17
- # status :string
18
-
19
15
  # timestamps
20
16
 
21
17
  scope :deep, -> { includes(subscriptions: :subscribable) }
22
18
 
23
- after_commit do
24
- subscriptions.each { |subscription| subscription.subscribable.update_column(:subscription_status, status) }
25
- end
26
-
27
19
  validates :user, presence: true
28
20
  validates :stripe_customer_id, presence: true
29
- validates :status, if: -> { stripe_subscription_id.present? }, inclusion: { in: EffectiveOrders::STATUSES.keys }
30
21
 
31
22
  def self.for_user(user)
32
23
  Effective::Customer.where(user: user).first_or_initialize
@@ -47,28 +38,29 @@ module Effective
47
38
  end
48
39
  end
49
40
 
50
- def stripe_subscription
51
- @stripe_subscription ||= if stripe_subscription_id.present?
52
- Rails.logger.info "[STRIPE] get subscription: #{stripe_subscription_id}"
53
- ::Stripe::Subscription.retrieve(stripe_subscription_id)
54
- end
55
- end
56
-
57
41
  def upcoming_invoice
58
- @upcoming_invoice ||= if stripe_customer_id.present? && stripe_subscription_id.present?
42
+ @upcoming_invoice ||= if stripe_customer_id.present?
59
43
  Rails.logger.info "[STRIPE] get upcoming invoice: #{stripe_customer_id}"
60
44
  ::Stripe::Invoice.upcoming(customer: stripe_customer_id) rescue nil
61
45
  end
62
46
  end
63
47
 
64
48
  def token_required?
65
- active_card.blank? || (active_card.present? && stripe_subscription_id.present? && status != 'active')
49
+ active_card.blank? || past_due?
50
+ end
51
+
52
+ def past_due?
53
+ subscriptions.any? { |subscription| subscription.past_due? }
54
+ end
55
+
56
+ def active?
57
+ subscriptions.present? && subscriptions.all? { |subscription| subscription.active? }
66
58
  end
67
59
 
68
60
  def payment_status
69
- if status == 'past_due'
61
+ if past_due?
70
62
  'We ran into an error processing your last payment. Please update or confirm your card details to continue.'
71
- elsif status == 'active'
63
+ elsif active?
72
64
  "Your payment is in good standing. Thanks so much for your support!"
73
65
  elsif active_card.blank?
74
66
  'No credit card on file. Please add a card.'
@@ -4,8 +4,8 @@ module Effective
4
4
  class Subscripter
5
5
  include ActiveModel::Model
6
6
 
7
- attr_accessor :user, :subscribable, :customer
8
- attr_accessor :subscribable_global_id, :stripe_token, :stripe_plan_id, :include_trial
7
+ attr_accessor :current_user, :user, :subscribable, :customer
8
+ attr_accessor :subscribable_global_id, :stripe_token, :stripe_plan_id, :quantity
9
9
 
10
10
  validates :user, presence: true
11
11
  validates :subscribable, presence: true, if: -> { stripe_plan_id.present? }
@@ -17,10 +17,23 @@ module Effective
17
17
  self.errors.add(:stripe_token, 'updated payment card required') if stripe_token.blank? && token_required?
18
18
  end
19
19
 
20
+ validate(if: -> { stripe_plan_id && subscribable }) do
21
+ quantity_used = [subscribable.subscribable_quantity_used, 0].max
22
+ self.errors.add(:quantity, "must be #{quantity_used} or greater") unless quantity >= quantity_used
23
+ end
24
+
25
+ def to_s
26
+ 'Your Plan'
27
+ end
28
+
20
29
  def customer
21
30
  @customer ||= Effective::Customer.deep.where(user: user).first_or_initialize
22
31
  end
23
32
 
33
+ def current_user=(user)
34
+ @user = user
35
+ end
36
+
24
37
  def subscribable_global_id
25
38
  subscribable&.to_global_id
26
39
  end
@@ -29,20 +42,12 @@ module Effective
29
42
  @subscribable = GlobalID::Locator.locate(global_id)
30
43
  end
31
44
 
32
- def user_id=(id)
33
- @user = User.find(id)
34
- end
35
-
36
- def current_plan
37
- subscribable&.subscription&.plan
38
- end
39
-
40
45
  def plan
41
46
  EffectiveOrders.stripe_plans[stripe_plan_id]
42
47
  end
43
48
 
44
- def stripe_plan_id
45
- @stripe_plan_id || (current_plan[:id] if current_plan)
49
+ def quantity
50
+ (@quantity.to_i if @quantity.present?)
46
51
  end
47
52
 
48
53
  def token_required?
@@ -50,142 +55,113 @@ module Effective
50
55
  end
51
56
 
52
57
  def save!
53
- return true if (plan == current_plan) && stripe_token.blank? # No work to do
54
-
55
58
  raise 'is invalid' unless valid?
56
59
 
57
60
  create_customer!
58
61
  create_stripe_token!
59
- build_subscription!
60
- sync_subscription!
62
+ save_subscription!
61
63
  true
62
64
  end
63
65
 
64
66
  def destroy!
65
67
  return true unless plan
66
68
 
67
- subscribable.subscription.destroy!
68
- subscribable.update_column(:subscription_status, nil)
69
- sync_subscription!
69
+ subscription = subscribable.subscription
70
+
71
+ Rails.logger.info " -> [STRIPE] delete subscription"
72
+ subscription.stripe_subscription.delete
73
+ subscription.destroy!
74
+
70
75
  true
71
76
  end
72
77
 
73
78
  protected
74
79
 
75
- # This should work even if the rest of the form doesn't. Careful with our transactions...
76
80
  def create_customer!
77
- if customer.stripe_customer.blank?
78
- Rails.logger.info "[STRIPE] create customer: #{user.email}"
79
- customer.stripe_customer = Stripe::Customer.create(email: user.email, description: user.to_s, metadata: { user_id: user.id })
80
- customer.stripe_customer_id = customer.stripe_customer.id
81
- customer.save!
82
- end
81
+ return if customer.stripe_customer.present?
82
+
83
+ Rails.logger.info "[STRIPE] create customer: #{user.email}"
84
+ customer.stripe_customer = Stripe::Customer.create(email: user.email, description: user.to_s, metadata: { user_id: user.id })
85
+ customer.stripe_customer_id = customer.stripe_customer.id
86
+ customer.save!
83
87
  end
84
88
 
85
89
  # Update stripe customer card
86
90
  def create_stripe_token!
87
- if stripe_token.present?
88
- Rails.logger.info "[STRIPE] update source: #{stripe_token}"
89
- customer.stripe_customer.source = stripe_token
90
- customer.stripe_customer.save
91
-
92
- if customer.stripe_customer.default_source.present?
93
- card = customer.stripe_customer.sources.retrieve(customer.stripe_customer.default_source)
94
- customer.active_card = "**** **** **** #{card.last4} #{card.brand} #{card.exp_month}/#{card.exp_year}"
95
- customer.save!
96
- end
97
- end
98
- end
91
+ return if stripe_token.blank?
99
92
 
100
- def build_subscription!
101
- return unless plan.present?
102
- subscription.stripe_plan_id = plan[:id]
103
- end
93
+ Rails.logger.info "[STRIPE] update source: #{stripe_token}"
94
+ customer.stripe_customer.source = stripe_token
95
+ customer.stripe_customer.save
104
96
 
105
- def sync_subscription!
106
- return unless plan.present?
107
- customer.stripe_subscription.blank? ? create_subscription! : update_subscription!
97
+ return if customer.stripe_customer.default_source.blank?
108
98
 
99
+ card = customer.stripe_customer.sources.retrieve(customer.stripe_customer.default_source)
100
+ customer.active_card = "**** **** **** #{card.last4} #{card.brand} #{card.exp_month}/#{card.exp_year}"
109
101
  customer.save!
110
102
  end
111
103
 
112
- def create_subscription!
104
+ def save_subscription!
113
105
  return unless plan.present?
114
- return if customer.stripe_subscription.present?
115
106
 
116
- Rails.logger.info "[STRIPE] create subscription: #{items(metadata: false)}"
117
- customer.stripe_subscription = Stripe::Subscription.create(customer: customer.stripe_customer_id, items: items(metadata: false), metadata: metadata)
118
- customer.stripe_subscription_id = customer.stripe_subscription.id
107
+ subscription.assign_attributes(stripe_plan_id: stripe_plan_id, quantity: quantity)
119
108
 
120
- customer.status = customer.stripe_subscription.status
121
- customer.stripe_subscription_interval = customer.stripe_subscription.plan.interval
109
+ cancel_subscription!
110
+ create_subscription! || update_subscription!
111
+ true
122
112
  end
123
113
 
124
- def update_subscription!
125
- return unless plan.present?
126
- return if customer.stripe_subscription.blank?
127
-
128
- Rails.logger.info "[STRIPE] update subscription: #{customer.stripe_subscription_id} #{items}"
114
+ def cancel_subscription!
115
+ return false unless subscription.persisted? && subscription.stripe_plan_id_changed?
129
116
 
130
- if items.length == 0
131
- customer.stripe_subscription.delete
132
- customer.stripe_subscription_id = nil
133
- customer.status = EffectiveOrders::CANCELED
134
- return
135
- end
117
+ item = items.first
118
+ stripe_item = subscription.stripe_subscription.items.first
136
119
 
137
- # Update stripe subscription items. Keep track if quantity changed here or not
138
- quantity_increased = false
120
+ return false unless stripe_item.present? && item[:plan] != stripe_item['plan']['id']
139
121
 
140
- customer.stripe_subscription.items.each do |stripe_item|
141
- item = items.find { |item| item[:plan] == stripe_item['plan']['id'] }
122
+ Rails.logger.info " -> [STRIPE] cancel plan: #{stripe_item['plan']['id']}"
123
+ subscription.stripe_subscription.delete
124
+ subscription.assign_attributes(stripe_subscription: nil, stripe_subscription_id: nil)
142
125
 
143
- next if item.blank? || item[:quantity] == stripe_item['quantity']
144
-
145
- quantity_increased ||= (item[:quantity] > stripe_item['quantity']) # Any quantity increased
126
+ true
127
+ end
146
128
 
147
- stripe_item.quantity = item[:quantity]
148
- stripe_item.metadata = item[:metadata]
129
+ def create_subscription!
130
+ return false unless subscription.stripe_subscription.blank?
149
131
 
150
- Rails.logger.info " -> [STRIPE] update plan: #{item[:plan]}"
151
- stripe_item.save
152
- end
132
+ Rails.logger.info "[STRIPE] create subscription: #{items}"
133
+ stripe_subscription = Stripe::Subscription.create(customer: customer.stripe_customer_id, items: items, metadata: metadata)
153
134
 
154
- # Create stripe subscription items
155
- items.each do |item|
156
- next if customer.stripe_subscription.items.find { |stripe_item| item[:plan] == stripe_item['plan']['id'] }
135
+ subscription.update!(
136
+ stripe_subscription: stripe_subscription,
137
+ stripe_subscription_id: stripe_subscription.id,
138
+ status: stripe_subscription.status,
139
+ name: stripe_subscription.plan.nickname,
140
+ interval: stripe_subscription.plan.interval,
141
+ quantity: quantity
142
+ )
143
+ end
157
144
 
158
- Rails.logger.info " -> [STRIPE] create plan: #{item[:plan]}"
159
- customer.stripe_subscription.items.create(plan: item[:plan], quantity: item[:quantity], metadata: item[:metadata])
160
- end
145
+ def update_subscription!
146
+ return false unless subscription.stripe_subscription.present?
161
147
 
162
- # Delete stripe subscription items
163
- customer.stripe_subscription.items.each do |stripe_item|
164
- next if items.find { |item| item[:plan] == stripe_item['plan']['id'] }
148
+ stripe_item = subscription.stripe_subscription.items.first
149
+ item = items.first
165
150
 
166
- Rails.logger.info " -> [STRIPE] delete plan: #{stripe_item['plan']['id']}"
167
- stripe_item.delete
168
- end
151
+ return false unless stripe_item.present? && item[:plan] == stripe_item['plan']['id']
152
+ return false unless item[:quantity] != subscription.stripe_subscription.quantity
169
153
 
170
- # Update metadata
171
- if customer.stripe_subscription.metadata.to_h != metadata
172
- Rails.logger.info " -> [STRIPE] update metadata: #{metadata}"
173
- customer.stripe_subscription.metadata = metadata
174
- customer.stripe_subscription.save
175
- end
154
+ Rails.logger.info " -> [STRIPE] update plan: #{item[:plan]}"
155
+ stripe_item.quantity = item[:quantity]
156
+ stripe_item.save
176
157
 
177
- # When upgrading a plan, invoice immediately.
178
- if quantity_increased
179
- Rails.logger.info " -> [STRIPE] generate invoice"
180
- Stripe::Invoice.create(customer: customer.stripe_customer_id).pay
181
- end
158
+ subscription.update!(status: subscription.stripe_subscription.status)
182
159
 
183
- customer.status = customer.stripe_subscription.status
184
- end
160
+ # Invoice immediately
161
+ Rails.logger.info " -> [STRIPE] generate invoice"
162
+ Stripe::Invoice.create(customer: customer.stripe_customer_id).pay
185
163
 
186
- def subscribe!(stripe_plan_id)
187
- self.stripe_plan_id = stripe_plan_id
188
- save!
164
+ true
189
165
  end
190
166
 
191
167
  private
@@ -195,33 +171,18 @@ module Effective
195
171
  customer.subscriptions.find { |sub| sub.subscribable == subscribable } || customer.subscriptions.build(subscribable: subscribable, customer: customer)
196
172
  end
197
173
 
198
- def items(metadata: true)
199
- customer.subscriptions.group_by { |sub| sub.stripe_plan_id }.map do |plan, subscriptions|
200
- if metadata
201
- { plan: plan, quantity: subscriptions.length, metadata: metadata(subscriptions: subscriptions) }
202
- else
203
- { plan: plan, quantity: subscriptions.length }
204
- end
205
- end
174
+ def items
175
+ [{ plan: subscription.stripe_plan_id, quantity: subscription.quantity }]
206
176
  end
207
177
 
208
178
  # The stripe metadata limit is 500 characters
209
- def metadata(subscriptions: nil)
210
- retval = { user_id: user.id.to_s, user: user.to_s.truncate(500) }
211
-
212
- (subscriptions || customer.subscriptions).group_by { |sub| sub.subscribable_type }.each do |subscribable_type, subs|
213
- subs = subs.sort
214
-
215
- if subs.length == 1
216
- retval[subscribable_type.downcase + '_id'] = subs.map { |sub| sub.subscribable.id }.join(',')
217
- retval[subscribable_type.downcase] = subs.map { |sub| sub.subscribable.to_s }.join(',').truncate(500)
218
- else
219
- retval[subscribable_type.downcase + '_ids'] = subs.map { |sub| sub.subscribable.id }.join(',')
220
- retval[subscribable_type.downcase.pluralize] = subs.map { |sub| sub.subscribable.to_s }.join(',').truncate(500)
221
- end
222
- end
223
-
224
- retval
179
+ def metadata
180
+ {
181
+ :user_id => user.id.to_s,
182
+ :user => user.to_s.truncate(500),
183
+ (subscription.subscribable_type.downcase + '_id').to_sym => subscription.subscribable.id.to_s,
184
+ subscription.subscribable_type.downcase.to_sym => subscription.subscribable.to_s
185
+ }
225
186
  end
226
187
 
227
188
  end
@@ -1,27 +1,58 @@
1
- # This links the acts_as_subscribable_buyer (customer) to the acts_as_subscribable (subscribable)
2
-
3
1
  module Effective
4
2
  class Subscription < ActiveRecord::Base
5
3
  self.table_name = EffectiveOrders.subscriptions_table_name.to_s
6
4
 
5
+ attr_accessor :stripe_subscription
6
+
7
7
  belongs_to :customer, class_name: 'Effective::Customer', counter_cache: true
8
8
  belongs_to :subscribable, polymorphic: true
9
9
 
10
10
  # Attributes
11
- # stripe_plan_id :string
12
- # name :string
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
13
19
  #
14
20
  # timestamps
15
21
 
16
22
  before_validation(if: -> { plan && (stripe_plan_id_changed? || new_record?) }) do
17
- self.name = "#{plan[:name]} #{plan[:description]}"
23
+ self.name = plan[:name]
24
+ self.description = plan[:description]
25
+ end
26
+
27
+ after_save(on: [:create, :update]) do
28
+ subscribable.subscription_name = name if subscribable.respond_to?(:subscription_name=)
29
+ subscribable.subscription_description = description if subscribable.respond_to?(:subscription_description=)
30
+ subscribable.subscription_interval = interval if subscribable.respond_to?(:subscription_interval=)
31
+ subscribable.subscription_quantity = quantity if subscribable.respond_to?(:subscription_quantity=)
32
+ subscribable.subscription_status = status if subscribable.respond_to?(:subscription_status=)
33
+ subscribable.save!(validate: false)
34
+ end
35
+
36
+ after_destroy do
37
+ subscribable.subscription_name = nil if subscribable.respond_to?(:subscription_name=)
38
+ subscribable.subscription_description = nil if subscribable.respond_to?(:subscription_description=)
39
+ subscribable.subscription_interval = nil if subscribable.respond_to?(:subscription_interval=)
40
+ subscribable.subscription_quantity = nil if subscribable.respond_to?(:subscription_quantity=)
41
+ subscribable.subscription_status = nil if subscribable.respond_to?(:subscription_status=)
42
+ subscribable.save!(validate: false)
18
43
  end
19
44
 
20
45
  validates :customer, presence: true
21
46
  validates :subscribable, presence: true
22
47
 
23
48
  validates :stripe_plan_id, presence: true, inclusion: { in: EffectiveOrders.stripe_plans.keys }
49
+ validates :stripe_subscription_id, presence: true
50
+
24
51
  validates :name, presence: true
52
+ validates :interval, presence: true
53
+ validates :quantity, presence: true, numericality: { greater_than: 0 }
54
+
55
+ validates :status, inclusion: { in: EffectiveOrders::STATUSES.keys }
25
56
 
26
57
  def to_s
27
58
  name || 'New Subscription'
@@ -31,9 +62,28 @@ module Effective
31
62
  EffectiveOrders.stripe_plans[stripe_plan_id]
32
63
  end
33
64
 
65
+ def stripe_subscription
66
+ @stripe_subscription ||= if stripe_subscription_id.present?
67
+ Rails.logger.info "[STRIPE] get subscription: #{stripe_subscription_id}"
68
+ ::Stripe::Subscription.retrieve(stripe_subscription_id)
69
+ end
70
+ end
71
+
34
72
  def <=>(other)
35
73
  name.to_s <=> other&.name.to_s
36
74
  end
37
75
 
76
+ def active?
77
+ status == 'active'
78
+ end
79
+
80
+ def past_due?
81
+ status == 'past_due'
82
+ end
83
+
84
+ def canceled?
85
+ status == 'canceled'
86
+ end
87
+
38
88
  end
39
89
  end
@@ -1,85 +1,21 @@
1
- - if customer.stripe_subscription.present?
2
- .card.my-4
3
- .card-header Subscription
4
- .card-body
5
- %table.table
6
- %tbody
7
- %tr
8
- %th Status
9
- %td= customer.stripe_subscription.status.presence || 'unknown'
10
-
11
- %tr
12
- %th Email
13
- %td= customer.stripe_customer.email
14
-
15
- %tr
16
- %th Card
17
- %td
18
- - if customer.stripe_customer.default_source.present?
19
- - card = customer.stripe_customer.sources.retrieve(customer.stripe_customer.default_source)
20
- = "**** **** **** #{card.last4} #{card.brand} #{card.exp_month}/#{card.exp_year}"
21
- - else
22
- None
23
-
24
- %tr
25
- %th Currency
26
- %td= customer.stripe_customer.currency.to_s.upcase
27
-
28
- - if customer.stripe_subscription.discount.present?
29
- %tr
30
- %th Coupon
31
- %td= stripe_coupon_description(customer.stripe_subscription.discount.coupon)
32
-
33
- - if customer.stripe_subscription.start.present?
34
- %tr
35
- %th Started
36
- %td= Time.zone.at(customer.stripe_subscription.start).strftime('%F')
37
-
38
- - if customer.stripe_subscription.ended_at.present?
39
- %tr
40
- %th Ended
41
- %td= Time.zone.at(customer.stripe_subscription.ended_at).strftime('%F')
42
-
43
- - if customer.stripe_subscription.canceled_at.present?
44
- %tr
45
- %th Cancelled
46
- %td= Time.zone.at(customer.stripe_subscription.canceled_at).strftime('%F')
47
-
48
- - if customer.stripe_subscription.current_period_start.present?
49
- %tr
50
- %th Current Period Start
51
- %td= Time.zone.at(customer.stripe_subscription.current_period_start).strftime('%F')
52
-
53
- - if customer.stripe_subscription.current_period_end.present?
54
- %tr
55
- %th Current Period End
56
- %td= Time.zone.at(customer.stripe_subscription.current_period_end).strftime('%F')
57
-
58
- - if customer.stripe_subscription.metadata.present? && false
59
- %tr
60
- %th Metadata
61
- %td= tableize_hash(customer.stripe_subscription.metadata.to_h, th: false)
62
-
63
- - if customer.stripe_subscription.items.present?
64
- %tr
65
- %th Plans
66
- %td= tableize_hash(customer.stripe_subscription.items.inject({}) { |h, item| h[item.plan.nickname] = item.quantity; h }, th: false)
67
-
68
1
  - if customer.subscriptions.present?
69
2
  .card.my-4
70
- .card-header Subscribed
3
+ .card-header Subscriptions
71
4
  .card-body
72
5
  %table.table
73
6
  %thead
74
7
  %tr
75
- %th Name
8
+ %th Subscribed
76
9
  %th Plan
77
- %th
10
+ %th Quantity
11
+ %th Interval
78
12
  %tbody
79
13
  - customer.subscriptions.each do |sub|
80
14
  %tr
81
15
  %td= sub.subscribable
82
16
  %td= sub
17
+ %td= sub.quantity
18
+ %td= sub.interval
83
19
 
84
20
  - if customer.stripe_customer.invoices.present?
85
21
  .card.my-4
@@ -94,10 +30,10 @@
94
30
  %tbody
95
31
  - customer.stripe_customer.invoices.each do |invoice|
96
32
  %tr
97
- %td= Time.zone.at(invoice.date).strftime('%F')
33
+ %td= link_to Time.zone.at(invoice.date).strftime('%F'), invoice.invoice_pdf
98
34
  %td
99
35
  %p
100
- = invoice.id
36
+ = invoice.number
101
37
  %br
102
38
  = Time.zone.at(invoice.lines.first.period.start).strftime('%F')
103
39
  to
@@ -1,5 +1,5 @@
1
1
  <% resource = (@_effective_resource || Effective::Resource.new(controller_path)) %>
2
2
  <% @resource = instance_variable_get('@' + resource.name) if resource.name %>
3
3
 
4
- EffectiveForm.remote_form_payload = $("<div><%= j render_resource_form(resource, subscripter: @subscripter) %></div>");
4
+ EffectiveForm.remote_form_payload = "<%= j render('effective/customers/form', subscripter: @subscripter) %>";
5
5
  EffectiveForm.remote_form_flash = <%= raw flash.to_json %>;
@@ -1,11 +1,64 @@
1
1
  = javascript_include_tag 'https://checkout.stripe.com/checkout.js'
2
2
 
3
3
  = effective_form_with(model: subscripter, url: effective_orders.subscripter_path, remote: true, data: { stripe: subscripter_stripe_data(subscripter) }) do |f|
4
- = f.hidden_field :user_id, value: current_user.id
5
4
  = f.hidden_field :subscribable_global_id
6
5
  = f.hidden_field :stripe_token, value: nil
7
6
  = f.error :stripe_token
8
7
 
9
- = f.radios :stripe_plan_id, stripe_plans_collection(f), inline: false, cards: false, label: false, required: true
8
+ = f.hidden_field :stripe_plan_id
10
9
 
11
- = f.submit 'Choose Plan', center: true, border: false, class: ('effective-orders-stripe-token-required' if f.object.token_required?)
10
+ - stripe_plans = stripe_plans_collection(f.object)
11
+ - stripe_plans = stripe_plans.select { |plan| plan[:name].include?('per User') }
12
+
13
+ - f.object.stripe_plan_id ||= stripe_plans.first[:id]
14
+
15
+ - if f.object.subscribable.subscribed?
16
+ - quantity_used = f.object.subscribable.subscribable_quantity_used.to_i
17
+ - quantity_purchased = f.object.subscribable.subscription.quantity
18
+
19
+ .text-center
20
+ %p
21
+ You currently have <strong>#{pluralize(quantity_used, 'member')}</strong> in your team
22
+ and have space for <strong>#{pluralize(quantity_purchased, 'member')}</strong>.
23
+
24
+ - if quantity_purchased > quantity_used
25
+ %p You can add #{pluralize(quantity_purchased - quantity_used, 'more member')} without updating your plan.
26
+ - else
27
+ %p To add more members you will need to update your plan.
28
+
29
+ %p To pay for less than #{pluralize(quantity_used, 'member')} you'll have to remove some first.
30
+
31
+ .card.mb-4
32
+ .card-body.text-center
33
+ %h5.card-title Billing Cycle
34
+
35
+ = f.radios :stripe_plan_id, stripe_plans.map { |plan| [plan[:name], plan[:id]] }, label: false, buttons: true
36
+
37
+ %hr
38
+
39
+ - stripe_plans.select { |plan| plan[:interval] == 'month' }.each do |plan|
40
+ = f.show_if(:stripe_plan_id, plan[:id]) do
41
+ .effective-orders-stripe-plan{'data-id': plan[:id], 'data-amount': plan[:amount], 'data-interval': plan[:interval]}
42
+ .d-flex.justify-content-around.align-items-center
43
+ = f.number_field :quantity, class: 'effective-orders-subscripter-plan-quantity form-control-lg', autocomplete: 'off', required: true
44
+ %div= 'x'
45
+ = f.static_field :price_per_person do
46
+ = price_to_currency(plan[:amount])
47
+ %div= '='
48
+ = f.static_field :total_amount do
49
+ = price_to_currency(plan[:amount] * f.object.quantity.to_i)
50
+
51
+ - stripe_plans.select { |plan| plan[:interval] == 'year' }.each do |plan|
52
+ = f.show_if(:stripe_plan_id, plan[:id]) do
53
+ .effective-orders-stripe-plan{'data-id': plan[:id], 'data-amount': plan[:amount], 'data-interval': plan[:interval]}
54
+ .d-flex.justify-content-around.align-items-center
55
+ = f.number_field :quantity, class: 'effective-orders-subscripter-plan-quantity form-control-lg', autocomplete: 'off', required: true
56
+ %div= 'x'
57
+ = f.static_field :price_per_person do
58
+ = price_to_currency(plan[:amount] / 12)
59
+ %div= '='
60
+ = f.static_field :total_amount do
61
+ = price_to_currency(plan[:amount] * f.object.quantity.to_i)
62
+
63
+ = f.submit(border: false, center: true) do
64
+ = f.save('Update Plan', class: ('effective-orders-stripe-token-required' if f.object.token_required?))
@@ -73,9 +73,6 @@ class CreateEffectiveOrders < ActiveRecord::Migration[4.2]
73
73
 
74
74
  t.string :stripe_customer_id
75
75
  t.string :active_card
76
-
77
- t.string :stripe_subscription_id
78
- t.string :stripe_subscription_interval
79
76
  t.string :status
80
77
 
81
78
  t.integer :subscriptions_count, :default => 0
@@ -92,7 +89,13 @@ class CreateEffectiveOrders < ActiveRecord::Migration[4.2]
92
89
  t.string :subscribable_type
93
90
 
94
91
  t.string :stripe_plan_id
92
+ t.string :stripe_subscription_id
93
+
95
94
  t.string :name
95
+ t.string :description
96
+ t.string :interval
97
+ t.integer :quantity
98
+ t.string :status
96
99
 
97
100
  t.timestamps
98
101
  end
@@ -1,3 +1,3 @@
1
1
  module EffectiveOrders
2
- VERSION = '4.0.6'.freeze
2
+ VERSION = '4.1.0'.freeze
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: effective_orders
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.0.6
4
+ version: 4.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Code and Effect
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-10-12 00:00:00.000000000 Z
11
+ date: 2019-04-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -231,8 +231,6 @@ files:
231
231
  - app/views/effective/orders_mailer/subscription_trialing.html.haml
232
232
  - app/views/effective/orders_mailer/subscription_updated.html.haml
233
233
  - app/views/effective/subscripter/_form.html.haml
234
- - app/views/effective/subscriptions/_plan.html.haml
235
- - app/views/effective/subscriptions/_subscription.html.haml
236
234
  - app/views/layouts/effective_orders_mailer_layout.html.haml
237
235
  - config/effective_orders.rb
238
236
  - config/routes.rb
@@ -1,20 +0,0 @@
1
- .card-header.text-center
2
- %h3= plan[:name]
3
- %p= plan[:description]
4
-
5
- .card-body
6
- %p Includes features
7
-
8
- - if plan[:amount] == 0
9
- %p Free plan
10
-
11
- .visible-when-unselected.text-center
12
- = link_to 'Select', '#', class: 'btn btn-secondary', 'data-toggle': 'card'
13
-
14
- .visible-when-selected.text-center
15
- - if subscribed
16
- %p
17
- %strong Your current plan
18
- - else
19
- %p This is #{['an amazing', 'a great', 'an excellent', 'an exceptional', 'a marvelous', 'a wonderful'].sample} plan
20
-
@@ -1,3 +0,0 @@
1
- .effective-radios.card-deck
2
- .card.active
3
- = render_plan(subscription.plan)