Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
114 changes: 80 additions & 34 deletions app/services/subscriptions/activate_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -39,17 +39,6 @@ def activate_from_pending
end
end

def activate_from_incomplete
return if subscription.activation_rules.rejected.exists?

subscription.mark_as_active!(timestamp)

after_commit do
bill_subscription if subscription.activation_rules.payment.none?
notify_started
end
end

def gate_subscription
subscription.mark_as_incomplete!(timestamp)

Expand All @@ -66,59 +55,116 @@ def gate_subscription
def activate_with_side_effects
if upgrade?
activate_for_upgrade
elsif downgrade?
activate_for_downgrade
else
activate_standalone
end
end

def activate_from_incomplete
return if subscription.activation_rules.rejected.exists?

if upgrade?
activate_for_upgrade
elsif downgrade?
activate_for_downgrade
else
subscription.mark_as_active!(timestamp)

after_commit do
bill_subscription if subscription.activation_rules.payment.none?
notify_started
end
end
end

def upgrade?
return false unless subscription.previous_subscription
return false if subscription.plan.id == subscription.previous_subscription.plan.id

subscription.plan.yearly_amount_cents >= subscription.previous_subscription.plan.yearly_amount_cents
end

def activate_standalone
subscription.mark_as_active!(timestamp)

emit_fixed_charge_events
def downgrade?
return false unless subscription.previous_subscription
return false if subscription.plan.id == subscription.previous_subscription.plan.id

after_commit do
bill_subscription(skip_charges: true)
notify_started
end
subscription.plan.yearly_amount_cents < subscription.previous_subscription.plan.yearly_amount_cents
end

def activate_for_upgrade
from_incomplete = subscription.incomplete?
previous_subscription = subscription.previous_subscription

Subscriptions::TerminateService.call(
subscription: subscription.previous_subscription,
subscription: previous_subscription,
upgrade: true
)

subscription.mark_as_active!(timestamp)

emit_fixed_charge_events
billable_subscriptions = [previous_subscription]

# When from_incomplete, the new subscription was already billed and its fixed-charge
# events emitted during gate_subscription — only the previous needs billing.
unless from_incomplete
emit_fixed_charge_events
billable_subscriptions << subscription if subscription.fixed_charges.pay_in_advance.any? ||
(subscription.plan.pay_in_advance? && !subscription.in_trial_period?)
end

after_commit { notify_started }

bill_upgrade_subscriptions
bill_rotation_subscriptions(billable_subscriptions, billing_at: Time.current + 1.second)
end

def billable_subscriptions
@billable_subscriptions ||= begin
billable = [subscription.previous_subscription]
bill_new_in_advance = subscription.fixed_charges.pay_in_advance.any? ||
(subscription.plan.pay_in_advance? && !subscription.in_trial_period?)
billable << subscription if bill_new_in_advance
billable
def activate_for_downgrade
from_incomplete = subscription.incomplete?
previous_subscription = subscription.previous_subscription

previous_subscription.mark_as_terminated!(timestamp)

subscription.mark_as_active!(timestamp)

billable_subscriptions = [previous_subscription]

# When from_incomplete, the new subscription was already billed and its fixed-charge
# events emitted during gate_subscription — only the previous needs billing.
unless from_incomplete
emit_fixed_charge_events
billable_subscriptions << subscription if subscription.fixed_charges.pay_in_advance.any? || subscription.plan.pay_in_advance?
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

terminate_and_start_next on app/services/subscriptions/terminate_service.rb previously did:

# NOTE: Create an invoice for the terminated subscription
#       if it has not been billed yet
#       or only for the charges if subscription was billed in advance
#       Also, add new pay in advance plan inside if applicable
billable_subscriptions = if next_subscription.plan.pay_in_advance? || next_subscription.fixed_charges.pay_in_advance.any?
  [subscription, next_subscription]
else
  [subscription]
end

It's omitting the !subscription.in_trial_period? check and this is probably a bug, but the current behavior was kept unchanged

end

after_commit do
SendWebhookJob.perform_later("subscription.terminated", previous_subscription)
Utils::ActivityLog.produce(previous_subscription, "subscription.terminated")

if previous_subscription.should_sync_hubspot_subscription?
Integrations::Aggregator::Subscriptions::Hubspot::UpdateJob.perform_later(subscription: previous_subscription)
end

notify_started
end

bill_rotation_subscriptions(billable_subscriptions, billing_at: timestamp)
end

def bill_upgrade_subscriptions
def activate_standalone
subscription.mark_as_active!(timestamp)

emit_fixed_charge_events

after_commit do
bill_subscription(skip_charges: true)
notify_started
end
end

def bill_rotation_subscriptions(billable_subscriptions, billing_at:)
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)
BillNonInvoiceableFeesJob.perform_later([subscription.previous_subscription], billing_at)
end
end

Expand All @@ -128,8 +174,8 @@ def notify_started

return unless subscription.should_sync_hubspot_subscription?

if upgrade?
# The new upgrade subscription has no Hubspot record yet.
if upgrade? || downgrade?
# The new upgrade/downgrade subscription has no Hubspot record yet.
Integrations::Aggregator::Subscriptions::Hubspot::CreateJob.perform_later(subscription:)
elsif !during_creation
# Skip when activating during subscription creation — CreateService
Expand Down
46 changes: 5 additions & 41 deletions app/services/subscriptions/terminate_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -78,49 +78,13 @@ def terminate_and_start_next(timestamp:)
return result unless next_subscription
return result unless next_subscription.pending?

rotation_date = Time.zone.at(timestamp)

ActiveRecord::Base.transaction do
subscription.mark_as_terminated!(rotation_date)

if subscription.should_sync_hubspot_subscription?
Integrations::Aggregator::Subscriptions::Hubspot::UpdateJob.perform_later(subscription:)
end

next_subscription.mark_as_active!(rotation_date)

EmitFixedChargeEventsService.call!(
subscriptions: [next_subscription],
timestamp: next_subscription.started_at + 1.second
)

if next_subscription.should_sync_hubspot_subscription?
Integrations::Aggregator::Subscriptions::Hubspot::UpdateJob.perform_later(next_subscription)
end
end

# NOTE: Create an invoice for the terminated subscription
# if it has not been billed yet
# or only for the charges if subscription was billed in advance
# Also, add new pay in advance plan inside if applicable
billable_subscriptions = if next_subscription.plan.pay_in_advance? || next_subscription.fixed_charges.pay_in_advance.any?
[subscription, next_subscription]
else
[subscription]
end
BillSubscriptionJob.perform_later(billable_subscriptions, timestamp, invoicing_reason: :upgrading)
BillNonInvoiceableFeesJob.perform_later([subscription], rotation_date) # Ignore next subscription since there can't be events

SendWebhookJob.perform_later("subscription.terminated", subscription)
Utils::ActivityLog.produce(subscription, "subscription.terminated")
SendWebhookJob.perform_later("subscription.started", next_subscription)
Utils::ActivityLog.produce(next_subscription, "subscription.started")

result.subscription = next_subscription
activation_result = Subscriptions::ActivateService.call!(
subscription: next_subscription,
timestamp: Time.zone.at(timestamp)
)

result.subscription = activation_result.subscription
result
rescue ActiveRecord::RecordInvalid => e
result.record_validation_failure!(record: e.record)
end

private
Expand Down
Loading
Loading