Fix credit-counter reset on early subscription renewal

extendUserTier called setUserTier, which unconditionally zeroed
monthly_consumed and re-anchored the cycle. A user who renewed mid-cycle
(or a webhook double-firing across a restart) got their full monthly
allotment back for free. The monthly cycle already rolls on its own
anniversary via ensureRenewalRollover, so renewal must not reset it. Add
resetCycle to setUserTier (default true, preserving operator-grant
behavior); extendUserTier passes false for an in-force subscription and
true only for a brand-new or lapsed one. Add regression tests.
This commit is contained in:
Keysat
2026-06-13 16:23:26 -05:00
parent 8ad7c54da4
commit d2caa98248
2 changed files with 49 additions and 11 deletions
+22
View File
@@ -76,4 +76,26 @@ describe("extendUserTier (prepaid periods)", () => {
const days = (new Date(renewed.subscription_expires_at).getTime() - Date.now()) / DAY;
assert.ok(days > 29.9 && days < 30.1, `fresh ~30 days from now, got ${days}`);
});
test("early renewal PRESERVES the monthly credit counter (no free reset)", async () => {
await extendUserTier({ userId: "u4", tier: "pro", periodDays: 30 });
const row = await getUserCreditRow("u4");
row.monthly_consumed = 7; // simulate credits already spent this cycle
row.monthly_gemini_consumed = 3;
// Pay early / renew while the subscription is still in force.
await extendUserTier({ userId: "u4", tier: "pro", periodDays: 30 });
const after = await getUserCreditRow("u4");
assert.equal(after.monthly_consumed, 7, "consumed credits must survive an early renewal");
assert.equal(after.monthly_gemini_consumed, 3);
});
test("resubscribing AFTER a lapse starts a fresh cycle (counter reset)", async () => {
await extendUserTier({ userId: "u5", tier: "pro", periodDays: 30 });
const row = await getUserCreditRow("u5");
row.monthly_consumed = 9;
row.subscription_expires_at = new Date(Date.now() - 5 * DAY).toISOString(); // lapsed
await extendUserTier({ userId: "u5", tier: "pro", periodDays: 30 });
const after = await getUserCreditRow("u5");
assert.equal(after.monthly_consumed, 0, "a lapsed resubscribe starts a clean cycle");
});
});