mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-16 21:00:31 +00:00
feat(nestjs): migrate to NestJS
This commit is contained in:
@@ -0,0 +1,104 @@
|
||||
import { StripePaymentService } from '../StripePayment/StripePaymentService';
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { TenantModelProxy } from '../System/models/TenantBaseModel';
|
||||
import { SaleInvoice } from '../SaleInvoices/models/SaleInvoice';
|
||||
import { PaymentLink } from './models/PaymentLink';
|
||||
import { StripeInvoiceCheckoutSessionPOJO } from '../StripePayment/StripePayment.types';
|
||||
import { ModelObject } from 'objection';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
|
||||
const origin = 'http://localhost';
|
||||
|
||||
@Injectable()
|
||||
export class CreateInvoiceCheckoutSession {
|
||||
constructor(
|
||||
private readonly stripePaymentService: StripePaymentService,
|
||||
private readonly configService: ConfigService,
|
||||
|
||||
@Inject(SaleInvoice.name)
|
||||
private readonly saleInvoiceModel: TenantModelProxy<typeof SaleInvoice>,
|
||||
|
||||
@Inject(PaymentLink.name)
|
||||
private readonly paymentLinkModel: typeof PaymentLink,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Creates a new Stripe checkout session from the given sale invoice.
|
||||
* @param {number} saleInvoiceId - Sale invoice id.
|
||||
* @returns {Promise<StripeInvoiceCheckoutSessionPOJO>}
|
||||
*/
|
||||
async createInvoiceCheckoutSession(
|
||||
publicPaymentLinkId: string,
|
||||
): Promise<StripeInvoiceCheckoutSessionPOJO> {
|
||||
// Retrieves the payment link from the given id.
|
||||
const paymentLink = await this.paymentLinkModel
|
||||
.query()
|
||||
.findOne('linkId', publicPaymentLinkId)
|
||||
.where('resourceType', 'SaleInvoice')
|
||||
.throwIfNotFound();
|
||||
|
||||
// Retrieves the invoice from associated payment link.
|
||||
const invoice = await this.saleInvoiceModel()
|
||||
.query()
|
||||
.findById(paymentLink.resourceId)
|
||||
.withGraphFetched('paymentMethods')
|
||||
.throwIfNotFound();
|
||||
|
||||
// It will be only one Stripe payment method associated to the invoice.
|
||||
const stripePaymentMethod = invoice.paymentMethods?.find(
|
||||
(method) => method.paymentIntegration?.service === 'Stripe',
|
||||
);
|
||||
const stripeAccountId = stripePaymentMethod?.paymentIntegration?.accountId;
|
||||
const paymentIntegrationId = stripePaymentMethod?.paymentIntegration?.id;
|
||||
|
||||
// Creates checkout session for the given invoice.
|
||||
const session = await this.createCheckoutSession(invoice, stripeAccountId, {
|
||||
tenantId: paymentLink.tenantId,
|
||||
paymentLinkId: paymentLink.id,
|
||||
});
|
||||
return {
|
||||
sessionId: session.id,
|
||||
publishableKey: this.configService.get('stripePayment.publishableKey'),
|
||||
redirectTo: session.url,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new Stripe checkout session for the given sale invoice.
|
||||
* @param {ISaleInvoice} invoice - The sale invoice for which the checkout session is created.
|
||||
* @param {string} stripeAccountId - The Stripe account ID associated with the payment method.
|
||||
* @returns {Promise<any>} - The created Stripe checkout session.
|
||||
*/
|
||||
private createCheckoutSession(
|
||||
invoice: ModelObject<SaleInvoice>,
|
||||
stripeAccountId?: string,
|
||||
metadata?: Record<string, any>,
|
||||
) {
|
||||
return this.stripePaymentService.stripe.checkout.sessions.create(
|
||||
{
|
||||
payment_method_types: ['card'],
|
||||
line_items: [
|
||||
{
|
||||
price_data: {
|
||||
currency: invoice.currencyCode,
|
||||
product_data: {
|
||||
name: invoice.invoiceNo,
|
||||
},
|
||||
unit_amount: invoice.total * 100, // Amount in cents
|
||||
},
|
||||
quantity: 1,
|
||||
},
|
||||
],
|
||||
mode: 'payment',
|
||||
success_url: `${origin}/success`,
|
||||
cancel_url: `${origin}/cancel`,
|
||||
metadata: {
|
||||
saleInvoiceId: invoice.id,
|
||||
resource: 'SaleInvoice',
|
||||
...metadata,
|
||||
},
|
||||
},
|
||||
{ stripeAccount: stripeAccountId },
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
import moment from 'moment';
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { TenantModelProxy } from '../System/models/TenantBaseModel';
|
||||
import { SaleInvoice } from '../SaleInvoices/models/SaleInvoice';
|
||||
import { TransformerInjectable } from '../Transformer/TransformerInjectable.service';
|
||||
import { PaymentLink } from './models/PaymentLink';
|
||||
import { ServiceError } from '../Items/ServiceError';
|
||||
import { GetInvoicePaymentLinkMetaTransformer } from '../SaleInvoices/queries/GetInvoicePaymentLink.transformer';
|
||||
import { ClsService } from 'nestjs-cls';
|
||||
import { TenancyContext } from '../Tenancy/TenancyContext.service';
|
||||
import { TenantModel } from '../System/models/TenantModel';
|
||||
|
||||
@Injectable()
|
||||
export class GetInvoicePaymentLinkMetadata {
|
||||
constructor(
|
||||
private readonly transformer: TransformerInjectable,
|
||||
private readonly clsService: ClsService,
|
||||
private readonly tenancyContext: TenancyContext,
|
||||
|
||||
@Inject(SaleInvoice.name)
|
||||
private readonly saleInvoiceModel: TenantModelProxy<typeof SaleInvoice>,
|
||||
|
||||
@Inject(PaymentLink.name)
|
||||
private readonly paymentLinkModel: typeof PaymentLink,
|
||||
|
||||
@Inject(TenantModel.name)
|
||||
private readonly systemTenantModel: typeof TenantModel,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Retrieves the invoice sharable link meta of the link id.
|
||||
* @param {string} linkId - Link id.
|
||||
*/
|
||||
async getInvoicePaymentLinkMeta(linkId: string) {
|
||||
const paymentLink = await this.paymentLinkModel
|
||||
.query()
|
||||
.findOne('linkId', linkId)
|
||||
.where('resourceType', 'SaleInvoice')
|
||||
.throwIfNotFound();
|
||||
|
||||
// Validate the expiry at date.
|
||||
if (paymentLink.expiryAt) {
|
||||
const currentDate = moment();
|
||||
const expiryDate = moment(paymentLink.expiryAt);
|
||||
|
||||
if (expiryDate.isBefore(currentDate)) {
|
||||
throw new ServiceError('PAYMENT_LINK_EXPIRED');
|
||||
}
|
||||
}
|
||||
const tenant = await this.systemTenantModel
|
||||
.query()
|
||||
.findById(paymentLink.tenantId);
|
||||
|
||||
this.clsService.set('organizationId', tenant.organizationId);
|
||||
// this.clsService.set('userId', paymentLink.userId);
|
||||
|
||||
const invoice = await this.saleInvoiceModel()
|
||||
.query()
|
||||
.findById(paymentLink.resourceId)
|
||||
.withGraphFetched('entries.item')
|
||||
.withGraphFetched('customer')
|
||||
.withGraphFetched('taxes.taxRate')
|
||||
.withGraphFetched('paymentMethods.paymentIntegration')
|
||||
.withGraphFetched('pdfTemplate')
|
||||
.throwIfNotFound();
|
||||
|
||||
return this.transformer.transform(
|
||||
invoice,
|
||||
new GetInvoicePaymentLinkMetaTransformer(),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { SaleInvoicePdf } from '../SaleInvoices/queries/SaleInvoicePdf.service';
|
||||
import { PaymentLink } from './models/PaymentLink';
|
||||
import { ClsService } from 'nestjs-cls';
|
||||
import { TenantModel } from '../System/models/TenantModel';
|
||||
|
||||
@Injectable()
|
||||
export class GetPaymentLinkInvoicePdf {
|
||||
constructor(
|
||||
private readonly getSaleInvoicePdfService: SaleInvoicePdf,
|
||||
private readonly clsService: ClsService,
|
||||
|
||||
@Inject(PaymentLink.name)
|
||||
private readonly paymentLinkModel: typeof PaymentLink,
|
||||
|
||||
@Inject(TenantModel.name)
|
||||
private readonly systemTenantModel: typeof TenantModel,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Retrieves the sale invoice PDF of the given payment link id.
|
||||
* @param {number} paymentLinkId
|
||||
* @returns {Promise<Buffer, string>}
|
||||
*/
|
||||
async getPaymentLinkInvoicePdf(
|
||||
paymentLinkId: string,
|
||||
): Promise<[Buffer, string]> {
|
||||
const paymentLink = await this.paymentLinkModel
|
||||
.query()
|
||||
.findOne('linkId', paymentLinkId)
|
||||
.where('resourceType', 'SaleInvoice')
|
||||
.throwIfNotFound();
|
||||
|
||||
const saleInvoiceId = paymentLink.resourceId;
|
||||
const tenant = await this.systemTenantModel
|
||||
.query()
|
||||
.findById(paymentLink.tenantId);
|
||||
|
||||
this.clsService.set('organizationId', tenant.organizationId);
|
||||
|
||||
return this.getSaleInvoicePdfService.getSaleInvoicePdf(saleInvoiceId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
import { Response } from 'express';
|
||||
import { Controller, Get, Param, Res } from '@nestjs/common';
|
||||
import { PaymentLinksApplication } from './PaymentLinksApplication';
|
||||
import { ApiOperation, ApiParam, ApiResponse, ApiTags } from '@nestjs/swagger';
|
||||
|
||||
@Controller('payment-links')
|
||||
@ApiTags('payment-links')
|
||||
export class PaymentLinksController {
|
||||
constructor(private readonly paymentLinkApp: PaymentLinksApplication) {}
|
||||
|
||||
@Get('/:paymentLinkId/invoice')
|
||||
@ApiOperation({
|
||||
summary: 'Get payment link public metadata',
|
||||
description: 'Retrieves public metadata for an invoice payment link',
|
||||
})
|
||||
@ApiParam({
|
||||
name: 'paymentLinkId',
|
||||
description: 'The ID of the payment link',
|
||||
type: 'string',
|
||||
required: true,
|
||||
})
|
||||
@ApiResponse({
|
||||
status: 200,
|
||||
description: 'Successfully retrieved payment link metadata',
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
data: {
|
||||
type: 'object',
|
||||
description: 'Payment link metadata',
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
@ApiResponse({ status: 404, description: 'Payment link not found' })
|
||||
public async getPaymentLinkPublicMeta(
|
||||
@Param('paymentLinkId') paymentLinkId: string,
|
||||
) {
|
||||
const data = await this.paymentLinkApp.getInvoicePaymentLink(paymentLinkId);
|
||||
|
||||
return { data };
|
||||
}
|
||||
|
||||
@Get('/:paymentLinkId/stripe_checkout_session')
|
||||
@ApiOperation({
|
||||
summary: 'Create Stripe checkout session',
|
||||
description: 'Creates a Stripe checkout session for an invoice payment link',
|
||||
})
|
||||
@ApiParam({
|
||||
name: 'paymentLinkId',
|
||||
description: 'The ID of the payment link',
|
||||
type: 'string',
|
||||
required: true,
|
||||
})
|
||||
@ApiResponse({
|
||||
status: 200,
|
||||
description: 'Successfully created Stripe checkout session',
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: {
|
||||
type: 'string',
|
||||
description: 'Stripe checkout session ID',
|
||||
},
|
||||
url: {
|
||||
type: 'string',
|
||||
description: 'Stripe checkout session URL',
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
@ApiResponse({ status: 404, description: 'Payment link not found' })
|
||||
public async createInvoicePaymentLinkCheckoutSession(
|
||||
@Param('paymentLinkId') paymentLinkId: string,
|
||||
) {
|
||||
const session =
|
||||
await this.paymentLinkApp.createInvoicePaymentCheckoutSession(
|
||||
paymentLinkId,
|
||||
);
|
||||
return session;
|
||||
}
|
||||
|
||||
@Get('/:paymentLinkId/invoice/pdf')
|
||||
@ApiOperation({
|
||||
summary: 'Get payment link invoice PDF',
|
||||
description: 'Retrieves the PDF of the invoice associated with a payment link',
|
||||
})
|
||||
@ApiParam({
|
||||
name: 'paymentLinkId',
|
||||
description: 'The ID of the payment link',
|
||||
type: 'string',
|
||||
required: true,
|
||||
})
|
||||
@ApiResponse({
|
||||
status: 200,
|
||||
description: 'Successfully retrieved invoice PDF',
|
||||
content: {
|
||||
'application/pdf': {
|
||||
schema: {
|
||||
type: 'string',
|
||||
format: 'binary',
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
@ApiResponse({ status: 404, description: 'Payment link or invoice not found' })
|
||||
public async getPaymentLinkInvoicePdf(
|
||||
@Param('paymentLinkId') paymentLinkId: string,
|
||||
@Res() res: Response,
|
||||
) {
|
||||
const [pdfContent, filename] =
|
||||
await this.paymentLinkApp.getPaymentLinkInvoicePdf(paymentLinkId);
|
||||
|
||||
res.set({
|
||||
'Content-Type': 'application/pdf',
|
||||
'Content-Length': pdfContent.length,
|
||||
'Content-Disposition': `attachment; filename="${filename}"`,
|
||||
});
|
||||
res.send(pdfContent);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { CreateInvoiceCheckoutSession } from './CreateInvoiceCheckoutSession';
|
||||
import { GetPaymentLinkInvoicePdf } from './GetPaymentLinkInvoicePdf';
|
||||
import { PaymentLinksApplication } from './PaymentLinksApplication';
|
||||
import { PaymentLinksController } from './PaymentLinks.controller';
|
||||
import { InjectSystemModel } from '../System/SystemModels/SystemModels.module';
|
||||
import { PaymentLink } from './models/PaymentLink';
|
||||
import { StripePaymentModule } from '../StripePayment/StripePayment.module';
|
||||
import { SaleInvoicesModule } from '../SaleInvoices/SaleInvoices.module';
|
||||
import { GetInvoicePaymentLinkMetadata } from './GetInvoicePaymentLinkMetadata';
|
||||
import { TenancyContext } from '../Tenancy/TenancyContext.service';
|
||||
|
||||
const models = [InjectSystemModel(PaymentLink)];
|
||||
|
||||
@Module({
|
||||
imports: [StripePaymentModule, SaleInvoicesModule],
|
||||
providers: [
|
||||
...models,
|
||||
TenancyContext,
|
||||
CreateInvoiceCheckoutSession,
|
||||
GetPaymentLinkInvoicePdf,
|
||||
PaymentLinksApplication,
|
||||
GetInvoicePaymentLinkMetadata,
|
||||
],
|
||||
controllers: [PaymentLinksController],
|
||||
exports: [...models, PaymentLinksApplication],
|
||||
})
|
||||
export class PaymentLinksModule {}
|
||||
@@ -0,0 +1,51 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { GetInvoicePaymentLinkMetadata } from './GetInvoicePaymentLinkMetadata';
|
||||
import { CreateInvoiceCheckoutSession } from './CreateInvoiceCheckoutSession';
|
||||
import { GetPaymentLinkInvoicePdf } from './GetPaymentLinkInvoicePdf';
|
||||
import { StripeInvoiceCheckoutSessionPOJO } from '../StripePayment/StripePayment.types';
|
||||
|
||||
@Injectable()
|
||||
export class PaymentLinksApplication {
|
||||
constructor(
|
||||
private readonly getInvoicePaymentLinkMetadataService: GetInvoicePaymentLinkMetadata,
|
||||
private readonly createInvoiceCheckoutSessionService: CreateInvoiceCheckoutSession,
|
||||
private readonly getPaymentLinkInvoicePdfService: GetPaymentLinkInvoicePdf,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Retrieves the invoice payment link.
|
||||
* @param {string} paymentLinkId
|
||||
* @returns {}
|
||||
*/
|
||||
public getInvoicePaymentLink(paymentLinkId: string) {
|
||||
return this.getInvoicePaymentLinkMetadataService.getInvoicePaymentLinkMeta(
|
||||
paymentLinkId,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the invoice payment checkout session from the given payment link id.
|
||||
* @param {string} paymentLinkId - Payment link id.
|
||||
* @returns {Promise<StripeInvoiceCheckoutSessionPOJO>}
|
||||
*/
|
||||
public createInvoicePaymentCheckoutSession(
|
||||
paymentLinkId: string,
|
||||
): Promise<StripeInvoiceCheckoutSessionPOJO> {
|
||||
return this.createInvoiceCheckoutSessionService.createInvoiceCheckoutSession(
|
||||
paymentLinkId,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the sale invoice pdf of the given payment link id.
|
||||
* @param {number} paymentLinkId
|
||||
* @returns {Promise<Buffer> }
|
||||
*/
|
||||
public getPaymentLinkInvoicePdf(
|
||||
paymentLinkId: string,
|
||||
): Promise<[Buffer, string]> {
|
||||
return this.getPaymentLinkInvoicePdfService.getPaymentLinkInvoicePdf(
|
||||
paymentLinkId,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import { SystemModel } from '@/modules/System/models/SystemModel';
|
||||
import { Model } from 'objection';
|
||||
|
||||
export class PaymentLink extends SystemModel {
|
||||
public id!: number;
|
||||
public tenantId!: number;
|
||||
public resourceId!: number;
|
||||
public resourceType!: string;
|
||||
public linkId!: string;
|
||||
public publicity!: string;
|
||||
public expiryAt!: Date;
|
||||
|
||||
// Timestamps
|
||||
public createdAt!: Date;
|
||||
public updatedAt!: Date;
|
||||
|
||||
/**
|
||||
* Table name.
|
||||
* @returns {string}
|
||||
*/
|
||||
static get tableName() {
|
||||
return 'payment_links';
|
||||
}
|
||||
|
||||
/**
|
||||
* Timestamps columns.
|
||||
* @returns {string[]}
|
||||
*/
|
||||
static get timestamps() {
|
||||
return ['createdAt', 'updatedAt'];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user