From 2ebb4595a8222a3b8237a62f755154f4f9890c71 Mon Sep 17 00:00:00 2001 From: Ahmed Bouhuolia Date: Thu, 19 Sep 2024 10:25:13 +0200 Subject: [PATCH] feat: Emit Stripe webhooks to events in the system --- .../StripeIntegrationController.ts | 24 +++++++--- .../StripeWebhooksController.ts | 40 ++++++++--------- .../server/src/interfaces/StripePayment.ts | 8 ++-- packages/server/src/loaders/eventEmitter.ts | 2 + .../events/StripeWebhooksSubscriber.ts | 44 +++++++++++++++++++ packages/server/src/subscribers/events.ts | 5 +++ 6 files changed, 92 insertions(+), 31 deletions(-) create mode 100644 packages/server/src/services/StripePayment/events/StripeWebhooksSubscriber.ts diff --git a/packages/server/src/api/controllers/StripeIntegration/StripeIntegrationController.ts b/packages/server/src/api/controllers/StripeIntegration/StripeIntegrationController.ts index 1efb9d098..bf4919d6d 100644 --- a/packages/server/src/api/controllers/StripeIntegration/StripeIntegrationController.ts +++ b/packages/server/src/api/controllers/StripeIntegration/StripeIntegrationController.ts @@ -23,6 +23,13 @@ export class StripeIntegrationController { return router; } + /** + * Creates a new Stripe account. + * @param {Request} req - The Express request object. + * @param {Response} res - The Express response object. + * @param {NextFunction} next - The Express next middleware function. + * @returns {Promise} + */ public async createAccount(req: Request, res: Response, next: NextFunction) { const { tenantId } = req; @@ -31,17 +38,22 @@ export class StripeIntegrationController { tenantId ); - res - .status(201) - .json({ - accountId, - message: 'The Stripe account has been created successfully.', - }); + res.status(201).json({ + accountId, + message: 'The Stripe account has been created successfully.', + }); } catch (error) { next(error); } } + /** + * Creates a new Stripe account session. + * @param {Request} req - The Express request object. + * @param {Response} res - The Express response object. + * @param {NextFunction} next - The Express next middleware function. + * @returns {Promise} + */ public async createAccountSession( req: Request, res: Response, diff --git a/packages/server/src/api/controllers/StripeIntegration/StripeWebhooksController.ts b/packages/server/src/api/controllers/StripeIntegration/StripeWebhooksController.ts index cbd45953c..0d9017e27 100644 --- a/packages/server/src/api/controllers/StripeIntegration/StripeWebhooksController.ts +++ b/packages/server/src/api/controllers/StripeIntegration/StripeWebhooksController.ts @@ -1,10 +1,11 @@ -import { StripePaymentService } from '@/services/StripePayment/StripePaymentService'; import { NextFunction, Request, Response, Router } from 'express'; import { Inject, Service } from 'typedi'; -import config from '@/config'; import bodyParser from 'body-parser'; -import { SaleInvoiceStripePaymentLink } from '@/services/StripePayment/SaleInvoiceStripePaymentLink'; -import { CreatePaymentReceiveStripePayment } from '@/services/StripePayment/CreatePaymentReceivedStripePayment'; +import { EventPublisher } from '@/lib/EventPublisher/EventPublisher'; +import { StripeCheckoutSessionCompletedEventPayload } from '@/interfaces/StripePayment'; +import { StripePaymentService } from '@/services/StripePayment/StripePaymentService'; +import events from '@/subscribers/events'; +import config from '@/config'; @Service() export class StripeWebhooksController { @@ -12,7 +13,7 @@ export class StripeWebhooksController { private stripePaymentService: StripePaymentService; @Inject() - private createPaymentReceiveStripePayment: CreatePaymentReceiveStripePayment; + private eventPublisher: EventPublisher; router() { const router = Router(); @@ -26,10 +27,13 @@ export class StripeWebhooksController { } /** + * Handles incoming Stripe webhook events. + * Verifies the webhook signature, processes the event based on its type, + * and triggers appropriate actions or events in the system. * - * @param req - * @param res - * @param next + * @param {Request} req - The Express request object containing the webhook payload. + * @param {Response} res - The Express response object. + * @param {NextFunction} next - The Express next middleware function. */ public async handleWebhook(req: Request, res: Response, next: NextFunction) { try { @@ -50,20 +54,12 @@ export class StripeWebhooksController { // Handle the event based on its type switch (event.type) { case 'checkout.session.completed': - const { metadata } = event.data.object; - const tenantId = parseInt(metadata.tenantId, 10); - const saleInvoiceId = parseInt(metadata.saleInvoiceId, 10); - - // Get the amount from the event - const amount = event.data.object.amount_total; - - // Convert from Stripe amount (cents) to normal amount (dollars) - const amountInDollars = amount / 100; - - await this.createPaymentReceiveStripePayment.createPaymentReceived( - tenantId, - saleInvoiceId, - amountInDollars + // Triggers `onStripeCheckoutSessionCompleted` event. + this.eventPublisher.emitAsync( + events.stripeWebhooks.onCheckoutSessionCompleted, + { + event, + } as StripeCheckoutSessionCompletedEventPayload ); break; case 'payment_intent.payment_failed': diff --git a/packages/server/src/interfaces/StripePayment.ts b/packages/server/src/interfaces/StripePayment.ts index 98e934e11..003d00b05 100644 --- a/packages/server/src/interfaces/StripePayment.ts +++ b/packages/server/src/interfaces/StripePayment.ts @@ -1,8 +1,10 @@ - - export interface StripePaymentLinkCreatedEventPayload { tenantId: number; paymentLinkId: string; saleInvoiceId: number; stripeIntegrationId: number; -} \ No newline at end of file +} + +export interface StripeCheckoutSessionCompletedEventPayload { + event: any; +} diff --git a/packages/server/src/loaders/eventEmitter.ts b/packages/server/src/loaders/eventEmitter.ts index d9e7ec54d..77abea439 100644 --- a/packages/server/src/loaders/eventEmitter.ts +++ b/packages/server/src/loaders/eventEmitter.ts @@ -121,6 +121,7 @@ import { TriggerInvalidateCacheOnSubscriptionChange } from '@/services/Subscript import { EventsTrackerListeners } from '@/services/EventsTracker/events/events'; import { CreatePaymentLinkOnInvoiceCreated } from '@/services/StripePayment/events/CreatePaymentLinkOnInvoiceCreated'; import { InvoicePaymentIntegrationSubscriber } from '@/services/Sales/Invoices/subscribers/InvoicePaymentIntegrationSubscriber'; +import { StripeWebhooksSubscriber } from '@/services/StripePayment/events/StripeWebhooksSubscriber'; export default () => { return new EventPublisher(); @@ -296,6 +297,7 @@ export const susbcribers = () => { // Stripe Payment CreatePaymentLinkOnInvoiceCreated, InvoicePaymentIntegrationSubscriber, + StripeWebhooksSubscriber, ...EventsTrackerListeners ]; diff --git a/packages/server/src/services/StripePayment/events/StripeWebhooksSubscriber.ts b/packages/server/src/services/StripePayment/events/StripeWebhooksSubscriber.ts new file mode 100644 index 000000000..f9ad3ad5c --- /dev/null +++ b/packages/server/src/services/StripePayment/events/StripeWebhooksSubscriber.ts @@ -0,0 +1,44 @@ +import { Inject, Service } from 'typedi'; +import events from '@/subscribers/events'; +import { CreatePaymentReceiveStripePayment } from '../CreatePaymentReceivedStripePayment'; +import { StripeCheckoutSessionCompletedEventPayload } from '@/interfaces/StripePayment'; + +@Service() +export class StripeWebhooksSubscriber { + @Inject() + private createPaymentReceiveStripePayment: CreatePaymentReceiveStripePayment; + + /** + * Attaches the subscriber to the event dispatcher. + */ + public attach(bus) { + bus.subscribe( + events.stripeWebhooks.onCheckoutSessionCompleted, + this.handleCheckoutSessionCompleted.bind(this) + ); + } + + /** + * Handles the checkout session completed webhook event. + * @param {StripeCheckoutSessionCompletedEventPayload} payload - + */ + async handleCheckoutSessionCompleted({ + event, + }: StripeCheckoutSessionCompletedEventPayload) { + const { metadata } = event.data.object; + const tenantId = parseInt(metadata.tenantId, 10); + const saleInvoiceId = parseInt(metadata.saleInvoiceId, 10); + + // Get the amount from the event + const amount = event.data.object.amount_total; + + // Convert from Stripe amount (cents) to normal amount (dollars) + const amountInDollars = amount / 100; + + await this.createPaymentReceiveStripePayment.createPaymentReceived( + tenantId, + saleInvoiceId, + amountInDollars + ); + } +} diff --git a/packages/server/src/subscribers/events.ts b/packages/server/src/subscribers/events.ts index f78c5fbd7..c29090d87 100644 --- a/packages/server/src/subscribers/events.ts +++ b/packages/server/src/subscribers/events.ts @@ -716,5 +716,10 @@ export default { onPaymentLinkCreated: 'onStripePaymentLinkCreated', onPaymentLinkInactivated: 'onStripePaymentLinkInactivated' + }, + + // Stripe Payment Webhooks + stripeWebhooks: { + onCheckoutSessionCompleted: 'onStripeCheckoutSessionCompleted' } };