From e79239a100422c181fd92cbfa4f0fae3a610cd57 Mon Sep 17 00:00:00 2001 From: Mauricio Siu Date: Sun, 30 Aug 2026 18:08:35 -0600 Subject: [PATCH 1/2] fix: detect billing plan across all active Stripe subscriptions getCurrentPlanForUser and getProducts only inspected subscriptions.data[0], so a customer with more than one active Stripe subscription (e.g. Startup plus a separately purchased additional server) could have their plan resolved from the wrong subscription, resulting in currentPlan !== "startup" and the HubSpot chat bubble not rendering. --- apps/dokploy/server/api/routers/stripe.ts | 62 ++++++++++++++--------- apps/dokploy/server/utils/billing.ts | 7 ++- 2 files changed, 41 insertions(+), 28 deletions(-) diff --git a/apps/dokploy/server/api/routers/stripe.ts b/apps/dokploy/server/api/routers/stripe.ts index e1c7ee7bd..32e69b253 100644 --- a/apps/dokploy/server/api/routers/stripe.ts +++ b/apps/dokploy/server/api/routers/stripe.ts @@ -80,38 +80,52 @@ export const stripeRouter = createTRPCRouter({ let currentPlan: CurrentPlan = "legacy"; let isAnnualCurrent = false; let currentPriceAmount: number | null = null; - const activeSub = subscriptions.data[0]; - if (activeSub) { - const priceIds = activeSub.items.data.map( - (item) => (item.price as Stripe.Price).id, + if (subscriptions.data.length > 0) { + const matchedSub = subscriptions.data.find((sub) => + sub.items.data.some( + (item) => + (item.price as Stripe.Price).id === STARTUP_BASE_PRICE_MONTHLY_ID || + (item.price as Stripe.Price).id === STARTUP_BASE_PRICE_ANNUAL_ID, + ), ); - if ( - priceIds.some( - (id) => - id === STARTUP_BASE_PRICE_MONTHLY_ID || - id === STARTUP_BASE_PRICE_ANNUAL_ID, - ) - ) { + const hobbySub = subscriptions.data.find((sub) => + sub.items.data.some( + (item) => + (item.price as Stripe.Price).id === HOBBY_PRICE_MONTHLY_ID || + (item.price as Stripe.Price).id === HOBBY_PRICE_ANNUAL_ID, + ), + ); + const legacySub = subscriptions.data.find((sub) => + sub.items.data.some((item) => + LEGACY_PRICE_IDS.includes((item.price as Stripe.Price).id), + ), + ); + + const activeSub = matchedSub ?? hobbySub ?? legacySub; + if (matchedSub) { currentPlan = "startup"; - } else if ( - priceIds.some( - (id) => id === HOBBY_PRICE_MONTHLY_ID || id === HOBBY_PRICE_ANNUAL_ID, - ) - ) { + } else if (hobbySub) { currentPlan = "hobby"; - } else if (priceIds.some((id) => LEGACY_PRICE_IDS.includes(id))) { + } else if (legacySub) { currentPlan = "legacy"; } - const firstPrice = activeSub.items.data[0]?.price as + + const firstPrice = activeSub?.items.data[0]?.price as | Stripe.Price | undefined; isAnnualCurrent = firstPrice?.recurring?.interval === "year"; - const totalCents = activeSub.items.data.reduce((sum, item) => { - const price = item.price as Stripe.Price; - const amount = price.unit_amount ?? 0; - const qty = item.quantity ?? 1; - return sum + amount * qty; - }, 0); + + const totalCents = subscriptions.data.reduce( + (subTotal, sub) => + subTotal + + sub.items.data.reduce((sum, item) => { + const price = item.price as Stripe.Price; + const amount = price.unit_amount ?? 0; + const qty = item.quantity ?? 1; + return sum + amount * qty; + }, 0), + 0, + ); currentPriceAmount = totalCents / 100; } diff --git a/apps/dokploy/server/utils/billing.ts b/apps/dokploy/server/utils/billing.ts index 532878466..399edce63 100644 --- a/apps/dokploy/server/utils/billing.ts +++ b/apps/dokploy/server/utils/billing.ts @@ -27,11 +27,10 @@ export const getCurrentPlanForUser = async ( status: "active", expand: ["data.items.data.price"], }); - const activeSub = subscriptions.data[0]; - if (!activeSub) return null; + if (subscriptions.data.length === 0) return null; - const priceIds = activeSub.items.data.map( - (item) => (item.price as Stripe.Price).id, + const priceIds = subscriptions.data.flatMap((sub) => + sub.items.data.map((item) => (item.price as Stripe.Price).id), ); if ( From 3f6dfbaaa7e682341f5221543fcc2702a5d4b91b Mon Sep 17 00:00:00 2001 From: Mauricio Siu Date: Sun, 30 Aug 2026 18:13:33 -0600 Subject: [PATCH 2/2] fix: only sum currentPriceAmount from the identified plan subscription Summing across all active subscriptions could mix amounts with different billing intervals (isAnnualCurrent only reflects the matched plan subscription), producing an inconsistent total. --- apps/dokploy/server/api/routers/stripe.ts | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/apps/dokploy/server/api/routers/stripe.ts b/apps/dokploy/server/api/routers/stripe.ts index 32e69b253..f89d2dcbf 100644 --- a/apps/dokploy/server/api/routers/stripe.ts +++ b/apps/dokploy/server/api/routers/stripe.ts @@ -115,17 +115,12 @@ export const stripeRouter = createTRPCRouter({ | undefined; isAnnualCurrent = firstPrice?.recurring?.interval === "year"; - const totalCents = subscriptions.data.reduce( - (subTotal, sub) => - subTotal + - sub.items.data.reduce((sum, item) => { - const price = item.price as Stripe.Price; - const amount = price.unit_amount ?? 0; - const qty = item.quantity ?? 1; - return sum + amount * qty; - }, 0), - 0, - ); + const totalCents = (activeSub?.items.data ?? []).reduce((sum, item) => { + const price = item.price as Stripe.Price; + const amount = price.unit_amount ?? 0; + const qty = item.quantity ?? 1; + return sum + amount * qty; + }, 0); currentPriceAmount = totalCents / 100; }