Skip to content
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

feat (plan-changes): Adjust boundaries upon subscription upgrade #1695

Merged
merged 5 commits into from
Feb 20, 2024
Merged
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
16 changes: 16 additions & 0 deletions app/models/subscription.rb
Original file line number Diff line number Diff line change
Expand Up @@ -135,4 +135,20 @@ def display_name
def invoice_name
name.presence || plan.invoice_name
end

# When upgrade, we want to bill one day less since date of the upgrade will be
# included in the first invoice for the new plan
def date_diff_with_timezone(from_datetime, to_datetime)
number_od_days = Utils::DatetimeService.date_diff_with_timezone(
from_datetime,
to_datetime,
customer.applicable_timezone,
)

return number_od_days unless terminated? && upgraded?

number_od_days -= 1

number_od_days.negative? ? 0 : number_od_days
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -102,11 +102,7 @@ def period_duration
# we want to bill the persisted metrics at prorata of the full period duration.
# ie: the number of day of the terminated period divided by number of days without termination
def persisted_pro_rata
Utils::DatetimeService.date_diff_with_timezone(
from_datetime,
to_datetime,
subscription.customer.applicable_timezone,
).fdiv(period_duration)
subscription.date_diff_with_timezone(from_datetime, to_datetime).fdiv(period_duration)
end

attr_accessor :options
Expand Down
12 changes: 2 additions & 10 deletions app/services/billable_metrics/prorated_aggregations/sum_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -139,11 +139,7 @@ def compute_event_aggregation
def persisted_sum
persisted_event_store_instante.prorated_sum(
period_duration:,
persisted_duration: Utils::DatetimeService.date_diff_with_timezone(
from_datetime,
to_datetime,
subscription.customer.applicable_timezone,
),
persisted_duration: subscription.date_diff_with_timezone(from_datetime, to_datetime),
)
end

Expand Down Expand Up @@ -180,11 +176,7 @@ def compute_grouped_event_aggregation
def grouped_persisted_sums
persisted_event_store_instante.grouped_prorated_sum(
period_duration:,
persisted_duration: Utils::DatetimeService.date_diff_with_timezone(
from_datetime,
to_datetime,
subscription.customer.applicable_timezone,
),
persisted_duration: subscription.date_diff_with_timezone(from_datetime, to_datetime),
)
end
end
Expand Down
10 changes: 7 additions & 3 deletions app/services/credit_notes/create_from_termination.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@

module CreditNotes
class CreateFromTermination < BaseService
def initialize(subscription:, reason: 'order_change')
def initialize(subscription:, reason: 'order_change', upgrade: false)
@subscription = subscription
@reason = reason
@upgrade = upgrade
lovrocolic marked this conversation as resolved.
Show resolved Hide resolved

super
end
Expand Down Expand Up @@ -37,7 +38,7 @@ def call

private

attr_accessor :subscription, :reason
attr_accessor :subscription, :reason, :upgrade

delegate :plan, :terminated_at, :customer, to: :subscription

Expand Down Expand Up @@ -70,6 +71,7 @@ def terminated_at_in_timezone

def remaining_duration
billed_from = terminated_at_in_timezone.end_of_day.utc.to_date
billed_from -= 1.day if upgrade

if plan.has_trial? && subscription.trial_end_date >= billed_from
billed_from = if subscription.trial_end_date > to_date
Expand All @@ -79,7 +81,9 @@ def remaining_duration
end
end

(to_date - billed_from).to_i
duration = (to_date - billed_from).to_i

duration.negative? ? 0 : duration
end

def creditable_amount_cents(item_amount)
Expand Down
6 changes: 1 addition & 5 deletions app/services/fees/create_true_up_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,7 @@ def prorated_min_amount_cents
# NOTE: number of days between beginning of the period and the termination date
from_datetime = boundaries.charges_from_datetime.to_time
to_datetime = boundaries.charges_to_datetime.to_time
number_of_day_to_bill = Utils::DatetimeService.date_diff_with_timezone(
from_datetime,
to_datetime,
subscription.customer.applicable_timezone,
)
number_of_day_to_bill = subscription.date_diff_with_timezone(from_datetime, to_datetime)

date_service.charge_single_day_price(charge:) * number_of_day_to_bill
end
Expand Down
6 changes: 1 addition & 5 deletions app/services/fees/subscription_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -181,11 +181,7 @@ def terminated_amount
end

# NOTE: number of days between beginning of the period and the termination date
number_of_day_to_bill = Utils::DatetimeService.date_diff_with_timezone(
from_datetime,
to_datetime,
customer.applicable_timezone,
)
number_of_day_to_bill = subscription.date_diff_with_timezone(from_datetime, to_datetime)

# Remove later customer timezone fix while passing optional_from_date
# single_day_price method should return correct amount even without the timezone fix since
Expand Down
2 changes: 1 addition & 1 deletion app/services/subscriptions/create_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ def upgrade_subscription

# NOTE: When upgrading, the new subscription becomes active immediatly
# The previous one must be terminated
Subscriptions::TerminateService.call(subscription: current_subscription)
Subscriptions::TerminateService.call(subscription: current_subscription, upgrade: true)

new_subscription.mark_as_active!
perform_later(job_class: SendWebhookJob, arguments: ['subscription.started', new_subscription])
Expand Down
6 changes: 4 additions & 2 deletions app/services/subscriptions/terminate_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@

module Subscriptions
class TerminateService < BaseService
def initialize(subscription:, async: true)
def initialize(subscription:, async: true, upgrade: false)
@subscription = subscription
@async = async
@upgrade = upgrade

super
end
Expand All @@ -25,6 +26,7 @@ def call
credit_note_result = CreditNotes::CreateFromTermination.new(
subscription:,
reason: 'order_cancellation',
upgrade:,
).call
credit_note_result.raise_if_error!
end
Expand Down Expand Up @@ -76,7 +78,7 @@ def terminate_and_start_next(timestamp:)

private

attr_reader :subscription, :async
attr_reader :subscription, :async, :upgrade

def bill_subscription
if async
Expand Down
46 changes: 46 additions & 0 deletions spec/models/subscription_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -396,4 +396,50 @@
end
end
end

describe '.date_diff_with_timezone' do
let(:from_datetime) { Time.zone.parse('2023-08-31T23:10:00') }
let(:to_datetime) { Time.zone.parse('2023-09-30T22:59:59') }
let(:customer) { create(:customer, timezone:) }
let(:terminated_at) { nil }
let(:timezone) { 'Europe/Paris' }

let(:subscription) do
create(
:subscription,
plan:,
customer:,
terminated_at:,
)
end

let(:result) do
subscription.date_diff_with_timezone(from_datetime, to_datetime)
end

it 'returns the number of days between the two datetime' do
expect(result).to eq(30)
end

context 'with terminated and upgraded subscription' do
let(:terminated_at) { Time.zone.parse('2023-09-30T22:59:59') }
let(:new_subscription) do
create(
:subscription,
plan:,
customer:,
previous_subscription_id: subscription.id,
)
end

before do
subscription.terminated!
new_subscription
end

it 'takes the daylight saving time into account' do
expect(result).to eq(29)
end
end
end
end
4 changes: 2 additions & 2 deletions spec/scenarios/charge_models/prorated_graduated_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -566,7 +566,7 @@

subscription = customer.subscriptions.first

travel_to(DateTime.new(2023, 10, 18)) do
travel_to(DateTime.new(2023, 10, 18, 5, 20)) do
create(
:graduated_charge,
billable_metric:,
Expand Down Expand Up @@ -608,7 +608,7 @@

invoice = subscription.invoices.order(created_at: :desc).first
expect(invoice.fees.charge_kind.count).to eq(1)
# 30226 (17 / 31 * 75 units) + 2.58 = 2 / 31 * 20 units (prorated event in termination period)
# 30226 (17 / 31 * 75 units) + 2.58 (2 / 31 * 20 units - prorated event in termination period)
expect(invoice.total_amount_cents).to eq(27_323)
end

Expand Down
Loading
Loading