Functional Weave
Code in TypeScript

subscriptions.next-billing-date@1.0.0

README.md

1,549 bytes · view raw

# subscriptions.next-billing-date

Every billing date is the anchor plus k whole intervals, counted from the
anchor each time, and the answer is the first of them strictly after
`afterDate`. That one rule is what keeps month-end subscriptions on month
ends. A plan anchored on 31 January bills on 28 February, 31 March, 30 April,
31 May: each date is dates.add-months from the anchor, clamped to the length
of its own month. The common bug is to add one month to the previous billing
date instead, which turns 28 February into 28 March and keeps the customer on
the 28th for ever. Stripe's billing cycle anchor behaves the same way: a
subscription anchored on the 31st is billed on the last day of shorter months.

"Strictly after" is deliberate. On a billing date itself the renewal is today's
invoice, and the next one is a whole interval away. When `afterDate` is before
the anchor (a subscription with a future start, or one still in its trial) the
next billing date is the anchor.

Intervals: `day` and `week` step in exact days (a week is 7), `month`,
`quarter` (3 months) and `year` (12 months) step in calendar months with the
month-end clamp, so a yearly plan anchored on 29 February renews on 28 February
in ordinary years and on 29 February again in leap years. `intervalCount`
multiplies the interval, as Stripe's `interval_count` does: `month` with 6 is
half-yearly, `day` with 14 is fortnightly.

An interval count below 1, an unknown interval, and an impossible date are
errors. Dates past 9999-12-31 are outside the supported range.