fix: stripe payment webhooks

This commit is contained in:
Ahmed Bouhuolia
2025-12-02 01:26:58 +02:00
parent 8f54754aba
commit eb51646035
5 changed files with 35 additions and 22 deletions

View File

@@ -15,7 +15,7 @@ global.__views_dirname = path.join(global.__static_dirname, '/views');
global.__images_dirname = path.join(global.__static_dirname, '/images'); global.__images_dirname = path.join(global.__static_dirname, '/images');
async function bootstrap() { async function bootstrap() {
const app = await NestFactory.create(AppModule); const app = await NestFactory.create(AppModule, { rawBody: true });
app.setGlobalPrefix('/api'); app.setGlobalPrefix('/api');
// create and mount the middleware manually here // create and mount the middleware manually here

View File

@@ -1,5 +1,6 @@
import { EventEmitter2 } from '@nestjs/event-emitter'; import { EventEmitter2 } from '@nestjs/event-emitter';
import { Inject, Injectable } from '@nestjs/common'; import { Inject, Injectable } from '@nestjs/common';
import { Knex } from 'knex';
import { TransformerInjectable } from '@/modules/Transformer/TransformerInjectable.service'; import { TransformerInjectable } from '@/modules/Transformer/TransformerInjectable.service';
import { SaleInvoice } from '../models/SaleInvoice'; import { SaleInvoice } from '../models/SaleInvoice';
import { SaleInvoiceTransformer } from './SaleInvoice.transformer'; import { SaleInvoiceTransformer } from './SaleInvoice.transformer';
@@ -27,9 +28,10 @@ export class GetSaleInvoice {
*/ */
public async getSaleInvoice( public async getSaleInvoice(
saleInvoiceId: number, saleInvoiceId: number,
trx?: Knex.Transaction,
): Promise<SaleInvoiceResponseDto> { ): Promise<SaleInvoiceResponseDto> {
const saleInvoice = await this.saleInvoiceModel() const saleInvoice = await this.saleInvoiceModel()
.query() .query(trx)
.findById(saleInvoiceId) .findById(saleInvoiceId)
.withGraphFetched('entries.item') .withGraphFetched('entries.item')
.withGraphFetched('entries.tax') .withGraphFetched('entries.tax')

View File

@@ -28,7 +28,7 @@ export class CreatePaymentReceiveStripePayment {
// Retrieves the given invoice to create payment transaction associated to it. // Retrieves the given invoice to create payment transaction associated to it.
const invoice = const invoice =
await this.getSaleInvoiceService.getSaleInvoice(saleInvoiceId); await this.getSaleInvoiceService.getSaleInvoice(saleInvoiceId, trx);
const paymentReceivedDTO = { const paymentReceivedDTO = {
customerId: invoice.customerId, customerId: invoice.customerId,
@@ -38,6 +38,7 @@ export class CreatePaymentReceiveStripePayment {
referenceNo: '', referenceNo: '',
statement: '', statement: '',
depositAccountId: stripeClearingAccount.id, depositAccountId: stripeClearingAccount.id,
branchId: invoice.branchId,
entries: [{ invoiceId: saleInvoiceId, paymentAmount: paidAmount }], entries: [{ invoiceId: saleInvoiceId, paymentAmount: paidAmount }],
}; };
// Create a payment received transaction associated to the given invoice. // Create a payment received transaction associated to the given invoice.

View File

@@ -5,8 +5,8 @@ import {
HttpException, HttpException,
HttpStatus, HttpStatus,
Post, Post,
RawBodyRequest,
Req, Req,
Res,
} from '@nestjs/common'; } from '@nestjs/common';
import { Request, Response } from 'express'; import { Request, Response } from 'express';
import { ApiOperation, ApiTags } from '@nestjs/swagger'; import { ApiOperation, ApiTags } from '@nestjs/swagger';
@@ -42,13 +42,10 @@ export class StripePaymentWebhooksController {
@HttpCode(200) @HttpCode(200)
@ApiOperation({ summary: 'Listen to Stripe webhooks' }) @ApiOperation({ summary: 'Listen to Stripe webhooks' })
async handleWebhook( async handleWebhook(
@Req() req: Request, @Req() req: RawBodyRequest<Request>,
@Res() res: Response,
@Headers('stripe-signature') signature: string, @Headers('stripe-signature') signature: string,
) { ) {
console.log(signature, 'signature');
try { try {
// @ts-ignore - rawBody is set by middleware
const rawBody = req.rawBody || req.body; const rawBody = req.rawBody || req.body;
const webhooksSecret = this.configService.get( const webhooksSecret = this.configService.get(
'stripePayment.webhooksSecret', 'stripePayment.webhooksSecret',
@@ -82,7 +79,6 @@ export class StripePaymentWebhooksController {
HttpStatus.BAD_REQUEST, HttpStatus.BAD_REQUEST,
); );
} }
console.log(event.type, 'event.type');
// Handle the event based on its type // Handle the event based on its type
switch (event.type) { switch (event.type) {
case 'checkout.session.completed': case 'checkout.session.completed':
@@ -94,7 +90,6 @@ export class StripePaymentWebhooksController {
} as StripeCheckoutSessionCompletedEventPayload, } as StripeCheckoutSessionCompletedEventPayload,
); );
break; break;
case 'account.updated': case 'account.updated':
// Triggers `onStripeAccountUpdated` event. // Triggers `onStripeAccountUpdated` event.
await this.eventEmitter.emitAsync( await this.eventEmitter.emitAsync(
@@ -109,8 +104,7 @@ export class StripePaymentWebhooksController {
default: default:
console.log(`Unhandled event type ${event.type}`); console.log(`Unhandled event type ${event.type}`);
} }
return { received: true };
return res.status(200).json({ received: true });
} catch (error) { } catch (error) {
if (error instanceof HttpException) { if (error instanceof HttpException) {
throw error; throw error;

View File

@@ -1,21 +1,25 @@
import { Inject, Injectable } from '@nestjs/common';
import { OnEvent } from '@nestjs/event-emitter';
import { ClsService } from 'nestjs-cls';
import { CreatePaymentReceiveStripePayment } from '../CreatePaymentReceivedStripePayment'; import { CreatePaymentReceiveStripePayment } from '../CreatePaymentReceivedStripePayment';
import { import {
StripeCheckoutSessionCompletedEventPayload, StripeCheckoutSessionCompletedEventPayload,
StripeWebhookEventPayload, StripeWebhookEventPayload,
} from '../StripePayment.types'; } from '../StripePayment.types';
// import { initalizeTenantServices } from '@/api/middleware/TenantDependencyInjection';
// import { initializeTenantSettings } from '@/api/middleware/SettingsMiddleware';
import { OnEvent } from '@nestjs/event-emitter';
import { Inject, Injectable } from '@nestjs/common';
import { events } from '@/common/events/events'; import { events } from '@/common/events/events';
import { PaymentIntegration } from '../models/PaymentIntegration.model'; import { PaymentIntegration } from '../models/PaymentIntegration.model';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel'; import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
import { TenantModel } from '@/modules/System/models/TenantModel'; import { TenantModel } from '@/modules/System/models/TenantModel';
import { SystemUser } from '@/modules/System/models/SystemUser';
@Injectable() @Injectable()
export class StripeWebhooksSubscriber { export class StripeWebhooksSubscriber {
constructor( constructor(
private readonly createPaymentReceiveStripePayment: CreatePaymentReceiveStripePayment, private readonly createPaymentReceiveStripePayment: CreatePaymentReceiveStripePayment,
private readonly clsService: ClsService,
@Inject(SystemUser.name)
private readonly systemUserModel: typeof SystemUser,
@Inject(PaymentIntegration.name) @Inject(PaymentIntegration.name)
private readonly paymentIntegrationModel: TenantModelProxy< private readonly paymentIntegrationModel: TenantModelProxy<
@@ -23,7 +27,7 @@ export class StripeWebhooksSubscriber {
>, >,
@Inject(TenantModel.name) @Inject(TenantModel.name)
private readonly tenantModel: typeof TenantModel private readonly tenantModel: typeof TenantModel,
) { } ) { }
/** /**
@@ -38,10 +42,22 @@ export class StripeWebhooksSubscriber {
const tenantId = parseInt(metadata.tenantId, 10); const tenantId = parseInt(metadata.tenantId, 10);
const saleInvoiceId = parseInt(metadata.saleInvoiceId, 10); const saleInvoiceId = parseInt(metadata.saleInvoiceId, 10);
const tenant = await this.tenantModel
.query()
.findOne({ id: tenantId })
.throwIfNotFound();
const user = await this.systemUserModel
.query()
.findOne({
tenantId: tenant.id,
})
.modify('active')
.throwIfNotFound();
// await initalizeTenantServices(tenantId); this.clsService.set('organizationId', tenant.organizationId);
// await initializeTenantSettings(tenantId); this.clsService.set('userId', user.id);
this.clsService.set('tenantId', tenant.id);
// Get the amount from the event // Get the amount from the event
const amount = event.data.object.amount_total; const amount = event.data.object.amount_total;