-
-
Notifications
You must be signed in to change notification settings - Fork 78
Subscriptions refactor #222
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -22,127 +22,31 @@ def show | |
|
|
||
| def change | ||
| new_plan_id = params[:stripe_plan_id] | ||
| possible_plan_ids = BillingPlan.where(available: true).map(&:stripe_plan_id) | ||
| raise "Invalid billing plan ID: #{new_plan_id}" unless SubscriptionService.available_plan?(new_plan_id) | ||
|
|
||
| raise "Invalid billing plan ID: #{new_plan_id}" unless possible_plan_ids.include? new_plan_id | ||
| # Grab the stripe object once upfront, since it's an external API call | ||
| stripe_customer = SubscriptionService.stripe_customer_object_for(current_user) | ||
|
|
||
| # If the user is changing to Starter, go ahead and cancel any active subscriptions and do it | ||
| if new_plan_id == 'starter' | ||
| current_user.active_subscriptions.each do |subscription| | ||
| subscription.update(end_date: Time.now) | ||
| end | ||
|
|
||
| if current_user.selected_billing_plan_id.nil? | ||
| old_billing_plan = BillingPlan.new(name: 'No billing plan', monthly_cents: 0) | ||
| else | ||
| old_billing_plan = BillingPlan.find(current_user.selected_billing_plan_id) | ||
| end | ||
| new_billing_plan = BillingPlan.find_by(stripe_plan_id: new_plan_id, available: true) | ||
| current_user.selected_billing_plan_id = new_billing_plan.id | ||
|
|
||
| # Remove any bonus bandwidth our old plan granted | ||
| current_user.upload_bandwidth_kb -= old_billing_plan.bonus_bandwidth_kb | ||
|
|
||
| current_user.save | ||
|
|
||
| Subscription.create( | ||
| user: current_user, | ||
| billing_plan: new_billing_plan, | ||
| start_date: Time.now, | ||
| end_date: Time.now.end_of_day + 5.years | ||
| ) | ||
|
|
||
| report_subscription_change_to_slack current_user, old_billing_plan, new_billing_plan | ||
|
|
||
| flash[:notice] = "You have been successfully downgraded to Starter." #todo proration/credit | ||
| return redirect_to subscription_path | ||
| # If a user is moving to premium, make sure they have a card on file | ||
| if SubscriptionService.PAID_PREMIUM_PLAN_IDS.include?(new_plan_id) && !SubscriptionService.has_card_on_file_for?(stripe_customer) | ||
| return redirect_to payment_info_path(plan: new_plan_id) | ||
| end | ||
|
|
||
| # Fetch current subscription | ||
| stripe_customer = Stripe::Customer.retrieve current_user.stripe_customer_id | ||
| stripe_subscription = stripe_customer.subscriptions.data[0] | ||
|
|
||
| # If the user already has a payment method on file, change their plan and add a new subscription | ||
| if stripe_customer.sources.total_count > 0 | ||
| # Cancel any active subscriptions, since we're changing plans | ||
| current_user.active_subscriptions.each do |subscription| | ||
| subscription.update(end_date: Time.now) | ||
| end | ||
|
|
||
| # Change subscription plan if they already have a payment method on file | ||
| stripe_subscription.plan = new_plan_id | ||
| begin | ||
| stripe_subscription.save | ||
| rescue Stripe::CardError => e | ||
| flash[:alert] = "We couldn't upgrade you to Premium because #{e.message.downcase} Please double check that your information is correct." | ||
| return redirect_to :back | ||
| end | ||
| # Make the plan transition | ||
| success = SubscriptionService.transition_plan( | ||
| user: current_user, | ||
| old_plan: BillingPlan.find(current_user.selected_billing_plan_id) | ||
| new_plan: BillingPlan.find(new_plan_id), | ||
| stripe_object: stripe_customer | ||
| ) | ||
|
|
||
| # If this is the first time this user is subscribing to Premium, gift them (and their referrer, if applicable) feature votes and space | ||
| existing_premium_subscriptions = current_user.subscriptions.where(billing_plan_id: [2, 3, 4, 5, 6]) | ||
| unless existing_premium_subscriptions.any? | ||
| referring_user = current_user.referrer || current_user | ||
|
|
||
| # First-time premium! | ||
| # +100 MB | ||
| current_user.update upload_bandwidth_kb: current_user.upload_bandwidth_kb + 100_000 | ||
| current_user.reload | ||
|
|
||
| # +1 vote | ||
| current_user.votes.create | ||
| # +1 vote if referred | ||
| current_user.votes.create if referring_user.present? | ||
|
|
||
| # +1 raffle entry | ||
| current_user.raffle_entries.create | ||
| # +1 raffle entry if referred | ||
| current_user.raffle_entries.create if referring_user.present? | ||
|
|
||
| if referring_user | ||
| # +100MB | ||
| referring_user.update upload_bandwidth_kb: referring_user.upload_bandwidth_kb + 100_000 | ||
|
|
||
| # +2 votes | ||
| referring_user.votes.create | ||
| referring_user.votes.create | ||
|
|
||
| # +2 raffle entries | ||
| referring_user.raffle_entries.create | ||
| referring_user.raffle_entries.create | ||
| end | ||
| end | ||
|
|
||
| if current_user.selected_billing_plan_id.nil? | ||
| old_billing_plan = BillingPlan.new(name: 'No billing plan', monthly_cents: 0) | ||
| else | ||
| old_billing_plan = BillingPlan.find(current_user.selected_billing_plan_id) | ||
| end | ||
| new_billing_plan = BillingPlan.find_by(stripe_plan_id: new_plan_id, available: true) | ||
| current_user.selected_billing_plan_id = new_billing_plan.id | ||
|
|
||
| # Add any bonus bandwidth our new plan grants, unless we're moving from Premium to Premium | ||
| premium_ids = [3, 4, 5, 6] | ||
| if !premium_ids.include?(current_user.selected_billing_plan_id) && premium_ids.include?(new_plan_id) | ||
| current_user.upload_bandwidth_kb += new_billing_plan.bonus_bandwidth_kb | ||
| end | ||
|
|
||
| current_user.save | ||
|
|
||
| Subscription.create( | ||
| user: current_user, | ||
| billing_plan: new_billing_plan, | ||
| start_date: Time.now, | ||
| end_date: Time.now.end_of_day + 5.years | ||
| ) | ||
|
|
||
| report_subscription_change_to_slack current_user, old_billing_plan, new_billing_plan | ||
|
|
||
| flash[:notice] = "You have been successfully upgraded to #{new_billing_plan.name}!" | ||
| redirect_to subscription_path | ||
| if success | ||
| flash[:notice] = "You have successfully changed your plan to #{new_billing_plan.name}." | ||
| else | ||
| # If they don't have a payment method on file, redirect them to collect one | ||
| redirect_to payment_info_path(plan: new_plan_id) | ||
| # error-specific messages are set in SubscriptionService, so we don't need to add a flash message here | ||
| end | ||
|
|
||
| redirect_to subscription_path | ||
| end | ||
|
|
||
| # This isn't actually needed since we change the paid plan to the free plan, but will be needed when we | ||
|
|
@@ -194,11 +98,7 @@ def information_change | |
| # After saving the user's payment method, move them over to the associated billing plan | ||
| new_billing_plan = BillingPlan.find_by(stripe_plan_id: params[:plan], available: true) | ||
| if new_billing_plan | ||
| if current_user.selected_billing_plan_id.nil? | ||
| old_billing_plan = BillingPlan.new(name: 'No billing plan', monthly_cents: 0) | ||
| else | ||
| old_billing_plan = BillingPlan.find(current_user.selected_billing_plan_id) | ||
| end | ||
| old_billing_plan = SubscriptionService.current_billing_plan_for(current_user) | ||
| current_user.selected_billing_plan_id = new_billing_plan.id | ||
|
|
||
| # Remove any bonus bandwidth our old plan granted | ||
|
|
@@ -218,42 +118,16 @@ def information_change | |
| end | ||
|
|
||
| # End all currently-active subscriptions | ||
| current_user.active_subscriptions.each do |subscription| | ||
| subscription.update(end_date: Time.now) | ||
| end | ||
| SubscriptionService.end_all_active_subscriptions_for current_user | ||
|
|
||
| # If this is the first time this user is subscribing to Premium, gift them (and their referrer, if applicable) feature votes and space | ||
| existing_premium_subscriptions = current_user.subscriptions.where(billing_plan_id: [2, 3, 4, 5, 6]) | ||
| unless existing_premium_subscriptions.any? | ||
| referring_user = current_user.referrer || current_user | ||
|
|
||
| # First-time premium! | ||
| # +100 MB | ||
| current_user.update upload_bandwidth_kb: current_user.upload_bandwidth_kb + 100_000 | ||
| current_user.reload | ||
|
|
||
| # +1 vote | ||
| current_user.votes.create | ||
| # +1 vote if referred | ||
| current_user.votes.create if referring_user.present? | ||
|
|
||
| # +1 raffle entry | ||
| current_user.raffle_entries.create | ||
| # +1 raffle entry if referred | ||
| current_user.raffle_entries.create if referring_user.present? | ||
|
|
||
| if referring_user | ||
| # +100MB | ||
| referring_user.update upload_bandwidth_kb: referring_user.upload_bandwidth_kb + 100_000 | ||
|
|
||
| # +2 votes | ||
| referring_user.votes.create | ||
| referring_user.votes.create | ||
|
|
||
| # +2 raffle entries | ||
| referring_user.raffle_entries.create | ||
| referring_user.raffle_entries.create | ||
| end | ||
| if existing_premium_subscriptions.none? | ||
| SubscriptionService.add_first_time_premium_benefits_to(current_user) | ||
| SubscriptionService.add_referral_benefits_to( | ||
| referree: current_user | ||
| referrer: current_user.referrer || current_user | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. unexpected token tIDENTIFIER |
||
| ) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. unexpected token tRPAREN |
||
| end | ||
|
|
||
| # And create a subscription for them for the current plan | ||
|
|
@@ -264,7 +138,7 @@ def information_change | |
| end_date: Time.now.end_of_day + 31.days | ||
| ) | ||
|
|
||
| report_subscription_change_to_slack current_user, old_billing_plan, new_billing_plan | ||
| SubscriptionService.report_subscription_change_to_slack current_user, old_billing_plan, new_billing_plan | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ooo didn't realize this was a thing! |
||
|
|
||
| notice << "you have been successfully upgraded to #{new_billing_plan.name}" | ||
| end | ||
|
|
@@ -303,35 +177,4 @@ def stripe_webhook | |
| Mixpanel::Tracker.new(Rails.application.config.mixpanel_token).track(current_user.id, 'stripe webhook') if Rails.env.production? | ||
| #todo handle webhooks | ||
| end | ||
|
|
||
| def report_subscription_change_to_slack user, from, to | ||
| return unless Rails.env == 'production' | ||
| slack_hook = ENV['SLACK_HOOK'] | ||
| return unless slack_hook | ||
|
|
||
| notifier = Slack::Notifier.new slack_hook, | ||
| channel: '#subscriptions', | ||
| username: 'tristan' | ||
|
|
||
| if from.nil? || to.nil? | ||
| delta = ":tada: LOL :tada:" | ||
| elsif from.monthly_cents < to.monthly_cents | ||
| delta = ":tada: *UPGRADE* :tada:" | ||
| elsif from.monthly_cents == to.monthly_cents | ||
| delta = ":tada: ... sidegrade ... :tada:" | ||
| else | ||
| delta = ":wave: Downgrade" | ||
| end | ||
|
|
||
| active_subscriptions = Subscription.where('start_date < ?', Time.now).where('end_date > ?', Time.now) | ||
| total_subs_monthly = active_subscriptions.map(&:billing_plan).sum(&:monthly_cents).to_f / 100.0 | ||
|
|
||
| notifier.ping [ | ||
| "#{delta} for #{user.email.split('@').first}@ (##{user.id})", | ||
| "From: *#{from.name}* ($#{from.monthly_cents / 100.0}/month)", | ||
| "To: *#{to.name}* (#{to.stripe_plan_id}) ($#{to.monthly_cents / 100.0}/month)", | ||
| "#{active_subscriptions.count} subscriptions total $#{'%.2f' % total_subs_monthly}/mo" | ||
| ].join("\n") | ||
|
|
||
| end | ||
| end | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,145 @@ | ||
| class SubscriptionService < Service | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Controllers to Services! Love it! |
||
| STARTER_PLAN_IDS = [1] | ||
| FREE_FOR_LIFE_PLAN_IDS = [2] | ||
| PAID_PREMIUM_PLAN_IDS = [3, 4, 5, 6] | ||
|
|
||
| SUBSCRIPTION_LENGTH = 5.years #todo refactor this out | ||
|
|
||
| def self.transition_plan user:, old_plan:, new_plan:, stripe_object: nil | ||
| # Fetch the user's current Stripe information | ||
| stripe_object ||= SubscriptionService.stripe_customer_object_for(current_user) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Any chance this could still return nil? |
||
| stripe_subscription = stripe_object.subscriptions.data[0] | ||
|
|
||
| # End all active subscriptions | ||
| SubscriptionService.end_all_active_subscriptions_for(user) | ||
|
|
||
| # Move the user over to their new plan on Stripe | ||
| stripe_subscription.plan = new_plan.id | ||
| begin | ||
| stripe_subscription.save | ||
| rescue Stripe::CardError => e | ||
| flash[:alert] = "We couldn't upgrade you to Premium because #{e.message.downcase} Please double check that your information is correct." | ||
| return false | ||
| end | ||
|
|
||
| # Move user over to their new billing plan | ||
| user.selected_billing_plan_id = new_plan.id | ||
|
|
||
| # Remove any added space from the old plan | ||
| user.upload_bandwidth_kb -= old_plan.bonus_bandwidth_kb | ||
|
|
||
| # Add any added space from the new plan | ||
| user.upload_bandwidth_kb += new_plan.bonus_bandwidth_kb | ||
|
|
||
| # Add any first-time-premium bonuses | ||
| if self.first_time_premium?(user) | ||
| add_first_time_premium_benefits_to(user) | ||
| add_referral_benefits_to( | ||
| referree: user | ||
| referrer: user.referrer || user # if a user wasn't referred by anyone, we treat them as referring themselves <3 | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. unexpected token tIDENTIFIER |
||
| ) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. unexpected token tRPAREN |
||
| end | ||
|
|
||
| # Save all changes to the user! | ||
| user.save! | ||
|
|
||
| # Create the new Subscription object | ||
| Subscription.create( | ||
| user: user, | ||
| billing_plan: new_plan, | ||
| start_date: DateTime.current, | ||
| end_date: DateTime.current.end_of_day + self.SUBSCRIPTION_LENGTH | ||
| ) | ||
|
|
||
| # Let Slack know about the changes | ||
| SubscriptionService.report_subscription_change_to_slack user, old_plan, new_plan | ||
|
|
||
| # Success! | ||
| true | ||
|
|
||
| # rescue | ||
| # false | ||
| end | ||
|
|
||
| def self.stripe_customer_object_for user | ||
| Stripe::Customer.retrieve user.stripe_customer_id | ||
| end | ||
|
|
||
| def self.has_card_on_file_for? stripe_object | ||
| stripe_object.sources.total_count > 0 | ||
| end | ||
|
|
||
| def self.first_time_premium? user | ||
| user.subscriptions.where(billing_plan_id: PAID_PREMIUM_PLAN_IDS).empty? | ||
| end | ||
|
|
||
| def self.available_plan? id: | ||
| possible_plan_ids = BillingPlan.where(available: true).pluck(:stripe_plan_id) | ||
| possible_plan_ids.include?(id) | ||
| end | ||
|
|
||
| def self.current_billing_plan_for user | ||
| if user.selected_billing_plan_id.nil? | ||
| BillingPlan.new(name: 'No billing plan', monthly_cents: 0) | ||
| else | ||
| BillingPlan.find(user.selected_billing_plan_id) | ||
| end | ||
| end | ||
|
|
||
| def self.end_all_active_subscriptions_for user | ||
| user.active_subscriptions.each do |subscription| | ||
| subscription.update(end_date: DateTime.current) | ||
| end | ||
| end | ||
|
|
||
| def self.add_first_time_premium_benefits_to user | ||
| # +100 MB | ||
| user.update(upload_bandwidth_kb: user.upload_bandwidth_kb + 100_000) | ||
|
|
||
| # +1 vote | ||
| user.votes.create | ||
| end | ||
|
|
||
| def self.add_referral_benefits_to referree:, referrer: | ||
| # +1 vote for referred user | ||
| referree.votes.create | ||
|
|
||
| # +2 votes for referring user | ||
| referrer.votes.create | ||
| referrer.votes.create | ||
|
|
||
| # +100MB for referrer | ||
| referrer.update(upload_bandwidth_kb: referrer.upload_bandwidth_kb + 100_000) | ||
| end | ||
|
|
||
| def self.report_subscription_change_to_slack user, from, to | ||
| return unless Rails.env == 'production' | ||
| slack_hook = ENV['SLACK_HOOK'] | ||
| return unless slack_hook | ||
|
|
||
| notifier = Slack::Notifier.new slack_hook, | ||
| channel: '#subscriptions', | ||
| username: 'tristan' | ||
|
|
||
| if from.nil? || to.nil? | ||
| delta = ":tada: LOL :tada:" | ||
| elsif from.monthly_cents < to.monthly_cents | ||
| delta = ":tada: *UPGRADE* :tada:" | ||
| elsif from.monthly_cents == to.monthly_cents | ||
| delta = ":tada: ... sidegrade ... :tada:" | ||
| else | ||
| delta = ":wave: Downgrade" | ||
| end | ||
|
|
||
| active_subscriptions = Subscription.where('start_date < ?', Time.now).where('end_date > ?', Time.now) | ||
| total_subs_monthly = active_subscriptions.map(&:billing_plan).sum(&:monthly_cents).to_f / 100.0 | ||
|
|
||
| notifier.ping [ | ||
| "#{delta} for #{user.email.split('@').first}@ (##{user.id})", | ||
| "From: *#{from.name}* ($#{from.monthly_cents / 100.0}/month)", | ||
| "To: *#{to.name}* (#{to.stripe_plan_id}) ($#{to.monthly_cents / 100.0}/month)", | ||
| "#{active_subscriptions.count} subscriptions total $#{'%.2f' % total_subs_monthly}/mo" | ||
| ].join("\n") | ||
| end | ||
|
|
||
| end | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
unexpected token tIDENTIFIER
unexpected token tCOMMA