Created
January 23, 2017 16:35
-
-
Save stevehanson/2c94ce889f37dc95e3601e303eb118c8 to your computer and use it in GitHub Desktop.
Stripe Billing Period Logic
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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