Dec 18, 2022

Calculating Rolling Billing Periods

Setting up billing for your SaaS product inevitably forces you to think about billing periods: While you may opt for a monthly subscription plan, certain customers may prefer different schedules like annual or semi-annual payments, especially when you’re closing enterprise deals.

Chances are you’re using an external provider like Stripe, Paddle, or Recurly for collecting payments already. To support recurring subscriptions, these services expose levers to manage the billing cycle of a customer subscription. Sometimes, though, you may want to calculate the current billing period of a customer in your app, without having to send a request to your payments provider.

Given a starting date (of the initial billing period), we want to retrieve the start and end dates of the current billing period, for an arbitrary billing cycle specified in months (e.g. 1, 6, 12, etc.). While there are multiple approaches to solving the problem, we’ll calculate the number of elapsed periods and add the rounded result to the initial billing period to get our current period starting date. From there, we can add a full period to get the end date.

By rounding down, we discard progress in a started period to consider completed periods only.

import { differenceInMonths, differenceInDays, addMonths } from 'date-fns';

export const calculateBillingPeriod = (
  from: Date,
  to: Date,
  billingPeriodInMonths: number
) => {
  const diffInMonths = differenceInMonths(to, from);

  // round down to discard incomplete periods
  const elapsedPeriods = Math.floor(diffInMonths / billingPeriodInMonths);

  // get starting date of current period
  const dateFrom = addMonths(from, elapsedPeriods * billingPeriodInMonths);

  // get end date of current period by adding full cycle to starting date
  const dateTo = addMonths(dateFrom, billingPeriodInMonths);

  return { from: dateFrom.toISOString(), to: dateTo.toISOString() };

This relatively simple function can save you lots of time when you need to display the current billing period, for example in your app’s billing settings.