feat: Sync Stripe subscription period end and cancellation status with local records.

This commit is contained in:
pradeepkumar
2026-03-14 23:44:06 +05:30
parent 4e602abf49
commit 2c0d0f0aeb
2 changed files with 75 additions and 16 deletions

View File

@@ -42,14 +42,19 @@ export class StripeController {
@Post('create-checkout-session') @Post('create-checkout-session')
@ApiBearerAuth() @ApiBearerAuth()
@ApiOperation({ summary: 'Create a Stripe Checkout session for subscription' }) @ApiOperation({
summary: 'Create a Stripe Checkout session for subscription',
})
@HttpCode(HttpStatus.OK) @HttpCode(HttpStatus.OK)
async createCheckoutSession( async createCheckoutSession(
@CurrentUser() user: any, @CurrentUser() user: any,
@Body() dto: CreateCheckoutDto, @Body() dto: CreateCheckoutDto,
) { ) {
const frontendUrl = this.configService.get<string>('app.frontendUrl') || 'http://localhost:3000'; const frontendUrl =
const successUrl = dto.successUrl || `${frontendUrl}/agent/settings/billings/confirmation`; this.configService.get<string>('app.frontendUrl') ||
'http://localhost:3000';
const successUrl =
dto.successUrl || `${frontendUrl}/agent/settings/billings/confirmation`;
const cancelUrl = dto.cancelUrl || `${frontendUrl}/agent/settings/billings`; const cancelUrl = dto.cancelUrl || `${frontendUrl}/agent/settings/billings`;
return this.stripeService.createCheckoutSession( return this.stripeService.createCheckoutSession(
@@ -73,7 +78,9 @@ export class StripeController {
throw new BadRequestException('No active subscription found'); throw new BadRequestException('No active subscription found');
} }
const returnUrl = dto.returnUrl || `${this.configService.get<string>('app.frontendUrl') || 'http://localhost:3000'}/agent/settings/billings`; const returnUrl =
dto.returnUrl ||
`${this.configService.get<string>('app.frontendUrl') || 'http://localhost:3000'}/agent/settings/billings`;
return this.stripeService.createBillingPortalSession( return this.stripeService.createBillingPortalSession(
subscription.stripeCustomerId, subscription.stripeCustomerId,
returnUrl, returnUrl,
@@ -85,7 +92,35 @@ export class StripeController {
@ApiOperation({ summary: 'Get current user subscription status' }) @ApiOperation({ summary: 'Get current user subscription status' })
async getSubscription(@CurrentUser() user: any) { async getSubscription(@CurrentUser() user: any) {
const subscription = await this.subscriptionService.getActivePlan(user.id); const subscription = await this.subscriptionService.getActivePlan(user.id);
return subscription || null; if (!subscription) return null;
// Sync from Stripe if key fields are missing
if (
subscription.stripeSubscriptionId &&
(!subscription.currentPeriodEnd ||
subscription.cancelAtPeriodEnd === null)
) {
try {
const stripeSub = await this.stripeService.getSubscription(
subscription.stripeSubscriptionId,
);
const updated = await this.subscriptionService.updateSubscriptionStatus(
subscription.stripeSubscriptionId,
subscription.status,
{
currentPeriodEnd: new Date(
stripeSub.items.data[0].current_period_end * 1000,
),
cancelAtPeriodEnd: stripeSub.cancel_at_period_end,
},
);
return updated;
} catch {
// Fall through to return existing data
}
}
return subscription;
} }
@Post('cancel-subscription') @Post('cancel-subscription')
@@ -98,12 +133,20 @@ export class StripeController {
throw new BadRequestException('No active subscription found'); throw new BadRequestException('No active subscription found');
} }
await this.stripeService.cancelSubscription(subscription.stripeSubscriptionId, true); const stripeSubscription = await this.stripeService.cancelSubscription(
subscription.stripeSubscriptionId,
true,
);
return this.subscriptionService.updateSubscriptionStatus( return this.subscriptionService.updateSubscriptionStatus(
subscription.stripeSubscriptionId, subscription.stripeSubscriptionId,
'ACTIVE', 'ACTIVE',
{ cancelAtPeriodEnd: true }, {
cancelAtPeriodEnd: true,
currentPeriodEnd: new Date(
stripeSubscription.items.data[0].current_period_end * 1000,
),
},
); );
} }

View File

@@ -18,13 +18,17 @@ export class StripeService {
apiVersion: '2025-02-24.acacia' as any, apiVersion: '2025-02-24.acacia' as any,
}); });
} else { } else {
this.logger.warn('Stripe secret key not configured. Stripe features will be unavailable.'); this.logger.warn(
'Stripe secret key not configured. Stripe features will be unavailable.',
);
} }
} }
private getStripe(): Stripe { private getStripe(): Stripe {
if (!this.stripe) { if (!this.stripe) {
throw new BadRequestException('Stripe is not configured. Please set STRIPE_SECRET_KEY in environment.'); throw new BadRequestException(
'Stripe is not configured. Please set STRIPE_SECRET_KEY in environment.',
);
} }
return this.stripe; return this.stripe;
} }
@@ -90,7 +94,11 @@ export class StripeService {
? `${user.agentProfile.firstName || ''} ${user.agentProfile.lastName || ''}`.trim() ? `${user.agentProfile.firstName || ''} ${user.agentProfile.lastName || ''}`.trim()
: undefined; : undefined;
const customerId = await this.createCustomer(userId, user.email, customerName); const customerId = await this.createCustomer(
userId,
user.email,
customerName,
);
const session = await this.getStripe().checkout.sessions.create({ const session = await this.getStripe().checkout.sessions.create({
customer: customerId, customer: customerId,
@@ -144,11 +152,19 @@ export class StripeService {
return this.getStripe().subscriptions.cancel(stripeSubscriptionId); return this.getStripe().subscriptions.cancel(stripeSubscriptionId);
} }
constructWebhookEvent( async getSubscription(
payload: Buffer, stripeSubscriptionId: string,
signature: string, ): Promise<Stripe.Subscription> {
): Stripe.Event { return this.getStripe().subscriptions.retrieve(stripeSubscriptionId);
const webhookSecret = this.configService.get<string>('stripe.webhookSecret') || ''; }
return this.getStripe().webhooks.constructEvent(payload, signature, webhookSecret);
constructWebhookEvent(payload: Buffer, signature: string): Stripe.Event {
const webhookSecret =
this.configService.get<string>('stripe.webhookSecret') || '';
return this.getStripe().webhooks.constructEvent(
payload,
signature,
webhookSecret,
);
} }
} }