From c64457020f524a1561fc112845ea5d5cd222d5cb Mon Sep 17 00:00:00 2001 From: Julian Cuni Date: Sat, 20 Jun 2026 19:55:29 +0200 Subject: [PATCH] fix(subs): resolve the plan version at the SALE instant, not validFrom MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Selling/quoting a subscription resolved the plan version against `validFrom`, but validFrom is a DATE (midnight UTC for "starts today"). A plan published later the same day (effectiveFrom 15:22) then failed `effectiveFrom ≤ validFrom` (00:00), so resolvePlanVersion returned null → "no active plan for that planId", and the form's selectedPlan went null (hiding the new count field too). The plan/price in force is determined by WHEN THE SALE HAPPENS, not by the coverage start — like a tariff, the customer buys today's published rate. Resolve at new Date() in all three sites (validate, priceSale, /quote); validFrom is kept only for span pricing. Verified the two live plans now resolve. Claude-Session: https://claude.ai/code/session_01Xcm6ikLgGoCxxHrxtjkk5V --- apps/server/src/routes/subscriptions.ts | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/apps/server/src/routes/subscriptions.ts b/apps/server/src/routes/subscriptions.ts index f588782..fd3e814 100644 --- a/apps/server/src/routes/subscriptions.ts +++ b/apps/server/src/routes/subscriptions.ts @@ -112,7 +112,10 @@ export async function subscriptionRoutes( } else if (Date.parse(to) <= Date.parse(from)) { errs.push("validTo must be after validFrom"); } else { - const plan = resolvePlanVersion(db, b.planId.trim(), from); + // Resolve the plan version at the SALE instant (now) — the customer buys today's + // published plan/price. (validFrom is the coverage start, which may be midnight + // today and predate a plan published this afternoon.) + const plan = resolvePlanVersion(db, b.planId.trim(), new Date().toISOString()); if (!plan) errs.push("no active plan found for the selected planId"); } } @@ -205,7 +208,9 @@ export async function subscriptionRoutes( if (!b.planId?.trim() || !b.validTo?.trim()) return null; const validFrom = b.validFrom?.trim() || new Date().toISOString(); const validTo = b.validTo.trim(); - const plan = resolvePlanVersion(db, b.planId.trim(), validFrom); + // Plan version is resolved at the SALE instant (now), not validFrom (which is the + // coverage start and may predate a plan published later today). + const plan = resolvePlanVersion(db, b.planId.trim(), new Date().toISOString()); if (!plan) return null; const quantity = b.quantity != null && b.quantity > 0 ? Math.round(b.quantity) : 1; const base = priceSubscriptionSpan(plan, validFrom, validTo); @@ -276,7 +281,8 @@ export async function subscriptionRoutes( if (Date.parse(validTo) <= Date.parse(validFrom)) { return reply.code(400).send({ error: "validTo must be after validFrom" }); } - const plan = resolvePlanVersion(db, b.planId.trim(), validFrom); + // Resolve at the sale instant (now), not validFrom — see priceSale. + const plan = resolvePlanVersion(db, b.planId.trim(), new Date().toISOString()); if (!plan) return reply.code(404).send({ error: "no active plan for that planId" }); const quantity = b.quantity != null && b.quantity > 0 ? Math.round(b.quantity) : 1; const base = priceSubscriptionSpan(plan, validFrom, validTo);