@@ -15,7 +15,7 @@ global.__views_dirname = path.join(global.__static_dirname, '/views');
|
||||
global.__images_dirname = path.join(global.__static_dirname, '/images');
|
||||
|
||||
async function bootstrap() {
|
||||
const app = await NestFactory.create(AppModule);
|
||||
const app = await NestFactory.create(AppModule, { rawBody: true });
|
||||
app.setGlobalPrefix('/api');
|
||||
|
||||
// create and mount the middleware manually here
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Response } from 'express';
|
||||
import { Controller, Get, Param, Res } from '@nestjs/common';
|
||||
import { Controller, Get, Param, Post, Res } from '@nestjs/common';
|
||||
import { PaymentLinksApplication } from './PaymentLinksApplication';
|
||||
import { ApiOperation, ApiParam, ApiResponse, ApiTags } from '@nestjs/swagger';
|
||||
import { ApiCommonHeaders } from '@/common/decorators/ApiCommonHeaders';
|
||||
@@ -40,7 +40,7 @@ export class PaymentLinksController {
|
||||
return { data };
|
||||
}
|
||||
|
||||
@Get('/:paymentLinkId/stripe_checkout_session')
|
||||
@Post('/:paymentLinkId/stripe_checkout_session')
|
||||
@ApiOperation({
|
||||
summary: 'Create Stripe checkout session',
|
||||
description:
|
||||
|
||||
@@ -15,7 +15,7 @@ export class GetPaymentMethodsStateService {
|
||||
private readonly paymentIntegrationModel: TenantModelProxy<
|
||||
typeof PaymentIntegration
|
||||
>,
|
||||
) {}
|
||||
) { }
|
||||
|
||||
/**
|
||||
* Retrieves the payment state provising state.
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { EventEmitter2 } from '@nestjs/event-emitter';
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Knex } from 'knex';
|
||||
import { TransformerInjectable } from '@/modules/Transformer/TransformerInjectable.service';
|
||||
import { SaleInvoice } from '../models/SaleInvoice';
|
||||
import { SaleInvoiceTransformer } from './SaleInvoice.transformer';
|
||||
@@ -17,7 +18,7 @@ export class GetSaleInvoice {
|
||||
|
||||
@Inject(SaleInvoice.name)
|
||||
private saleInvoiceModel: TenantModelProxy<typeof SaleInvoice>,
|
||||
) {}
|
||||
) { }
|
||||
|
||||
/**
|
||||
* Retrieve sale invoice with associated entries.
|
||||
@@ -27,9 +28,10 @@ export class GetSaleInvoice {
|
||||
*/
|
||||
public async getSaleInvoice(
|
||||
saleInvoiceId: number,
|
||||
trx?: Knex.Transaction,
|
||||
): Promise<SaleInvoiceResponseDto> {
|
||||
const saleInvoice = await this.saleInvoiceModel()
|
||||
.query()
|
||||
.query(trx)
|
||||
.findById(saleInvoiceId)
|
||||
.withGraphFetched('entries.item')
|
||||
.withGraphFetched('entries.tax')
|
||||
|
||||
@@ -12,7 +12,7 @@ export class CreatePaymentReceiveStripePayment {
|
||||
private readonly createPaymentReceivedService: CreatePaymentReceivedService,
|
||||
private readonly uow: UnitOfWork,
|
||||
private readonly accountRepository: AccountRepository,
|
||||
) {}
|
||||
) { }
|
||||
|
||||
/**
|
||||
* Creates a payment received transaction associated to the given invoice.
|
||||
@@ -28,7 +28,7 @@ export class CreatePaymentReceiveStripePayment {
|
||||
|
||||
// Retrieves the given invoice to create payment transaction associated to it.
|
||||
const invoice =
|
||||
await this.getSaleInvoiceService.getSaleInvoice(saleInvoiceId);
|
||||
await this.getSaleInvoiceService.getSaleInvoice(saleInvoiceId, trx);
|
||||
|
||||
const paymentReceivedDTO = {
|
||||
customerId: invoice.customerId,
|
||||
@@ -38,6 +38,7 @@ export class CreatePaymentReceiveStripePayment {
|
||||
referenceNo: '',
|
||||
statement: '',
|
||||
depositAccountId: stripeClearingAccount.id,
|
||||
branchId: invoice.branchId,
|
||||
entries: [{ invoiceId: saleInvoiceId, paymentAmount: paidAmount }],
|
||||
};
|
||||
// Create a payment received transaction associated to the given invoice.
|
||||
|
||||
@@ -7,7 +7,7 @@ export class GetStripeAuthorizationLinkService {
|
||||
|
||||
public getStripeAuthLink() {
|
||||
const clientId = this.config.get('stripePayment.clientId');
|
||||
const redirectUrl = this.config.get('stripePayment.redirectTo');
|
||||
const redirectUrl = this.config.get('stripePayment.redirectUrl');
|
||||
|
||||
const authorizationUri = `https://connect.stripe.com/oauth/v2/authorize?response_type=code&client_id=${clientId}&scope=read_write&redirect_uri=${redirectUrl}`;
|
||||
|
||||
|
||||
@@ -3,12 +3,11 @@ import { CreateStripeAccountLinkService } from './CreateStripeAccountLink';
|
||||
import { CreateStripeAccountService } from './CreateStripeAccountService';
|
||||
import { StripePaymentApplication } from './StripePaymentApplication';
|
||||
import { ExchangeStripeOAuthTokenService } from './ExchangeStripeOauthToken';
|
||||
import { PaymentIntegration } from './models/PaymentIntegration.model';
|
||||
import { SeedStripeAccountsOnOAuthGrantedSubscriber } from './subscribers/SeedStripeAccounts';
|
||||
import { StripeWebhooksSubscriber } from './subscribers/StripeWebhooksSubscriber';
|
||||
import { StripeIntegrationController } from './StripePayment.controller';
|
||||
import { StripePaymentWebhooksController } from './StripePaymentWebhooks.controller';
|
||||
import { StripePaymentService } from './StripePaymentService';
|
||||
import { InjectSystemModel } from '../System/SystemModels/SystemModels.module';
|
||||
import { GetStripeAuthorizationLinkService } from './GetStripeAuthorizationLink';
|
||||
import { AccountsModule } from '../Accounts/Accounts.module';
|
||||
import { CreatePaymentReceiveStripePayment } from './CreatePaymentReceivedStripePayment';
|
||||
@@ -16,8 +15,6 @@ import { SaleInvoicesModule } from '../SaleInvoices/SaleInvoices.module';
|
||||
import { PaymentsReceivedModule } from '../PaymentReceived/PaymentsReceived.module';
|
||||
import { TenancyContext } from '../Tenancy/TenancyContext.service';
|
||||
|
||||
const models = [InjectSystemModel(PaymentIntegration)];
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
AccountsModule,
|
||||
@@ -25,7 +22,6 @@ const models = [InjectSystemModel(PaymentIntegration)];
|
||||
forwardRef(() => SaleInvoicesModule),
|
||||
],
|
||||
providers: [
|
||||
...models,
|
||||
StripePaymentService,
|
||||
GetStripeAuthorizationLinkService,
|
||||
CreateStripeAccountLinkService,
|
||||
@@ -38,6 +34,6 @@ const models = [InjectSystemModel(PaymentIntegration)];
|
||||
TenancyContext,
|
||||
],
|
||||
exports: [StripePaymentService, GetStripeAuthorizationLinkService],
|
||||
controllers: [StripeIntegrationController],
|
||||
controllers: [StripeIntegrationController, StripePaymentWebhooksController],
|
||||
})
|
||||
export class StripePaymentModule {}
|
||||
export class StripePaymentModule { }
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
import {
|
||||
Controller,
|
||||
Headers,
|
||||
HttpCode,
|
||||
HttpException,
|
||||
HttpStatus,
|
||||
Post,
|
||||
RawBodyRequest,
|
||||
Req,
|
||||
} from '@nestjs/common';
|
||||
import { Request, Response } from 'express';
|
||||
import { ApiOperation, ApiTags } from '@nestjs/swagger';
|
||||
import { EventEmitter2 } from '@nestjs/event-emitter';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { StripePaymentService } from './StripePaymentService';
|
||||
import { events } from '@/common/events/events';
|
||||
import {
|
||||
StripeCheckoutSessionCompletedEventPayload,
|
||||
StripeWebhookEventPayload,
|
||||
} from './StripePayment.types';
|
||||
import { PublicRoute } from '../Auth/guards/jwt.guard';
|
||||
|
||||
@Controller('/webhooks/stripe')
|
||||
@ApiTags('stripe')
|
||||
@PublicRoute()
|
||||
export class StripePaymentWebhooksController {
|
||||
constructor(
|
||||
private readonly stripePaymentService: StripePaymentService,
|
||||
private readonly eventEmitter: EventEmitter2,
|
||||
private readonly configService: ConfigService,
|
||||
) { }
|
||||
|
||||
/**
|
||||
* 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 {Request} req - The Express request object containing the webhook payload.
|
||||
* @param {Response} res - The Express response object.
|
||||
* @returns {Promise<Response>}
|
||||
*/
|
||||
@Post('/')
|
||||
@HttpCode(200)
|
||||
@ApiOperation({ summary: 'Listen to Stripe webhooks' })
|
||||
async handleWebhook(
|
||||
@Req() req: RawBodyRequest<Request>,
|
||||
@Headers('stripe-signature') signature: string,
|
||||
) {
|
||||
try {
|
||||
const rawBody = req.rawBody || req.body;
|
||||
const webhooksSecret = this.configService.get(
|
||||
'stripePayment.webhooksSecret',
|
||||
);
|
||||
if (!webhooksSecret) {
|
||||
throw new HttpException(
|
||||
'Stripe webhook secret is not configured',
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
}
|
||||
|
||||
if (!signature) {
|
||||
throw new HttpException(
|
||||
'Stripe signature header is missing',
|
||||
HttpStatus.BAD_REQUEST,
|
||||
);
|
||||
}
|
||||
let event;
|
||||
|
||||
// Verify webhook signature and extract the event.
|
||||
// See https://stripe.com/docs/webhooks#verify-events for more information.
|
||||
try {
|
||||
event = this.stripePaymentService.stripe.webhooks.constructEvent(
|
||||
rawBody,
|
||||
signature,
|
||||
webhooksSecret,
|
||||
);
|
||||
} catch (err) {
|
||||
throw new HttpException(
|
||||
`Webhook Error: ${err.message}`,
|
||||
HttpStatus.BAD_REQUEST,
|
||||
);
|
||||
}
|
||||
// Handle the event based on its type
|
||||
switch (event.type) {
|
||||
case 'checkout.session.completed':
|
||||
// Triggers `onStripeCheckoutSessionCompleted` event.
|
||||
await this.eventEmitter.emitAsync(
|
||||
events.stripeWebhooks.onCheckoutSessionCompleted,
|
||||
{
|
||||
event,
|
||||
} as StripeCheckoutSessionCompletedEventPayload,
|
||||
);
|
||||
break;
|
||||
case 'account.updated':
|
||||
// Triggers `onStripeAccountUpdated` event.
|
||||
await this.eventEmitter.emitAsync(
|
||||
events.stripeWebhooks.onAccountUpdated,
|
||||
{
|
||||
event,
|
||||
} as StripeWebhookEventPayload,
|
||||
);
|
||||
break;
|
||||
|
||||
// Add more cases as needed
|
||||
default:
|
||||
console.log(`Unhandled event type ${event.type}`);
|
||||
}
|
||||
return { received: true };
|
||||
} catch (error) {
|
||||
if (error instanceof HttpException) {
|
||||
throw error;
|
||||
}
|
||||
throw new HttpException(
|
||||
error.message || 'Internal server error',
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 {
|
||||
StripeCheckoutSessionCompletedEventPayload,
|
||||
StripeWebhookEventPayload,
|
||||
} 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 { PaymentIntegration } from '../models/PaymentIntegration.model';
|
||||
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
|
||||
import { TenantModel } from '@/modules/System/models/TenantModel';
|
||||
import { SystemUser } from '@/modules/System/models/SystemUser';
|
||||
|
||||
@Injectable()
|
||||
export class StripeWebhooksSubscriber {
|
||||
constructor(
|
||||
private readonly createPaymentReceiveStripePayment: CreatePaymentReceiveStripePayment,
|
||||
private readonly clsService: ClsService,
|
||||
|
||||
@Inject(SystemUser.name)
|
||||
private readonly systemUserModel: typeof SystemUser,
|
||||
|
||||
@Inject(PaymentIntegration.name)
|
||||
private readonly paymentIntegrationModel: TenantModelProxy<
|
||||
@@ -23,8 +27,8 @@ export class StripeWebhooksSubscriber {
|
||||
>,
|
||||
|
||||
@Inject(TenantModel.name)
|
||||
private readonly tenantModel: typeof TenantModel
|
||||
) {}
|
||||
private readonly tenantModel: typeof TenantModel,
|
||||
) { }
|
||||
|
||||
/**
|
||||
* Handles the checkout session completed webhook event.
|
||||
@@ -38,10 +42,22 @@ export class StripeWebhooksSubscriber {
|
||||
const tenantId = parseInt(metadata.tenantId, 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);
|
||||
// await initializeTenantSettings(tenantId);
|
||||
this.clsService.set('organizationId', tenant.organizationId);
|
||||
this.clsService.set('userId', user.id);
|
||||
this.clsService.set('tenantId', tenant.id);
|
||||
|
||||
// Get the amount from the event
|
||||
const amount = event.data.object.amount_total;
|
||||
|
||||
@@ -26,6 +26,7 @@ import { BillLandedCostEntry } from '@/modules/BillLandedCosts/models/BillLanded
|
||||
import { BillLandedCost } from '@/modules/BillLandedCosts/models/BillLandedCost';
|
||||
import { VendorCreditAppliedBill } from '@/modules/VendorCreditsApplyBills/models/VendorCreditAppliedBill';
|
||||
import { SaleInvoice } from '@/modules/SaleInvoices/models/SaleInvoice';
|
||||
import { PaymentIntegration } from '@/modules/StripePayment/models/PaymentIntegration.model';
|
||||
import { PaymentReceivedEntry } from '@/modules/PaymentReceived/models/PaymentReceivedEntry';
|
||||
import { CreditNoteAppliedInvoice } from '@/modules/CreditNotesApplyInvoice/models/CreditNoteAppliedInvoice';
|
||||
import { CreditNote } from '@/modules/CreditNotes/models/CreditNote';
|
||||
@@ -75,6 +76,7 @@ const models = [
|
||||
VendorCredit,
|
||||
VendorCreditAppliedBill,
|
||||
RefundVendorCredit,
|
||||
PaymentIntegration,
|
||||
PaymentReceived,
|
||||
PaymentReceivedEntry,
|
||||
TenantUser,
|
||||
|
||||
@@ -243,7 +243,7 @@ const transformPaymentMethodsToRequest = (
|
||||
paymentMethods: Record<string, { enable: boolean }>,
|
||||
): Array<{ payment_integration_id: string; enable: boolean }> => {
|
||||
return Object.entries(paymentMethods).map(([paymentMethodId, method]) => ({
|
||||
payment_integration_id: paymentMethodId,
|
||||
payment_integration_id: +paymentMethodId,
|
||||
enable: method.enable,
|
||||
}));
|
||||
};
|
||||
|
||||
@@ -37,7 +37,7 @@ export const useCreateStripeAccountLink = (
|
||||
return useMutation(
|
||||
(values: StripeAccountLinkValues) => {
|
||||
return apiRequest
|
||||
.post('/stripe_integration/account_link', {
|
||||
.post('/stripe/account_link', {
|
||||
stripe_account_id: values?.stripeAccountId,
|
||||
})
|
||||
.then((res) => transformToCamelCase(res.data));
|
||||
@@ -72,7 +72,7 @@ export const useCreateStripeAccountSession = (
|
||||
return useMutation(
|
||||
(values: AccountSessionValues) => {
|
||||
return apiRequest
|
||||
.post('/stripe_integration/account_session', {
|
||||
.post('/stripe/account_session', {
|
||||
account: values?.connectedAccountId,
|
||||
})
|
||||
.then((res) => res.data);
|
||||
@@ -100,7 +100,7 @@ export const useCreateStripeAccount = (
|
||||
return useMutation(
|
||||
(values: CreateStripeAccountValues) => {
|
||||
return apiRequest
|
||||
.post('/stripe_integration/account')
|
||||
.post('/stripe/account')
|
||||
.then((res) => res.data);
|
||||
},
|
||||
{ ...options },
|
||||
@@ -131,7 +131,7 @@ export const useGetStripeAccountLink = (
|
||||
'getStripeAccountLink',
|
||||
() => {
|
||||
return apiRequest
|
||||
.get('/stripe_integration/link')
|
||||
.get('/stripe/link')
|
||||
.then((res) => transformToCamelCase(res.data));
|
||||
},
|
||||
{ ...options },
|
||||
@@ -163,7 +163,7 @@ export const useSetStripeAccountCallback = (
|
||||
return useMutation(
|
||||
(values: StripeAccountCallbackMutationValues) => {
|
||||
return apiRequest
|
||||
.post(`/stripe_integration/callback`, values)
|
||||
.post(`/stripe/callback`, values)
|
||||
.then(
|
||||
(res) =>
|
||||
transformToCamelCase(
|
||||
|
||||
Reference in New Issue
Block a user