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) };
}