diff --git a/app/services/subscriptions/dates/monthly_service.rb b/app/services/subscriptions/dates/monthly_service.rb index 0188d44c200..ffa2a1c6a58 100644 --- a/app/services/subscriptions/dates/monthly_service.rb +++ b/app/services/subscriptions/dates/monthly_service.rb @@ -4,13 +4,28 @@ module Subscriptions module Dates class MonthlyService < Subscriptions::DatesService def compute_from_date(date = base_date) - if terminated_pay_in_arrear? + if plan.pay_in_advance? || terminated_pay_in_arrear? return subscription.anniversary? ? previous_anniversary_day(billing_date) : billing_date.beginning_of_month end subscription.anniversary? ? previous_anniversary_day(date) : date.beginning_of_month end + def compute_charges_from_date + return from_date if plan.pay_in_arrear? + return base_date.beginning_of_month if calendar? + + previous_anniversary_day(base_date) + end + + def compute_charges_to_date + return to_date if plan.pay_in_arrear? + + # NOTE: In pay in advance scenario, from_date will be the begining of the new period. + # To get the end of the previous one, we just have to take the day before + from_date - 1.day # TODO: check with upgrade on subscription day + end + private def compute_base_date @@ -32,10 +47,6 @@ def compute_to_date build_date(year, month, day) end - def compute_charges_from_date - from_date - end - def compute_next_end_of_period(date) return date.end_of_month if calendar? @@ -56,6 +67,12 @@ def compute_next_end_of_period(date) build_date(year, month, day) - 1.day end + def compute_previous_beginning_of_period(date) + return date.beginning_of_month if calendar? + + previous_anniversary_day(date) + end + def previous_anniversary_day(date) year = nil month = nil @@ -71,6 +88,13 @@ def previous_anniversary_day(date) build_date(year, month, day) end + + def compute_duration(from_date:) + return Time.days_in_month(from_date.month, from_date.year) if calendar? + + next_month_date = compute_to_date + (next_month_date.to_date + 1.day - from_date.to_date).to_i + end end end end diff --git a/app/services/subscriptions/dates/weekly_service.rb b/app/services/subscriptions/dates/weekly_service.rb index 8bdaaca6a52..d7ba28df0a0 100644 --- a/app/services/subscriptions/dates/weekly_service.rb +++ b/app/services/subscriptions/dates/weekly_service.rb @@ -3,6 +3,8 @@ module Subscriptions module Dates class WeeklyService < Subscriptions::DatesService + WEEK_DURATION = 7 + private def compute_base_date @@ -10,7 +12,7 @@ def compute_base_date end def compute_from_date - if terminated_pay_in_arrear? + if plan.pay_in_advance? || terminated_pay_in_arrear? return subscription.anniversary? ? previous_anniversary_day(billing_date) : billing_date.beginning_of_week end @@ -24,7 +26,17 @@ def compute_to_date end def compute_charges_from_date - from_date + return from_date if plan.pay_in_arrear? + + from_date - 1.week + end + + def compute_charges_to_date + return to_date if plan.pay_in_arrear? + + # NOTE: In pay in advance scenario, from_date will be the begining of the new period. + # To get the end of the previous one, we just have to take the day before + from_date - 1.day end def compute_next_end_of_period(date) @@ -35,6 +47,12 @@ def compute_next_end_of_period(date) date.next_occurring(subscription_day_name) - 1.day end + def compute_previous_beginning_of_period(date) + return date.beginning_of_week if calendar? + + previous_anniversary_day(date) + end + def previous_anniversary_day(date) date.prev_occurring(subscription_day_name) end @@ -42,6 +60,10 @@ def previous_anniversary_day(date) def subscription_day_name @subscription_day_name ||= subscription_date.strftime('%A').downcase.to_sym end + + def compute_duration(*) + WEEK_DURATION + end end end end diff --git a/app/services/subscriptions/dates/yearly_service.rb b/app/services/subscriptions/dates/yearly_service.rb index 0d06bef0c65..33fb284c2cb 100644 --- a/app/services/subscriptions/dates/yearly_service.rb +++ b/app/services/subscriptions/dates/yearly_service.rb @@ -3,6 +3,12 @@ module Subscriptions module Dates class YearlyService < Subscriptions::DatesService + def first_month_in_yearly_period? + return billing_date.month == 1 if calendar? + + monthly_service.compute_from_date(billing_date - 1.month).month == subscription_date.month + end + private def compute_base_date @@ -10,11 +16,11 @@ def compute_base_date end def monthly_service - @monthly_service ||= Subscriptions::Dates::MonthlyService.new(subscription, billing_date) + @monthly_service ||= Subscriptions::Dates::MonthlyService.new(subscription, billing_date, current_usage) end def compute_from_date - if terminated_pay_in_arrear? + if plan.pay_in_advance? || terminated_pay_in_arrear? return subscription.anniversary? ? previous_anniversary_day(billing_date) : billing_date.beginning_of_year end @@ -32,9 +38,21 @@ def compute_to_date end def compute_charges_from_date - return from_date unless plan.bill_charges_monthly + return monthly_service.compute_charges_from_date if plan.bill_charges_monthly + return from_date if plan.pay_in_arrear? + return base_date.beginning_of_year if calendar? + + previous_anniversary_day(base_date) + end + + def compute_charges_to_date + return to_date if plan.pay_in_arrear? + + # NOTE: In pay in advance scenario, from_date will be the begining of the new period. + # To get the end of the previous one, we just have to take the day before + return from_date - 1.day unless plan.bill_charges_monthly - monthly_service.compute_from_date(billing_date - 1.month) + monthly_service.compute_charges_to_date end def compute_next_end_of_period(date) @@ -51,6 +69,12 @@ def compute_next_end_of_period(date) build_date(year + 1, month, day) - 1.day end + def compute_previous_beginning_of_period(date) + return date.beginning_of_year if calendar? + + previous_anniversary_day(date) + end + def previous_anniversary_day(date) year = date.month < subscription_date.month ? date.year - 1 : date.year month = subscription_date.month @@ -58,6 +82,16 @@ def previous_anniversary_day(date) build_date(year, month, day) end + + def compute_duration(from_date:) + return Time.days_in_year(from_date.year) if calendar? + + year = from_date.year + # NOTE: if after February we must check if next year is a leap year + year += 1 if from_date.month > 2 + + Time.days_in_year(year) + end end end end diff --git a/app/services/subscriptions/dates_service.rb b/app/services/subscriptions/dates_service.rb index 128a356dcdf..871cda2083f 100644 --- a/app/services/subscriptions/dates_service.rb +++ b/app/services/subscriptions/dates_service.rb @@ -2,7 +2,7 @@ module Subscriptions class DatesService - def self.new_instance(subscription, billing_date) + def self.new_instance(subscription, billing_date, current_usage: false) klass = case subscription.plan.interval&.to_sym when :weekly Subscriptions::Dates::WeeklyService @@ -14,15 +14,16 @@ def self.new_instance(subscription, billing_date) raise NotImplementedError end - klass.new(subscription, billing_date) + klass.new(subscription, billing_date, current_usage) end - def initialize(subscription, billing_date) + def initialize(subscription, billing_date, current_usage) @subscription = subscription # NOTE: Billing date should usually be the end of the billing period + 1 day # When subscription is terminated, it is the termination day @billing_date = billing_date.to_date + @current_usage = current_usage end def from_date @@ -42,14 +43,8 @@ def to_date return @to_date if @to_date @to_date = compute_to_date - @to_date = subscription.terminated_at.to_date if subscription.terminated? && @to_date > subscription.terminated_at - # NOTE: When price plan is configured as `pay_in_advance`, subscription creation will be - # billed immediatly. An invoice must be generated for it with only the subscription fee. - # The invoicing period will be only one day: the subscription day - @to_date = subscription.started_at.to_date if plan.pay_in_advance? && subscription.fees.subscription_kind.none? - @to_date end @@ -60,18 +55,38 @@ def charges_from_date date end + def charges_to_date + date = compute_charges_to_date + date = subscription.terminated_at.to_date if subscription.terminated? && date > subscription.terminated_at + + date + end + def next_end_of_period(date) compute_next_end_of_period(date) end + # NOTE: Retrieve the beginning of the previous period based on the billing date + def previous_beginning_of_period(current_period: false) + date = base_date + date = billing_date if current_period + + compute_previous_beginning_of_period(date) + end + + def single_day_price(optional_from_date: nil) + duration = compute_duration(from_date: optional_from_date || compute_from_date) + plan.amount_cents.fdiv(duration.to_i) + end + private - attr_accessor :subscription, :billing_date + attr_accessor :subscription, :billing_date, :current_usage delegate :plan, :subscription_date, :calendar?, to: :subscription def base_date - @base_date ||= compute_base_date + @base_date ||= current_usage ? billing_date : compute_base_date end def terminated_pay_in_arrear? @@ -88,6 +103,10 @@ def build_date(year, month, day) Date.new(year, month, day) end + def compute_base_date + raise NotImplementedError + end + def compute_from_date raise NotImplementedError end @@ -100,8 +119,20 @@ def compute_charges_from_date raise NotImplementedError end + def compute_charges_to_date + raise NotImplementedError + end + def compute_next_end_of_period(date) raise NotImplementedError end + + def first_month_in_yearly_period? + false + end + + def compute_duration(from_date:) + raise NotImplementedError + end end end diff --git a/spec/services/subscriptions/dates/monthly_service_spec.rb b/spec/services/subscriptions/dates/monthly_service_spec.rb index a0b4690f10e..f5b0b9c42cf 100644 --- a/spec/services/subscriptions/dates/monthly_service_spec.rb +++ b/spec/services/subscriptions/dates/monthly_service_spec.rb @@ -3,7 +3,7 @@ require 'rails_helper' RSpec.describe Subscriptions::Dates::MonthlyService, type: :service do - subject(:date_service) { described_class.new(subscription, billing_date) } + subject(:date_service) { described_class.new(subscription, billing_date, false) } let(:subscription) do create( @@ -53,8 +53,8 @@ context 'when plan is pay in advance' do let(:pay_in_advance) { true } - it 'returns the beginning of the previous month' do - expect(result).to eq('2022-02-01') + it 'returns the beginning of the month' do + expect(result).to eq('2022-03-01') end end end @@ -64,7 +64,7 @@ let(:billing_time) { :anniversary } let(:billing_date) { DateTime.parse('03 Mar 2022') } - it 'returns the previous month month day' do + it 'returns the day in the previous month day' do expect(result).to eq('2022-02-02') end @@ -88,8 +88,8 @@ context 'when plan is pay in advance' do let(:pay_in_advance) { true } - it 'returns the previous month month day' do - expect(result).to eq('2022-02-02') + it 'returns the day in the current month' do + expect(result).to eq('2022-03-02') end end @@ -125,13 +125,11 @@ expect(result).to eq('2022-02-28') end - context 'when plan is pay in advance and billed for the first time' do - before { plan.update!(pay_in_advance: true) } - - let(:started_at) { DateTime.parse('07 Feb 2022') } + context 'when plan is pay in advance' do + let(:pay_in_advance) { true } - it 'returns the start date' do - expect(result).to eq(started_at.to_date.to_s) + it 'returns the end of the month' do + expect(result).to eq('2022-03-31') end end @@ -155,14 +153,14 @@ let(:billing_time) { :anniversary } let(:billing_date) { DateTime.parse('04 Mar 2022') } - it 'returns the previous month month day' do + it 'returns the day in the previous month' do expect(result).to eq('2022-03-01') end context 'when billing last month of year' do let(:billing_date) { DateTime.parse('04 Jan 2022') } - it 'returns the previous month month day' do + it 'returns the day in the previous month' do expect(result).to eq('2022-01-01') end end @@ -185,13 +183,11 @@ end end - context 'when plan is pay in advance and billed for the first time' do + context 'when plan is pay in advance' do before { plan.update!(pay_in_advance: true) } - let(:started_at) { DateTime.parse('08 Feb 2022') } - - it 'returns the start date' do - expect(result).to eq(started_at.to_date.to_s) + it 'returns the end of the current period' do + expect(result).to eq('2022-04-01') end end @@ -230,6 +226,15 @@ expect(result).to eq(subscription.started_at.to_date.to_s) end end + + context 'when plan is pay in advance' do + let(:pay_in_advance) { true } + let(:subscription_date) { DateTime.parse('02 Feb 2020') } + + it 'returns the start of the previous period' do + expect(result).to eq('2022-02-01') + end + end end context 'when billing_time is anniversary' do @@ -247,6 +252,76 @@ expect(result).to eq(subscription.started_at.to_date.to_s) end end + + context 'when plan is pay in advance' do + let(:pay_in_advance) { true } + let(:subscription_date) { DateTime.parse('02 Feb 2020') } + + it 'returns the start of the previous period' do + expect(result).to eq('2022-02-02') + end + end + end + end + + describe 'charges_to_date' do + let(:result) { date_service.charges_to_date.to_s } + + context 'when billing_time is calendar' do + let(:billing_time) { :calendar } + + it 'returns to_date' do + expect(result).to eq(date_service.to_date.to_s) + end + + context 'when subscription is terminated in the middle of a period' do + let(:terminated_at) { DateTime.parse('06 Mar 2022') } + + before do + subscription.update!(status: :terminated, terminated_at: terminated_at) + end + + it 'returns the terminated date' do + expect(result).to eq(subscription.terminated_at.to_date.to_s) + end + end + + context 'when plan is pay in advance' do + let(:pay_in_advance) { true } + + it 'returns the end of the previous period' do + expect(result).to eq((date_service.from_date - 1.day).to_s) + end + end + end + + context 'when billing_time is anniversary' do + let(:billing_time) { :anniversary } + let(:billing_date) { DateTime.parse('10 Mar 2022') } + + it 'returns to_date' do + expect(result).to eq(date_service.to_date.to_s) + end + + context 'when subscription is terminated in the middle of a period' do + let(:terminated_at) { DateTime.parse('06 Mar 2022') } + + before do + subscription.update!(status: :terminated, terminated_at: terminated_at) + end + + it 'returns the terminated date' do + expect(result).to eq(subscription.terminated_at.to_date.to_s) + end + end + + context 'when plan is pay in advance' do + let(:pay_in_advance) { true } + + it 'returns the end of the previous period' do + expect(result).to eq((date_service.from_date - 1.day).to_s) + end + end end end @@ -283,4 +358,80 @@ end end end + + describe 'compute_previous_beginning_of_period' do + let(:result) { date_service.previous_beginning_of_period(current_period: current_period).to_s } + + let(:current_period) { false } + + context 'when billing_time is calendar' do + let(:billing_time) { :calendar } + + it 'returns the first day of the previous month' do + expect(result).to eq('2022-02-01') + end + + context 'with current period argument' do + let(:current_period) { true } + + it 'returns the first day of the month' do + expect(result).to eq('2022-03-01') + end + end + end + + context 'when billing_time is anniversary' do + let(:billing_time) { :anniversary } + + it 'returns the beginning of the previous period' do + expect(result).to eq('2022-02-02') + end + + context 'with current period argument' do + let(:current_period) { true } + + it 'returns the beginning of the current period' do + expect(result).to eq('2022-03-02') + end + end + end + end + + describe 'single_day_price' do + let(:result) { date_service.single_day_price } + + context 'when billing_time is calendar' do + let(:billing_time) { :calendar } + + it 'returns the price of single day' do + expect(result).to eq(plan.amount_cents.fdiv(28)) + end + + context 'when on a leap year' do + let(:subscription_date) { DateTime.parse('28 Feb 2019') } + let(:billing_date) { DateTime.parse('01 Mar 2020') } + + it 'returns the price of single day' do + expect(result).to eq(plan.amount_cents.fdiv(29)) + end + end + end + + context 'when billing_time is anniversary' do + let(:billing_time) { :anniversary } + + it 'returns the price of single day' do + expect(result).to eq(plan.amount_cents.fdiv(28)) + end + + context 'when on a leap year' do + let(:subscription_date) { DateTime.parse('02 Feb 2019') } + let(:billing_date) { DateTime.parse('08 Mar 2020') } + + it 'returns the price of single day' do + expect(result).to eq(plan.amount_cents.fdiv(29)) + end + end + end + end end diff --git a/spec/services/subscriptions/dates/weekly_service_spec.rb b/spec/services/subscriptions/dates/weekly_service_spec.rb index a4fdef0da38..fc402baab8b 100644 --- a/spec/services/subscriptions/dates/weekly_service_spec.rb +++ b/spec/services/subscriptions/dates/weekly_service_spec.rb @@ -3,7 +3,7 @@ require 'rails_helper' RSpec.describe Subscriptions::Dates::WeeklyService, type: :service do - subject(:date_service) { described_class.new(subscription, billing_date) } + subject(:date_service) { described_class.new(subscription, billing_date, false) } let(:subscription) do create( @@ -54,8 +54,8 @@ context 'when plan is pay in advance' do let(:pay_in_advance) { true } - it 'returns the beginning of the previous week' do - expect(result).to eq('2022-02-28') + it 'returns the beginning of the current week' do + expect(result).to eq('2022-03-07') expect(Time.zone.parse(result).wday).to eq(1) end end @@ -91,8 +91,8 @@ context 'when plan is pay in advance' do let(:pay_in_advance) { true } - it 'returns the previous week week day' do - expect(result).to eq('2022-03-01') + it 'returns the current week week day' do + expect(result).to eq('2022-03-08') expect(Time.zone.parse(result).wday).to eq(subscription_date.wday) end end @@ -111,13 +111,12 @@ expect(result).to eq('2022-03-06') end - context 'when plan is pay in advance and billed for the first time' do + context 'when plan is pay in advance' do before { plan.update!(pay_in_advance: true) } - let(:started_at) { DateTime.parse('15 Jun 2022') } - - it 'returns the start date' do - expect(result).to eq(started_at.to_date.to_s) + it 'returns the end of the week' do + expect(result).to eq('2022-03-13') + expect(Time.zone.parse(result).wday).to eq(0) end end @@ -146,13 +145,11 @@ expect(result).to eq('2022-03-07') end - context 'when plan is pay in advance and billed for the first time' do + context 'when plan is pay in advance' do before { plan.update!(pay_in_advance: true) } - let(:started_at) { DateTime.parse('08 Mar 2022') } - - it 'returns the start date' do - expect(result).to eq(started_at.to_date.to_s) + it 'returns the end of the current period' do + expect(result).to eq('2022-03-14') end end @@ -188,6 +185,14 @@ expect(result).to eq(subscription.started_at.to_date.to_s) end end + + context 'when plan is pay in advance' do + let(:pay_in_advance) { true } + + it 'returns the start of the previous period' do + expect(result).to eq((date_service.from_date - 1.week).to_s) + end + end end context 'when billing_time is anniversary' do @@ -205,6 +210,75 @@ expect(result).to eq(subscription.started_at.to_date.to_s) end end + + context 'when plan is pay in advance' do + let(:pay_in_advance) { true } + + it 'returns the start of the previous period' do + expect(result).to eq((date_service.from_date - 1.week).to_s) + end + end + end + end + + describe 'charges_to_date' do + let(:result) { date_service.charges_to_date.to_s } + + context 'when billing_time is calendar' do + let(:billing_time) { :calendar } + + it 'returns to_date' do + expect(result).to eq(date_service.to_date.to_s) + end + + context 'when subscription is terminated in the middle of a period' do + let(:terminated_at) { DateTime.parse('06 Mar 2022') } + + before do + subscription.update!(status: :terminated, terminated_at: terminated_at) + end + + it 'returns the terminated date' do + expect(result).to eq(subscription.terminated_at.to_date.to_s) + end + end + + context 'when plan is pay in advance' do + let(:pay_in_advance) { true } + + it 'returns the end of the previous period' do + expect(result).to eq((date_service.from_date - 1.day).to_s) + end + end + end + + context 'when billing_time is anniversary' do + let(:billing_time) { :anniversary } + let(:billing_date) { DateTime.parse('10 Mar 2022') } + + it 'returns to_date' do + expect(result).to eq(date_service.to_date.to_s) + end + + context 'when subscription is terminated in the middle of a period' do + let(:terminated_at) { DateTime.parse('06 Mar 2022') } + + before do + subscription.update!(status: :terminated, terminated_at: terminated_at) + end + + it 'returns the terminated date' do + expect(result).to eq(subscription.terminated_at.to_date.to_s) + end + end + + context 'when plan is pay in advance' do + let(:pay_in_advance) { true } + + it 'returns the end of the previous period' do + expect(result).to eq((date_service.from_date - 1.day).to_s) + end + end end end @@ -236,4 +310,52 @@ end end end + + describe 'compute_previous_beginning_of_period' do + let(:result) { date_service.previous_beginning_of_period(current_period: current_period).to_s } + + let(:current_period) { false } + + context 'when billing_time is calendar' do + let(:billing_time) { :calendar } + + it 'returns the first day of the previous week' do + expect(result).to eq('2022-02-28') + end + + context 'with current period argument' do + let(:current_period) { true } + + it 'returns the first day of the week' do + expect(result).to eq('2022-03-07') + end + end + end + + context 'when billing_time is anniversary' do + let(:billing_time) { :anniversary } + + it 'returns the beginning of the previous period' do + expect(result).to eq('2022-02-22') + end + + context 'with current period argument' do + let(:current_period) { true } + + it 'returns the beginning of the current period' do + expect(result).to eq('2022-03-01') + end + end + end + end + + describe 'single_day_price' do + let(:billing_time) { :anniversary } + let(:billing_date) { DateTime.parse('08 Mar 2022') } + let(:result) { date_service.single_day_price } + + it 'returns the price of single day' do + expect(result).to eq(plan.amount_cents.fdiv(7)) + end + end end diff --git a/spec/services/subscriptions/dates/yearly_service_spec.rb b/spec/services/subscriptions/dates/yearly_service_spec.rb index 6d3ba71260f..e4ed8f87007 100644 --- a/spec/services/subscriptions/dates/yearly_service_spec.rb +++ b/spec/services/subscriptions/dates/yearly_service_spec.rb @@ -3,7 +3,7 @@ require 'rails_helper' RSpec.describe Subscriptions::Dates::YearlyService, type: :service do - subject(:date_service) { described_class.new(subscription, billing_date) } + subject(:date_service) { described_class.new(subscription, billing_date, false) } let(:subscription) do create( @@ -54,8 +54,8 @@ context 'when plan is pay in advance' do let(:pay_in_advance) { true } - it 'returns the beginning of the previous year' do - expect(result).to eq('2021-01-01') + it 'returns the beginning of the current year' do + expect(result).to eq('2022-01-01') end end end @@ -87,8 +87,8 @@ context 'when plan is pay in advance' do let(:pay_in_advance) { true } - it 'returns the previous year day and month' do - expect(result).to eq('2021-02-02') + it 'returns the current year day and month' do + expect(result).to eq('2022-02-02') end end @@ -124,13 +124,11 @@ expect(result).to eq('2021-12-31') end - context 'when plan is pay in advance and billed for the first time' do + context 'when plan is pay in advance' do before { plan.update!(pay_in_advance: true) } - let(:started_at) { DateTime.parse('07 Feb 2021') } - - it 'returns the start date' do - expect(result).to eq(started_at.to_date.to_s) + it 'returns the end of the currrent year' do + expect(result).to eq('2022-12-31') end end @@ -176,13 +174,11 @@ end end - context 'when plan is pay in advance and billed for the first time' do + context 'when plan is pay in advance' do before { plan.update!(pay_in_advance: true) } - let(:started_at) { DateTime.parse('02 Sep 2022') } - - it 'returns the start date' do - expect(result).to eq(started_at.to_date.to_s) + it 'returns the end of the current period' do + expect(result).to eq('2023-02-01') end end @@ -221,6 +217,15 @@ end end + context 'when plan is pay in advance' do + let(:pay_in_advance) { true } + let(:subscription_date) { DateTime.parse('02 Feb 2020') } + + it 'returns the start of the previous period' do + expect(result).to eq('2021-01-01') + end + end + context 'when billing charge monthly' do before { plan.update!(bill_charges_monthly: true) } @@ -254,6 +259,15 @@ end end + context 'when plan is pay in advance' do + let(:pay_in_advance) { true } + let(:subscription_date) { DateTime.parse('02 Feb 2020') } + + it 'returns the start of the previous period' do + expect(result).to eq('2021-02-02') + end + end + context 'when billing charge monthly' do before { plan.update!(bill_charges_monthly: true) } @@ -272,6 +286,96 @@ end end + describe 'charges_to_date' do + let(:result) { date_service.charges_to_date.to_s } + + context 'when billing_time is calendar' do + let(:billing_time) { :calendar } + + it 'returns to_date' do + expect(result).to eq(date_service.to_date.to_s) + end + + context 'when subscription is terminated in the middle of a period' do + let(:terminated_at) { DateTime.parse('06 Mar 2022') } + + before do + subscription.update!(status: :terminated, terminated_at: terminated_at) + end + + it 'returns the terminated date' do + expect(result).to eq(subscription.terminated_at.to_date.to_s) + end + end + + context 'when plan is pay in advance' do + let(:pay_in_advance) { true } + + it 'returns the end of the previous period' do + expect(result).to eq((date_service.from_date - 1.day).to_s) + end + end + + context 'when billing charge monthly' do + before { plan.update!(bill_charges_monthly: true) } + + it 'returns to_date' do + expect(result).to eq(date_service.to_date.to_s) + end + + context 'when subscription terminated in the middle of a period' do + let(:terminated_at) { DateTime.parse('10 Mar 2022') } + + before do + subscription.update!(status: :terminated, terminated_at: terminated_at) + end + + it 'returns the terminated_at date' do + expect(result).to eq(subscription.terminated_at.to_date.to_s) + end + end + + context 'when plan is pay in advance' do + let(:pay_in_advance) { true } + let(:subscription_date) { DateTime.parse('02 Feb 2020') } + + it 'returns the end of the current period' do + expect(result).to eq('2022-02-28') + end + end + end + end + + context 'when billing_time is anniversary' do + let(:billing_time) { :anniversary } + let(:billing_date) { DateTime.parse('10 Mar 2022') } + + it 'returns to_date' do + expect(result).to eq(date_service.to_date.to_s) + end + + context 'when subscription is terminated in the middle of a period' do + let(:terminated_at) { DateTime.parse('06 Mar 2022') } + + before do + subscription.update!(status: :terminated, terminated_at: terminated_at) + end + + it 'returns the terminated date' do + expect(result).to eq(subscription.terminated_at.to_date.to_s) + end + end + + context 'when plan is pay in advance' do + let(:pay_in_advance) { true } + + it 'returns the end of the previous period' do + expect(result).to eq((date_service.from_date - 1.day).to_s) + end + end + end + end + describe 'next_end_of_period' do let(:result) { date_service.next_end_of_period(billing_date.to_date).to_s } @@ -299,4 +403,80 @@ end end end + + describe 'compute_previous_beginning_of_period' do + let(:result) { date_service.previous_beginning_of_period(current_period: current_period).to_s } + + let(:current_period) { false } + + context 'when billing_time is calendar' do + let(:billing_time) { :calendar } + + it 'returns the first day of the previous year' do + expect(result).to eq('2021-01-01') + end + + context 'with current period argument' do + let(:current_period) { true } + + it 'returns the first day of the year' do + expect(result).to eq('2022-01-01') + end + end + end + + context 'when billing_time is anniversary' do + let(:billing_time) { :anniversary } + + it 'returns the beginning of the previous period' do + expect(result).to eq('2021-02-02') + end + + context 'with current period argument' do + let(:current_period) { true } + + it 'returns the beginning of the current period' do + expect(result).to eq('2022-02-02') + end + end + end + end + + describe 'single_day_price' do + let(:result) { date_service.single_day_price } + + context 'when billing_time is calendar' do + let(:billing_time) { :calendar } + + it 'returns the price of single day' do + expect(result).to eq(plan.amount_cents.fdiv(365)) + end + + context 'when on a leap year' do + let(:subscription_date) { DateTime.parse('28 Feb 2019') } + let(:billing_date) { DateTime.parse('01 Jan 2021') } + + it 'returns the price of single day' do + expect(result).to eq(plan.amount_cents.fdiv(366)) + end + end + end + + context 'when billing_time is anniversary' do + let(:billing_time) { :anniversary } + + it 'returns the price of single day' do + expect(result).to eq(plan.amount_cents.fdiv(365)) + end + + context 'when on a leap year' do + let(:subscription_date) { DateTime.parse('02 Feb 2019') } + let(:billing_date) { DateTime.parse('08 Mar 2021') } + + it 'returns the price of single day' do + expect(result).to eq(plan.amount_cents.fdiv(366)) + end + end + end + end end diff --git a/spec/services/subscriptions/dates_service_spec.rb b/spec/services/subscriptions/dates_service_spec.rb index 37a0ef3fcb5..0d916171597 100644 --- a/spec/services/subscriptions/dates_service_spec.rb +++ b/spec/services/subscriptions/dates_service_spec.rb @@ -3,7 +3,7 @@ require 'rails_helper' RSpec.describe Subscriptions::DatesService, type: :service do - subject(:date_service) { described_class.new(subscription, billing_date) } + subject(:date_service) { described_class.new(subscription, billing_date, false) } let(:subscription) do create( @@ -86,10 +86,24 @@ end end + describe 'charges_to_date' do + it 'raises a not implemented error' do + expect { date_service.charges_to_date } + .to raise_error(NotImplementedError) + end + end + describe 'next_end_of_period' do it 'raises a not implemented error' do expect { date_service.next_end_of_period(billing_date.to_date) } .to raise_error(NotImplementedError) end end + + describe 'previous_beginning_of_period' do + it 'raises a not implemented error' do + expect { date_service.previous_beginning_of_period } + .to raise_error(NotImplementedError) + end + end end