class BillingCycle::BillingCycle
Utility class for calculating the next billing date for a subscription.
Attributes
Public Class Methods
Initialize a billing cycle. @param created_at
[Time] The date/time the subscription was created @param interval [ActiveSupport::Duration] The interval the subscription is paid
# File lib/billing_cycle/billing_cycle.rb, line 13 def initialize(created_at, interval) @created_at = created_at @interval = interval end
Public Instance Methods
Returns the next billing date based on the subscription @param now [Time] The current time @return [Time] The next billing date/time
# File lib/billing_cycle/billing_cycle.rb, line 21 def next_due_at(now = Time.zone.now) # The next billing date is always the original date if "now" is earlier than the original date return created_at if now <= created_at number_of_cycles = number_of_cycles_since_created(now) next_due_at = calculate_due_at(number_of_cycles) # The number of cycles since the billing cycle was created only gets us to "now" # so we need probably need to add another cycle to get the next billing date. while next_due_at < now number_of_cycles += interval_value next_due_at = calculate_due_at(number_of_cycles) end next_due_at end
Returns the percentage of time that's elapsed between the previous due date and the next due date. @param now [Time] The current time @return [Float] The percentage as a fraction of 1.0
# File lib/billing_cycle/billing_cycle.rb, line 41 def percent_elapsed(now = Time.zone.now) time_elapsed(1.second, now) / seconds_in_cycle(now) end
Returns the percentage of time that's remaining between the previous due date and the next due date. @param now [Time] The current time @return [Float] The percentage as a fraction of 1.0
# File lib/billing_cycle/billing_cycle.rb, line 48 def percent_remaining(now = Time.zone.now) time_remaining(1.second, now) / seconds_in_cycle(now) end
Returns the previous billing date based on the subscription @param now [Time] The current time @return [Time] The previous billing date/time
# File lib/billing_cycle/billing_cycle.rb, line 55 def previous_due_at(now = Time.zone.now) # There was no previous billing date before the original billing date return nil if now <= created_at number_of_cycles = number_of_cycles_since_created(now) + interval_value previous_due_at = calculate_due_at(number_of_cycles) # The number of cycles since the billing cycle was created gets us to "now", # and if now matches a billing date exactly, we want the previous billing date. while previous_due_at >= now number_of_cycles -= interval_value previous_due_at = calculate_due_at(number_of_cycles) end previous_due_at end
Returns the time elapsed since the previous due date. @param interval [ActiveSupport::Duration] The duration @param now [Time] The current time @return [Float] The number of intervals since the previous due date
# File lib/billing_cycle/billing_cycle.rb, line 76 def time_elapsed(interval = 1.second, now = Time.zone.now) (now - previous_due_at(now)) / interval end
Returns the time remaining until the next due date. @param now [Time] The current time @return [Float] The number of intervals until the next due date
# File lib/billing_cycle/billing_cycle.rb, line 83 def time_remaining(interval = 1.second, now = Time.zone.now) (next_due_at(now) - now) / interval end
Private Instance Methods
Calculate the due date based on number of billing cycles since or before the date/time the subscription was created. @param number_of_cycles [Integer] @return [Time]
# File lib/billing_cycle/billing_cycle.rb, line 93 def calculate_due_at(number_of_cycles) number_of_cycles.send(interval_units).from_now(created_at) end
Returns the interval type from the interval's duration. @return [Symbol] `:month` for a duration of `1.month`
# File lib/billing_cycle/billing_cycle.rb, line 109 def interval_units if interval.parts.is_a? Array interval.parts[0][0] else interval.parts.keys[0] end end
Returns the number from the interval's duration. @return [Integer] `6` for a duration of `6.months`
# File lib/billing_cycle/billing_cycle.rb, line 99 def interval_value if interval.parts.is_a? Array interval.parts[0][1] else interval.parts.values[0] end end
Returns the number billing cycles that have occurred between the created date and “now”. @param now [Time] The current time @return [Integer]
# File lib/billing_cycle/billing_cycle.rb, line 120 def number_of_cycles_since_created(now) (interval_value * ((now - created_at).to_i / interval)).to_i end
Returns the total number of seconds in the current billing cycle. @param now [Time] The current time @return [Float]
# File lib/billing_cycle/billing_cycle.rb, line 127 def seconds_in_cycle(now) next_due_at(now) - previous_due_at(now) end