feat: Sync Stripe subscription period end and cancellation status with local records.
This commit is contained in:
@@ -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,
|
||||||
|
),
|
||||||
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user