diff --git a/src/controllers/admin/webhook.ts b/src/controllers/admin/webhook.ts index 15efe5b8..70b38281 100644 --- a/src/controllers/admin/webhook.ts +++ b/src/controllers/admin/webhook.ts @@ -1,4 +1,4 @@ -import Stripe from 'stripe'; +import type Stripe from 'stripe'; import type { Request, Response } from 'express'; import * as dotenv from 'dotenv'; import { StatusCodes } from 'http-status-codes'; @@ -15,21 +15,13 @@ import { buildSubscriptionData } from '../../services/track/helpers.js'; dotenv.config(); export class WebhookController { - private readonly stripe: Stripe; - - constructor() { - this.stripe = new Stripe(process.env.STRIPE_SECRET_KEY); - } - public static instance = new WebhookController(); public async handleWebhook(request: Request, response: Response) { // Signature verification and webhook handling is placed in the same method // cause stripe uses the mthod which validate the signature and provides the event. let event = request.body; - let subscription; - let status; - const stripe = new Stripe(process.env.STRIPE_SECRET_KEY); + const stripe = response.locals.stripe as Stripe; if (!process.env.STRIPE_WEBHOOK_SECRET) { await eventTracker.notify({ @@ -61,52 +53,14 @@ export class WebhookController { try { // Handle the event switch (event.type) { - case 'customer.subscription.trial_will_end': - subscription = event.data.object; - status = subscription.status; - await eventTracker.notify({ - message: EventTracker.compileBasicNotification( - `Subscription status is ${status} for subscription with id: ${subscription.id}`, - 'Stripe Webhook: customer.subscription.trial_will_end' - ), - severity: 'info', - } satisfies INotifyMessage); - break; case 'customer.subscription.deleted': - subscription = event.data.object; - status = subscription.status; - await eventTracker.notify({ - message: EventTracker.compileBasicNotification( - `Subscription status is ${status} for subscription with id: ${subscription.id}`, - 'Stripe Webhook: customer.subscription.deleted' - ), - severity: 'info', - } satisfies INotifyMessage); - await this.handleSubscriptionCancel(subscription); + await this.handleSubscriptionCancel(stripe, event.data.object); break; case 'customer.subscription.created': - subscription = event.data.object; - status = subscription.status; - await eventTracker.notify({ - message: EventTracker.compileBasicNotification( - `Subscription status is ${status} for subscription with id: ${subscription.id}`, - 'Stripe Webhook: customer.subscription.created' - ), - severity: 'info', - } satisfies INotifyMessage); - await this.handleSubscriptionCreate(subscription); + await this.handleSubscriptionCreate(stripe, event.data.object); break; case 'customer.subscription.updated': - subscription = event.data.object; - status = subscription.status; - await eventTracker.notify({ - message: EventTracker.compileBasicNotification( - `Subscription status is ${status} for subscription with id: ${subscription.id}`, - 'Stripe Webhook: customer.subscription.updated' - ), - severity: 'info', - } satisfies INotifyMessage); - await this.handleSubscriptionUpdate(subscription); + await this.handleSubscriptionUpdate(stripe, event.data.object); break; default: // Unexpected event type @@ -136,12 +90,25 @@ export class WebhookController { } } - async handleSubscriptionCreate(stripeSubscription: Stripe.Subscription, customer?: CustomerEntity): Promise { + async handleSubscriptionCreate( + stripe: Stripe, + stripeSubscription: Stripe.Subscription, + customer?: CustomerEntity + ): Promise { const data = buildSubscriptionData(stripeSubscription); const operation = OperationNameEnum.SUBSCRIPTION_CREATE; + + await eventTracker.notify({ + message: EventTracker.compileBasicNotification( + `Subscription status is ${stripeSubscription.status} for subscription with id: ${stripeSubscription.id}`, + 'Stripe Webhook: customer.subscription.created' + ), + severity: 'info', + } satisfies INotifyMessage); + const [product, stripeCustomer] = await Promise.all([ - this.stripe.products.retrieve(data.productId), - this.stripe.customers.retrieve(data.paymentProviderId), + stripe.products.retrieve(data.productId), + stripe.customers.retrieve(data.paymentProviderId), ]); if (!customer) { const customers = await CustomerService.instance.customerRepository.find({ @@ -233,10 +200,18 @@ export class WebhookController { }); } - async handleSubscriptionUpdate(stripeSubscription: Stripe.Subscription): Promise { + async handleSubscriptionUpdate(stripe: Stripe, stripeSubscription: Stripe.Subscription): Promise { const data = buildSubscriptionData(stripeSubscription); const operation = OperationNameEnum.SUBSCRIPTION_UPDATE; + await eventTracker.notify({ + message: EventTracker.compileBasicNotification( + `Subscription status is ${stripeSubscription.status} for subscription with id: ${stripeSubscription.id}`, + 'Stripe Webhook: customer.subscription.updated' + ), + severity: 'info', + } satisfies INotifyMessage); + const subscription = await SubscriptionService.instance.update( data.subscriptionId, data.status, @@ -260,7 +235,7 @@ export class WebhookController { const [customer, product] = await Promise.all([ CustomerService.instance.findbyPaymentProviderId(data.paymentProviderId), - this.stripe.products.retrieve(data.productId), + stripe.products.retrieve(data.productId), ]); if (customer) { @@ -276,10 +251,18 @@ export class WebhookController { }); } - async handleSubscriptionCancel(stripeSubscription: Stripe.Subscription): Promise { + async handleSubscriptionCancel(stripe: Stripe, stripeSubscription: Stripe.Subscription): Promise { const data = buildSubscriptionData(stripeSubscription); const operation = OperationNameEnum.SUBSCRIPTION_CANCEL; + await eventTracker.notify({ + message: EventTracker.compileBasicNotification( + `Subscription status is ${stripeSubscription.status} for subscription with id: ${stripeSubscription.id}`, + 'Stripe Webhook: customer.subscription.deleted' + ), + severity: 'info', + } satisfies INotifyMessage); + const subscription = await SubscriptionService.instance.update(data.subscriptionId, data.status); if (!subscription) { const message = EventTracker.compileBasicNotification( diff --git a/src/services/admin/stripe.ts b/src/services/admin/stripe.ts index c8c53fb0..bdf81cc5 100644 --- a/src/services/admin/stripe.ts +++ b/src/services/admin/stripe.ts @@ -11,6 +11,11 @@ dotenv.config(); export class StripeService { private isFullySynced = false; + private stripe: Stripe; + + constructor() { + this.stripe = new Stripe(process.env.STRIPE_SECRET_KEY); + } async syncAll(next: NextFunction): Promise { if (!this.isFullySynced) { @@ -21,9 +26,8 @@ export class StripeService { } async syncFull(): Promise { - const stripe = new Stripe(process.env.STRIPE_SECRET_KEY); // Sync all subscriptions - for await (const subscription of stripe.subscriptions.list({ + for await (const subscription of this.stripe.subscriptions.list({ status: 'all', })) { const current = await SubscriptionService.instance.subscriptionRepository.findOne({ @@ -46,8 +50,7 @@ export class StripeService { // Sync all the subscriptions for current customer async syncCustomer(customer: CustomerEntity): Promise { - const stripe = new Stripe(process.env.STRIPE_SECRET_KEY); - for await (const subscription of stripe.subscriptions.list({ + for await (const subscription of this.stripe.subscriptions.list({ customer: customer.paymentProviderId, status: 'all', })) { @@ -63,8 +66,6 @@ export class StripeService { } async syncOne(customer: CustomerEntity): Promise { - const stripe = new Stripe(process.env.STRIPE_SECRET_KEY); - const local = await SubscriptionService.instance.findCurrent(customer); if (!local) { await eventTracker.notify({ @@ -74,11 +75,11 @@ export class StripeService { ), severity: 'debug', }); - const activeSubs = await stripe.subscriptions.list({ + const activeSubs = await this.stripe.subscriptions.list({ customer: customer.paymentProviderId, status: 'active', }); - const trialSubs = await stripe.subscriptions.list({ + const trialSubs = await this.stripe.subscriptions.list({ customer: customer.paymentProviderId, status: 'trialing', }); @@ -99,7 +100,7 @@ export class StripeService { return; } const subscriptionId = local.subscriptionId; - const remote = await stripe.subscriptions.retrieve(subscriptionId); + const remote = await this.stripe.subscriptions.retrieve(subscriptionId); if (!remote) { await eventTracker.notify({ message: EventTracker.compileBasicNotification( @@ -121,7 +122,7 @@ export class StripeService { } async createSubscription(subscription: Stripe.Subscription, customer?: CustomerEntity): Promise { - await WebhookController.instance.handleSubscriptionCreate(subscription, customer); + await WebhookController.instance.handleSubscriptionCreate(this.stripe, subscription, customer); } async updateSubscription(subscription: Stripe.Subscription, current: SubscriptionEntity): Promise { @@ -136,7 +137,7 @@ export class StripeService { }); return; } - await WebhookController.instance.handleSubscriptionUpdate(subscription); + await WebhookController.instance.handleSubscriptionUpdate(this.stripe, subscription); } }