This commit is contained in:
pradeepkumar
2026-04-07 19:55:12 +05:30
parent e7898536ec
commit 0dc13cb43a
2 changed files with 41 additions and 13 deletions

View File

@@ -97,19 +97,38 @@ export class StripeWebhookController {
return; 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); const existingActive = await this.subscriptionService.getActivePlan(userId);
if (existingActive) { if (existingActive && existingActive.stripeSubscriptionId === stripeSubscriptionId) {
this.logger.log(`User ${userId} already has active subscription ${existingActive.id}, skipping duplicate`); this.logger.log(`User ${userId} already has this exact active subscription, skipping duplicate`);
return; 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({ await this.subscriptionService.createSubscription({
userId, userId,
planId, planId,
stripeCustomerId, stripeCustomerId,
stripeSubscriptionId, stripeSubscriptionId,
status: SubscriptionStatus.ACTIVE, status: SubscriptionStatus.ACTIVE,
currentPeriodStart,
currentPeriodEnd,
}); });
this.logger.log(`Created subscription for user ${userId}, plan ${planId}`); this.logger.log(`Created subscription for user ${userId}, plan ${planId}`);

View File

@@ -24,7 +24,7 @@ export class StripeService {
} }
} }
private getStripe(): Stripe { getStripe(): Stripe {
if (!this.stripe) { if (!this.stripe) {
throw new BadRequestException( throw new BadRequestException(
'Stripe is not configured. Please set STRIPE_SECRET_KEY in environment.', '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'); throw new BadRequestException('Invalid or inactive plan');
} }
// Check if user already has an active subscription // Check if user has a VALID active subscription (ACTIVE with valid period end in the future)
const activeSubscription = await this.prisma.agentSubscription.findFirst({ const now = new Date();
where: { userId, status: 'ACTIVE' }, 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'); throw new BadRequestException('You already have an active subscription');
} }
// Clean up any incomplete/failed subscriptions to prevent duplicates // Clean up stale subscriptions (failed states OR ACTIVE with no period / expired period)
// when user retries payment after a previous failure // 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({ const staleSubscriptions = await this.prisma.agentSubscription.findMany({
where: { where: {
userId, 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({ await this.prisma.agentSubscription.deleteMany({
where: { where: {
userId, id: { in: staleSubscriptions.map((s) => s.id) },
status: { in: ['INCOMPLETE', 'UNPAID', 'PAST_DUE'] },
}, },
}); });
this.logger.log(`Cleaned up ${staleSubscriptions.length} stale subscription(s) for user ${userId}`); this.logger.log(`Cleaned up ${staleSubscriptions.length} stale subscription(s) for user ${userId}`);