From 0dc13cb43af81c43a8ae82c3ce3ef6d4281cd182 Mon Sep 17 00:00:00 2001 From: pradeepkumar Date: Tue, 7 Apr 2026 19:55:12 +0530 Subject: [PATCH] fix --- src/stripe/stripe-webhook.controller.ts | 25 ++++++++++++++++++--- src/stripe/stripe.service.ts | 29 ++++++++++++++++--------- 2 files changed, 41 insertions(+), 13 deletions(-) diff --git a/src/stripe/stripe-webhook.controller.ts b/src/stripe/stripe-webhook.controller.ts index 7e22f88..cef1040 100644 --- a/src/stripe/stripe-webhook.controller.ts +++ b/src/stripe/stripe-webhook.controller.ts @@ -97,19 +97,38 @@ export class StripeWebhookController { return; } - // Check if user already has an active subscription (prevent duplicates) + // Check if user already has an active subscription with this same Stripe ID + // (prevent true duplicates only — new Stripe subscriptions for same user are allowed + // because createCheckoutSession cleanup handles stale records) const existingActive = await this.subscriptionService.getActivePlan(userId); - if (existingActive) { - this.logger.log(`User ${userId} already has active subscription ${existingActive.id}, skipping duplicate`); + if (existingActive && existingActive.stripeSubscriptionId === stripeSubscriptionId) { + this.logger.log(`User ${userId} already has this exact active subscription, skipping duplicate`); return; } + // Fetch full subscription from Stripe to get period dates immediately + let currentPeriodStart: Date | undefined; + let currentPeriodEnd: Date | undefined; + try { + const stripeSub: any = await this.stripeService.getStripe().subscriptions.retrieve(stripeSubscriptionId); + if (stripeSub.current_period_start) { + currentPeriodStart = new Date(stripeSub.current_period_start * 1000); + } + if (stripeSub.current_period_end) { + currentPeriodEnd = new Date(stripeSub.current_period_end * 1000); + } + } catch (err: any) { + this.logger.warn(`Could not fetch Stripe subscription ${stripeSubscriptionId}: ${err.message}`); + } + await this.subscriptionService.createSubscription({ userId, planId, stripeCustomerId, stripeSubscriptionId, status: SubscriptionStatus.ACTIVE, + currentPeriodStart, + currentPeriodEnd, }); this.logger.log(`Created subscription for user ${userId}, plan ${planId}`); diff --git a/src/stripe/stripe.service.ts b/src/stripe/stripe.service.ts index 424d7da..d960a0f 100644 --- a/src/stripe/stripe.service.ts +++ b/src/stripe/stripe.service.ts @@ -24,7 +24,7 @@ export class StripeService { } } - private getStripe(): Stripe { + getStripe(): Stripe { if (!this.stripe) { throw new BadRequestException( 'Stripe is not configured. Please set STRIPE_SECRET_KEY in environment.', @@ -71,21 +71,31 @@ export class StripeService { throw new BadRequestException('Invalid or inactive plan'); } - // Check if user already has an active subscription - const activeSubscription = await this.prisma.agentSubscription.findFirst({ - where: { userId, status: 'ACTIVE' }, + // Check if user has a VALID active subscription (ACTIVE with valid period end in the future) + const now = new Date(); + const validActive = await this.prisma.agentSubscription.findFirst({ + where: { + userId, + status: 'ACTIVE', + currentPeriodEnd: { gt: now }, + }, }); - if (activeSubscription) { + if (validActive) { throw new BadRequestException('You already have an active subscription'); } - // Clean up any incomplete/failed subscriptions to prevent duplicates - // when user retries payment after a previous failure + // Clean up stale subscriptions (failed states OR ACTIVE with no period / expired period) + // This handles the case where checkout completed but invoice.paid never fired, + // leaving an orphaned ACTIVE record without proper period dates const staleSubscriptions = await this.prisma.agentSubscription.findMany({ where: { userId, - status: { in: ['INCOMPLETE', 'UNPAID', 'PAST_DUE'] }, + OR: [ + { status: { in: ['INCOMPLETE', 'UNPAID', 'PAST_DUE', 'CANCELED'] } }, + { status: 'ACTIVE', currentPeriodEnd: null }, + { status: 'ACTIVE', currentPeriodEnd: { lte: now } }, + ], }, }); @@ -101,8 +111,7 @@ export class StripeService { } await this.prisma.agentSubscription.deleteMany({ where: { - userId, - status: { in: ['INCOMPLETE', 'UNPAID', 'PAST_DUE'] }, + id: { in: staleSubscriptions.map((s) => s.id) }, }, }); this.logger.log(`Cleaned up ${staleSubscriptions.length} stale subscription(s) for user ${userId}`);