Functional Weave
Code in Rust

subscriptions.proration@1.0.0

impl/typescript.ts

1,610 bytes · the TypeScript implementation · view raw

Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.

import { type RoundingMode, roundDiv } from "./math_round_div.ts";  ← from math.round-div ^1.0.0 · built alongside by fune
import { addMoney } from "./money_add.ts";  ← from money.add ^1.0.0 · built alongside by fune
import { type Money, assertSameCurrency, money } from "./money_amount.ts";  ← from money.amount ^1.0.0 · built alongside by fune
import { daysBetween } from "./dates_days_between.ts";  ← from dates.days-between ^1.0.0 · built alongside by fune
import { type PlanChangeProration } from "./subscriptions_proration_types.ts";

/**
 * Stripe-style proration for a mid-cycle plan change, by whole days: a credit
 * for the unused remainder of the old plan and a charge for the same remainder
 * on the new plan, each rounded once from the exact fraction.
 */
export function prorateChange(
  oldPrice: Money,
  newPrice: Money,
  periodStart: string,
  periodEnd: string,
  changeDate: string,
  mode: RoundingMode,
): PlanChangeProration {
  const totalDays = daysBetween(periodStart, periodEnd);
  if (totalDays <= 0) {
    throw new RangeError(`period end must be after period start, received ${periodStart} to ${periodEnd}`);
  }
  const elapsed = daysBetween(periodStart, changeDate);
  if (elapsed < 0 || elapsed > totalDays) {
    throw new RangeError(`change date must be within the billing period, received ${changeDate} for ${periodStart} to ${periodEnd}`);
  }
  assertSameCurrency(oldPrice, newPrice);
  const remainingDays = totalDays - elapsed;

  // 0 - x rather than -x, so a zero credit is 0 and never -0.
  const credit = money(0 - roundDiv(oldPrice.minor * remainingDays, totalDays, mode), oldPrice.currency);
  const charge = money(roundDiv(newPrice.minor * remainingDays, totalDays, mode), newPrice.currency);
  return { totalDays, remainingDays, credit, charge, net: addMoney(charge, credit) };
}