Skip to content

Instantly share code, notes, and snippets.

@stevehanson
Created January 23, 2017 16:35
Show Gist options
  • Save stevehanson/2c94ce889f37dc95e3601e303eb118c8 to your computer and use it in GitHub Desktop.
Save stevehanson/2c94ce889f37dc95e3601e303eb118c8 to your computer and use it in GitHub Desktop.
Stripe Billing Period Logic
class BillingPeriod
attr_reader :start, :end
def initialize(start_dt, end_dt)
@start = start
@end = end_dt
end
# the bill day can always be determined from any given period
# eg: if bill day is 1-28, the start and end will both always be on 28
# eg. if bill day is 31, either start or end will be 31 (we take the max)
# how stripe handles 31 bill day: jan 31, feb 28, mar 31, apr 30, ....
def bill_day
[start.day, self.end.day].max
end
def next
self.class.new(self.end, add_month_preserving_bill_day(self.end))
end
def plus_months(n)
period = self
n.times do
period = period.next
end
period
end
# would be more efficient to do some date arithmetic instead of looping, but usually only ever
# going one or two months anyway, so who cares
def period_at(time)
raise StandardError.new("Error: tried to look up billing period for date in the past") if time < self.start
per = self
loop do
if per.end > time
return per
end
per = per.next
end
end
# returns billing period starting at time, ending one month later. Mostly just a utility used in tests
def self.starting_at(time)
new(time, time + 1.month)
end
# build a billing period ending at the specified time (mostly used in tests)
# eg. if Dec 31, would return Nov 30 - Dec 31. if Feb 28, would return Jan 28 - Feb 28
def self.ending_at(time)
start = time - 1.month
start = start.change(day: [start.end_of_month.day, time.day].min)
new(start, time)
end
private
def add_month_preserving_bill_day(time)
new_time = time + 1.month
new_time.change(day: [new_time.end_of_month.day, bill_day].min)
end
end
class User < ApplicationRecord
def period
if subsc = stripe_subscription
if subsc.current_period_start && subsc.current_period_end
BillingPeriod.new(Time.at(subsc.current_period_start), Time.at(subsc.current_period_end))
end
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment