diff --git a/app/services/subscriptions/dates/monthly_service.rb b/app/services/subscriptions/dates/monthly_service.rb index cc934c17b65..970e182e503 100644 --- a/app/services/subscriptions/dates/monthly_service.rb +++ b/app/services/subscriptions/dates/monthly_service.rb @@ -69,11 +69,9 @@ def compute_to_date(from_date = compute_from_date) # NOTE: if subscription anniversary day is higher than the current last day of the month, # subscription period, will end on the previous end of day - if last_day_of_month?(date) && subscription_at.day > date.day - date - 1.day - else - date - end + return date - 1.day if last_day_of_month?(date) && subscription_at.day > date.day + + date end def compute_next_end_of_period diff --git a/app/services/subscriptions/dates/quarterly_service.rb b/app/services/subscriptions/dates/quarterly_service.rb index 37550dc5248..6b6286fc420 100644 --- a/app/services/subscriptions/dates/quarterly_service.rb +++ b/app/services/subscriptions/dates/quarterly_service.rb @@ -39,6 +39,16 @@ def compute_duration(from_date:) alias compute_charges_duration compute_duration def compute_base_date + # NOTE: if subscription anniversary is on last day of month and current month days count + # is less than month anniversary day count, we need to use the last day of the previous month + if subscription.anniversary? && last_day_of_month?(billing_date) && (billing_date.day < subscription_at.day) + if (billing_date - 3.months).end_of_month.day >= subscription_at.day + return (billing_date - 3.months).end_of_month.change(day: subscription_at.day) + end + + return (billing_date - 3.months).end_of_month + end + billing_date - 3.months end @@ -56,7 +66,13 @@ def compute_to_date(from_date = compute_from_date) year += 1 end - build_date(year, month, day) + date = build_date(year, month, day) + + # NOTE: if subscription anniversary day is higher than the current last day of the month, + # subscription period, will end on the previous end of day + return date - 1.day if last_day_of_month?(date) && subscription_at.day > date.day + + date end def compute_next_end_of_period diff --git a/spec/scenarios/subscriptions/billing_spec.rb b/spec/scenarios/subscriptions/billing_spec.rb index 09664c6cfe6..bcc8a0566d5 100644 --- a/spec/scenarios/subscriptions/billing_spec.rb +++ b/spec/scenarios/subscriptions/billing_spec.rb @@ -498,6 +498,51 @@ it_behaves_like 'a subscription billing without duplicated invoices' end + context 'with anniversary on a 31st' do + let(:billing_times) do + [ + DateTime.new(2023, 3, 31, 1), + DateTime.new(2023, 6, 30, 1), + DateTime.new(2023, 9, 30, 2), + DateTime.new(2023, 12, 31, 2), + ] + end + + let(:subscription_time) { DateTime.new(2022, 12, 31) } + + it_behaves_like 'a subscription billing on every billing day' + end + + context 'with anniversary on a 30' do + let(:billing_times) do + [ + DateTime.new(2023, 1, 30, 1), + DateTime.new(2023, 4, 30, 1), + DateTime.new(2023, 7, 30, 2), + DateTime.new(2023, 10, 30, 2), + ] + end + + let(:subscription_time) { DateTime.new(2022, 4, 30) } + + it_behaves_like 'a subscription billing on every billing day' + end + + context 'with anniversary on a 28 of february' do + let(:billing_times) do + [ + DateTime.new(2023, 2, 28, 1), + DateTime.new(2023, 5, 28, 1), + DateTime.new(2023, 8, 28, 2), + DateTime.new(2023, 11, 28, 2), + ] + end + + let(:subscription_time) { DateTime.new(2022, 2, 28) } + + it_behaves_like 'a subscription billing on every billing day' + end + context 'with UTC+ timezone' do let(:timezone) { 'Asia/Kolkata' } let(:subscription_time) { DateTime.new(2023, 2, 2) } diff --git a/spec/services/subscriptions/dates/quarterly_service_spec.rb b/spec/services/subscriptions/dates/quarterly_service_spec.rb index 8b6ff5c26c4..d0399b2232b 100644 --- a/spec/services/subscriptions/dates/quarterly_service_spec.rb +++ b/spec/services/subscriptions/dates/quarterly_service_spec.rb @@ -254,7 +254,16 @@ let(:billing_at) { DateTime.parse('01 Mar 2022') } it 'returns the last day of the previous month' do - expect(result).to eq('2022-02-28 23:59:59 UTC') + expect(result).to eq('2022-02-27 23:59:59 UTC') + end + + context 'when subscription is not the last day of the month' do + let(:subscription_at) { DateTime.parse('30 Jan 2022') } + let(:billing_at) { DateTime.parse('30 Apr 2022') } + + it 'returns the last day of the month' do + expect(result).to eq('2022-04-29 23:59:59 UTC') + end end end