-
Notifications
You must be signed in to change notification settings - Fork 113
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix(subscription upgrade): Upgrade subscriptions previously considere…
…d downgrade on amount increase (#2823) ## Context Currently, when a user updates the subscription fee amount for a plan associated with a pending downgrade (scheduled to activate at the end of the billing period), the system does not treat the pending subscription as an upgrade. As a result, the new subscription is not activated, and the previous one is not terminated. ## Description This change extracts the subscription upgrade logic into a dedicated service, ensuring it can be consistently applied across flows that trigger subscription upgrades. Currently, this includes plan updates and initial subscription creation. An additional edge case, involving plan subscription overrides, is not addressed in this PR. Handling this scenario introduces extra technical and product complexities that require further consideration.
- Loading branch information
Showing
6 changed files
with
389 additions
and
70 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
# frozen_string_literal: true | ||
|
||
module Subscriptions | ||
class PlanUpgradeService < BaseService | ||
def initialize(current_subscription:, plan:, params:) | ||
@current_subscription = current_subscription | ||
@plan = plan | ||
|
||
@params = params | ||
@name = params[:name].to_s.strip | ||
super | ||
end | ||
|
||
def call | ||
if current_subscription.starting_in_the_future? | ||
update_pending_subscription | ||
|
||
result.subscription = current_subscription | ||
return result | ||
end | ||
|
||
new_subscription = new_subcription_with_overrides | ||
|
||
ActiveRecord::Base.transaction do | ||
cancel_pending_subscription if pending_subscription? | ||
|
||
# Group subscriptions for billing | ||
billable_subscriptions = billable_subscriptions(new_subscription) | ||
|
||
# Terminate current subscription as part of the upgrade process | ||
Subscriptions::TerminateService.call( | ||
subscription: current_subscription, | ||
upgrade: true | ||
) | ||
|
||
new_subscription.mark_as_active! | ||
after_commit do | ||
SendWebhookJob.perform_later("subscription.started", new_subscription) | ||
end | ||
|
||
bill_subscriptions(billable_subscriptions) if billable_subscriptions.any? | ||
end | ||
|
||
result.subscription = new_subscription | ||
result | ||
rescue ActiveRecord::RecordInvalid => e | ||
result.record_validation_failure!(record: e.record) | ||
rescue BaseService::FailedResult => e | ||
result.fail_with_error!(e) | ||
end | ||
|
||
private | ||
|
||
attr_reader :current_subscription, :plan, :params, :name | ||
|
||
def new_subcription_with_overrides | ||
Subscription.new( | ||
customer: current_subscription.customer, | ||
plan: params.key?(:plan_overrides) ? override_plan : plan, | ||
name:, | ||
external_id: current_subscription.external_id, | ||
previous_subscription_id: current_subscription.id, | ||
subscription_at: current_subscription.subscription_at, | ||
billing_time: current_subscription.billing_time, | ||
ending_at: params.key?(:ending_at) ? params[:ending_at] : current_subscription.ending_at | ||
) | ||
end | ||
|
||
def update_pending_subscription | ||
current_subscription.plan = plan | ||
current_subscription.name = name if name.present? | ||
current_subscription.save! | ||
|
||
if current_subscription.should_sync_crm_subscription? | ||
Integrations::Aggregator::Subscriptions::Crm::UpdateJob.perform_later(subscription: current_subscription) | ||
end | ||
end | ||
|
||
def override_plan | ||
Plans::OverrideService.call(plan:, params: params[:plan_overrides].to_h.with_indifferent_access).plan | ||
end | ||
|
||
def cancel_pending_subscription | ||
current_subscription.next_subscription.mark_as_canceled! | ||
end | ||
|
||
def pending_subscription? | ||
return false unless current_subscription.next_subscription | ||
|
||
current_subscription.next_subscription.pending? | ||
end | ||
|
||
def billable_subscriptions(new_subscription) | ||
billable_subscriptions = if current_subscription.starting_in_the_future? | ||
[] | ||
elsif current_subscription.pending? | ||
[] | ||
elsif !current_subscription.terminated? | ||
[current_subscription] | ||
end.to_a | ||
|
||
billable_subscriptions << new_subscription if plan.pay_in_advance? && !new_subscription.in_trial_period? | ||
|
||
billable_subscriptions | ||
end | ||
|
||
def bill_subscriptions(billable_subscriptions) | ||
after_commit do | ||
billing_at = Time.current + 1.second | ||
BillSubscriptionJob.perform_later(billable_subscriptions, billing_at.to_i, invoicing_reason: :upgrading) | ||
BillNonInvoiceableFeesJob.perform_later(billable_subscriptions, billing_at) | ||
end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.