diff --git a/src/stripe/stripe-webhook.controller.ts b/src/stripe/stripe-webhook.controller.ts index bbd54b1..7e22f88 100644 --- a/src/stripe/stripe-webhook.controller.ts +++ b/src/stripe/stripe-webhook.controller.ts @@ -91,12 +91,19 @@ export class StripeWebhookController { const stripeCustomerId = session.customer as string; // Check if subscription already exists (idempotency) - const existing = await this.subscriptionService.getSubscriptionByStripeId(stripeSubscriptionId); - if (existing) { + const existingByStripeId = await this.subscriptionService.getSubscriptionByStripeId(stripeSubscriptionId); + if (existingByStripeId) { this.logger.log(`Subscription ${stripeSubscriptionId} already exists, skipping`); return; } + // Check if user already has an active subscription (prevent duplicates) + const existingActive = await this.subscriptionService.getActivePlan(userId); + if (existingActive) { + this.logger.log(`User ${userId} already has active subscription ${existingActive.id}, skipping duplicate`); + return; + } + await this.subscriptionService.createSubscription({ userId, planId, diff --git a/src/stripe/stripe.service.ts b/src/stripe/stripe.service.ts index 78de312..424d7da 100644 --- a/src/stripe/stripe.service.ts +++ b/src/stripe/stripe.service.ts @@ -80,6 +80,34 @@ export class StripeService { 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 + const staleSubscriptions = await this.prisma.agentSubscription.findMany({ + where: { + userId, + status: { in: ['INCOMPLETE', 'UNPAID', 'PAST_DUE'] }, + }, + }); + + if (staleSubscriptions.length > 0) { + // Cancel stale Stripe subscriptions before deleting DB records + for (const sub of staleSubscriptions) { + if (!sub.stripeSubscriptionId) continue; + try { + await this.getStripe().subscriptions.cancel(sub.stripeSubscriptionId); + } catch (err: any) { + this.logger.warn(`Could not cancel stale Stripe subscription ${sub.stripeSubscriptionId}: ${err.message}`); + } + } + await this.prisma.agentSubscription.deleteMany({ + where: { + userId, + status: { in: ['INCOMPLETE', 'UNPAID', 'PAST_DUE'] }, + }, + }); + this.logger.log(`Cleaned up ${staleSubscriptions.length} stale subscription(s) for user ${userId}`); + } + // Get or create Stripe customer const user = await this.prisma.user.findUnique({ where: { id: userId },