@zenuinebit/subscription-billing
v0.1.0
Published
Subscription billing periods, trials, proration for upgrades and cancellations, usage tiers, dunning dates and MRR.
Readme
@zenuinebit/subscription-billing
Subscription billing arithmetic: billing periods with trials, anniversary or calendar billing, proration for upgrades and cancellations, metered-usage tiers, retry dates after a failed payment, and MRR. Amounts are integers in minor units (see @zenuinebit/money); dates are "YYYY-MM-DD".
No plans or prices are built in, and no tax is added: apply tax to the invoice with
@zenuinebit/tax. You store plans and subscriptions; this works out the figures.
import { billingPeriods, prorateChange, refundUnused, usageCharge, dunningSchedule, mrr } from "@zenuinebit/subscription-billing";
const plan = { id: "pro", amount: 100_000, interval: "month", trialDays: 14, setupFee: 50_000 }; // per seat, per month
const sub = { id: "s1", planId: "pro", startDate: "2025-03-01", quantity: 5 };
billingPeriods(sub, plan, { through: "2025-06-01" });
// [{ type: "trial", start: "2025-03-01", end: "2025-03-14", amount: 0 },
// { type: "paid", start: "2025-03-15", end: "2025-04-14", amount: 500_000, setupFee: 50_000 }, ...]
// Mid-period upgrade or seat change: credit the unused old price, charge the rest at the new one
prorateChange({ periodStart: "2025-04-01", periodEnd: "2025-04-30", changeDate: "2025-04-16", oldAmount: 100_000, newAmount: 250_000 });
// { remainingDays: 15, credit: 50_000, charge: 125_000, net: 75_000 }
refundUnused({ periodStart: "2025-04-01", periodEnd: "2025-04-30", amount: 90_000, lastServiceDate: "2025-04-20" }); // { unusedDays: 10, refund: 30_000 }
// Metered usage: graduated (each slice at its tier) or volume (all units at the tier reached)
usageCharge(6_000, [{ upTo: 1_000, unitPrice: 2 }, { upTo: 5_000, unitPrice: 1.5 }, { unitPrice: 1 }]); // { amount: 9_000, slices: [...] }
dunningSchedule("2025-04-10", [1, 3, 7]); // retry dates: 11, 13 and 17 April
mrr(subscriptions, plans, "2025-06-15"); // { mrr, arr, count } - yearly, quarterly and weekly plans normalised to a monthHow it works
- Anniversary billing (default): periods are billed in advance and count from the first paid day, so a 31 January start renews on 28 February and then 31 March again, with no drift. Weeks, months, quarters and years work, with
intervalCountfor "every 2 months". - Calendar billing (
anchor: "calendar"): periods line up with calendar months, quarters or years, and the first is prorated by days (21 of 30 days, 46 of 91...). It is for one month, quarter or year at a time, not weeks. - Trials come first and cost nothing; the first paid period starts the day after. The setup fee goes on the first paid period.
- Cancellation:
endDateis the last day of service. By default the last period stays billed in full (it was billed in advance); passprorateFinal: trueto charge only the days used, or userefundUnusedto refund them. mrrleaves out subscriptions that have not started, have ended, or are still in a free trial (includeTrials: truecounts them).
Not included: invoices and payment collection, coupons and discounts, mid-period quantity schedules, pausing, tax, and revenue recognition.
