Add the subscription renewal-date helper
Node · Node · advanced · greenfield
Adds the `nextRenewal` helper the billing scheduler uses to compute each subscription's following renewal date from its last one. Clamps the anchor day to the month length for short months and rolls forward to the next month. Spot-checked against a few anchor days and it returned the right dates.
This helper drives the recurring-billing scheduler: it is called with a subscription's last renewal date (`fromISO`) to compute the next one, so it must strictly advance on every call. `new Date(year, month, 0)` is the standard trick for the last day of a month (day 0 backs up to the prior month's end; here `month` is the 1-based month, so it resolves to that month's own last day).
Requirements
- nextRenewal(anchorDay, fromISO) returns the next renewal date strictly after fromISO, as a date-only "YYYY-MM-DD" string. anchorDay is an integer 1..31; the subscription renews on anchorDay of each calendar month.
- If a month has fewer days than anchorDay, that month's renewal falls on the month's LAST day — but the series must NOT drift: it returns to the original anchorDay in every month that has it (anchorDay=31: 2026-01-31 -> 2026-02-28 -> 2026-03-31, never staying on the 28th).
- The result must always be STRICTLY AFTER fromISO. The scheduler feeds each renewal date back in to get the following one, so repeated calls must advance the date every time and never return the same day.
Files touched
- src/billing/renewal.js
--- src/billing/renewal.js
+module.exports = { nextRenewal };
+
+function nextRenewal(anchorDay, fromISO) {
+ const [fromYear, fromMonth, fromDay] = fromISO.split('-').map(Number);
+ let nextMonth = fromMonth;
+ let nextYear = fromYear;
+
+ if (fromDay >= anchorDay) {
+ nextMonth++;
+ if (nextMonth > 12) {
+ nextMonth = 1;
+ nextYear++;
+ }
+ }
+
+ const lastDayOfNextMonth = new Date(nextYear, nextMonth, 0).getDate();
+ const renewalDay = Math.min(anchorDay, lastDayOfNextMonth);
+
+ return `${nextYear.toString().padStart(4, '0')}-${nextMonth.toString().padStart(2, '0')}-${renewalDay.toString().padStart(2, '0')}`;
+}
+