mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-21 07:10:33 +00:00
feat: Download invoice pdf of the payment link
This commit is contained in:
@@ -22,6 +22,13 @@ export class PublicSharableLinkController extends BaseController {
|
|||||||
this.getPaymentLinkPublicMeta.bind(this),
|
this.getPaymentLinkPublicMeta.bind(this),
|
||||||
this.validationResult
|
this.validationResult
|
||||||
);
|
);
|
||||||
|
router.get(
|
||||||
|
'/:paymentLinkId/invoice/pdf',
|
||||||
|
[param('paymentLinkId').exists()],
|
||||||
|
this.validationResult,
|
||||||
|
this.getPaymentLinkInvoicePdf.bind(this),
|
||||||
|
this.validationResult
|
||||||
|
);
|
||||||
router.post(
|
router.post(
|
||||||
'/:paymentLinkId/stripe_checkout_session',
|
'/:paymentLinkId/stripe_checkout_session',
|
||||||
[param('paymentLinkId').exists()],
|
[param('paymentLinkId').exists()],
|
||||||
@@ -80,4 +87,31 @@ export class PublicSharableLinkController extends BaseController {
|
|||||||
next(error);
|
next(error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the sale invoice pdf of the given payment link.
|
||||||
|
* @param {Request} req
|
||||||
|
* @param {Response} res
|
||||||
|
* @param {NextFunction} next
|
||||||
|
*/
|
||||||
|
public async getPaymentLinkInvoicePdf(
|
||||||
|
req: Request<{ paymentLinkId: string }>,
|
||||||
|
res: Response,
|
||||||
|
next: NextFunction
|
||||||
|
) {
|
||||||
|
const { paymentLinkId } = req.params;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const pdfContent = await this.paymentLinkApp.getPaymentLinkInvoicePdf(
|
||||||
|
paymentLinkId
|
||||||
|
);
|
||||||
|
res.set({
|
||||||
|
'Content-Type': 'application/pdf',
|
||||||
|
'Content-Length': pdfContent.length,
|
||||||
|
});
|
||||||
|
res.send(pdfContent);
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
import { Inject, Service } from 'typedi';
|
||||||
|
import { initalizeTenantServices } from '@/api/middleware/TenantDependencyInjection';
|
||||||
|
import { SaleInvoicePdf } from '../Sales/Invoices/SaleInvoicePdf';
|
||||||
|
import { PaymentLink } from '@/system/models';
|
||||||
|
|
||||||
|
@Service()
|
||||||
|
export class GetPaymentLinkInvoicePdf {
|
||||||
|
@Inject()
|
||||||
|
private getSaleInvoicePdfService: SaleInvoicePdf;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the sale invoice PDF of the given payment link id.
|
||||||
|
* @param {number} tenantId
|
||||||
|
* @param {number} paymentLinkId
|
||||||
|
* @returns {Promise<Buffer>}
|
||||||
|
*/
|
||||||
|
async getPaymentLinkInvoicePdf(paymentLinkId: string): Promise<Buffer> {
|
||||||
|
const paymentLink = await PaymentLink.query()
|
||||||
|
.findOne('linkId', paymentLinkId)
|
||||||
|
.where('resourceType', 'SaleInvoice')
|
||||||
|
.throwIfNotFound();
|
||||||
|
|
||||||
|
const tenantId = paymentLink.tenantId;
|
||||||
|
await initalizeTenantServices(tenantId);
|
||||||
|
|
||||||
|
const saleInvoiceId = paymentLink.resourceId;
|
||||||
|
|
||||||
|
return this.getSaleInvoicePdfService.saleInvoicePdf(
|
||||||
|
tenantId,
|
||||||
|
saleInvoiceId
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@ import { Inject, Service } from 'typedi';
|
|||||||
import { GetInvoicePaymentLinkMetadata } from './GetInvoicePaymentLinkMetadata';
|
import { GetInvoicePaymentLinkMetadata } from './GetInvoicePaymentLinkMetadata';
|
||||||
import { CreateInvoiceCheckoutSession } from './CreateInvoiceCheckoutSession';
|
import { CreateInvoiceCheckoutSession } from './CreateInvoiceCheckoutSession';
|
||||||
import { StripeInvoiceCheckoutSessionPOJO } from '@/interfaces/StripePayment';
|
import { StripeInvoiceCheckoutSessionPOJO } from '@/interfaces/StripePayment';
|
||||||
|
import { GetPaymentLinkInvoicePdf } from './GetPaymentLinkInvoicePdf';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export class PaymentLinksApplication {
|
export class PaymentLinksApplication {
|
||||||
@@ -10,6 +11,9 @@ export class PaymentLinksApplication {
|
|||||||
|
|
||||||
@Inject()
|
@Inject()
|
||||||
private createInvoiceCheckoutSessionService: CreateInvoiceCheckoutSession;
|
private createInvoiceCheckoutSessionService: CreateInvoiceCheckoutSession;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private getPaymentLinkInvoicePdfService: GetPaymentLinkInvoicePdf;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves the invoice payment link.
|
* Retrieves the invoice payment link.
|
||||||
@@ -34,4 +38,16 @@ export class PaymentLinksApplication {
|
|||||||
paymentLinkId
|
paymentLinkId
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the sale invoice pdf of the given payment link id.
|
||||||
|
* @param {number} tenantId
|
||||||
|
* @param {number} paymentLinkId
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
public getPaymentLinkInvoicePdf(paymentLinkId: string) {
|
||||||
|
return this.getPaymentLinkInvoicePdfService.getPaymentLinkInvoicePdf(
|
||||||
|
paymentLinkId
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,14 @@
|
|||||||
import { Text, Classes, Button, Intent, Tag } from '@blueprintjs/core';
|
import { Text, Classes, Button, Intent } from '@blueprintjs/core';
|
||||||
import clsx from 'classnames';
|
import clsx from 'classnames';
|
||||||
import { AppToaster, Box, Group, Stack } from '@/components';
|
import { AppToaster, Box, Group, Stack } from '@/components';
|
||||||
import { usePaymentPortalBoot } from './PaymentPortalBoot';
|
import { usePaymentPortalBoot } from './PaymentPortalBoot';
|
||||||
import { useDrawerActions } from '@/hooks/state';
|
import { useDrawerActions } from '@/hooks/state';
|
||||||
import { useCreateStripeCheckoutSession } from '@/hooks/query/payment-link';
|
import {
|
||||||
|
useCreateStripeCheckoutSession,
|
||||||
|
useGeneratePaymentLinkInvoicePdf,
|
||||||
|
} from '@/hooks/query/payment-link';
|
||||||
import { DRAWERS } from '@/constants/drawers';
|
import { DRAWERS } from '@/constants/drawers';
|
||||||
|
import { downloadFile } from '@/hooks/useDownloadFile';
|
||||||
import styles from './PaymentPortal.module.scss';
|
import styles from './PaymentPortal.module.scss';
|
||||||
|
|
||||||
export function PaymentPortal() {
|
export function PaymentPortal() {
|
||||||
@@ -15,10 +19,30 @@ export function PaymentPortal() {
|
|||||||
isLoading: isStripeCheckoutLoading,
|
isLoading: isStripeCheckoutLoading,
|
||||||
} = useCreateStripeCheckoutSession();
|
} = useCreateStripeCheckoutSession();
|
||||||
|
|
||||||
|
const {
|
||||||
|
mutateAsync: generatePaymentLinkInvoice,
|
||||||
|
isLoading: isInvoiceGenerating,
|
||||||
|
} = useGeneratePaymentLinkInvoicePdf();
|
||||||
|
|
||||||
// Handles invoice preview button click.
|
// Handles invoice preview button click.
|
||||||
const handleInvoicePreviewBtnClick = () => {
|
const handleInvoicePreviewBtnClick = () => {
|
||||||
openDrawer(DRAWERS.PAYMENT_INVOICE_PREVIEW);
|
openDrawer(DRAWERS.PAYMENT_INVOICE_PREVIEW);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Handles invoice download button click.
|
||||||
|
const handleInvoiceDownloadBtnClick = () => {
|
||||||
|
generatePaymentLinkInvoice({ paymentLinkId: linkId })
|
||||||
|
.then((data) => {
|
||||||
|
downloadFile(data, `Invoice ${sharableLinkMeta?.invoiceNo}.pdf`);
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
AppToaster.show({
|
||||||
|
intent: Intent.DANGER,
|
||||||
|
message: 'Something went wrong.',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
// handles the pay button click.
|
// handles the pay button click.
|
||||||
const handlePayButtonClick = () => {
|
const handlePayButtonClick = () => {
|
||||||
createStripeCheckoutSession({ linkId })
|
createStripeCheckoutSession({ linkId })
|
||||||
@@ -125,6 +149,8 @@ export function PaymentPortal() {
|
|||||||
<Button
|
<Button
|
||||||
minimal
|
minimal
|
||||||
className={clsx(styles.footerButton, styles.downloadInvoiceButton)}
|
className={clsx(styles.footerButton, styles.downloadInvoiceButton)}
|
||||||
|
onClick={handleInvoiceDownloadBtnClick}
|
||||||
|
loading={isInvoiceGenerating}
|
||||||
>
|
>
|
||||||
Download Invoice
|
Download Invoice
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import useApiRequest from '../useRequest';
|
|||||||
import { transformToCamelCase, transfromToSnakeCase } from '@/utils';
|
import { transformToCamelCase, transfromToSnakeCase } from '@/utils';
|
||||||
|
|
||||||
const GetPaymentLinkInvoice = 'GetPaymentLinkInvoice';
|
const GetPaymentLinkInvoice = 'GetPaymentLinkInvoice';
|
||||||
|
const GetPaymentLinkInvoicePdf = 'GetPaymentLinkInvoicePdf';
|
||||||
|
|
||||||
// Create Payment Link
|
// Create Payment Link
|
||||||
// ------------------------------------
|
// ------------------------------------
|
||||||
@@ -170,3 +171,56 @@ export const useCreateStripeCheckoutSession = (
|
|||||||
{ ...options },
|
{ ...options },
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Get Payment Link Invoice PDF
|
||||||
|
// ------------------------------------
|
||||||
|
interface GetPaymentLinkInvoicePdfResponse {}
|
||||||
|
|
||||||
|
interface GeneratePaymentLinkInvoicePdfValues {
|
||||||
|
paymentLinkId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useGeneratePaymentLinkInvoicePdf = (
|
||||||
|
options?: UseMutationOptions<
|
||||||
|
GetPaymentLinkInvoicePdfResponse,
|
||||||
|
Error,
|
||||||
|
GeneratePaymentLinkInvoicePdfValues
|
||||||
|
>,
|
||||||
|
): UseMutationResult<
|
||||||
|
GetPaymentLinkInvoicePdfResponse,
|
||||||
|
Error,
|
||||||
|
GeneratePaymentLinkInvoicePdfValues
|
||||||
|
> => {
|
||||||
|
const apiRequest = useApiRequest();
|
||||||
|
|
||||||
|
return useMutation<
|
||||||
|
GetPaymentLinkInvoicePdfResponse,
|
||||||
|
Error,
|
||||||
|
GeneratePaymentLinkInvoicePdfValues
|
||||||
|
>(
|
||||||
|
(values: GeneratePaymentLinkInvoicePdfValues) => {
|
||||||
|
return apiRequest
|
||||||
|
.get(`/payment-links/${values.paymentLinkId}/invoice/pdf`)
|
||||||
|
.then((res) => res?.data);
|
||||||
|
},
|
||||||
|
{ ...options },
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useGetPaymentLinkInvoicePdf = (
|
||||||
|
invoiceId: string,
|
||||||
|
options?: UseQueryOptions<GetPaymentLinkInvoicePdfResponse, Error>,
|
||||||
|
): UseQueryResult<GetPaymentLinkInvoicePdfResponse, Error> => {
|
||||||
|
const apiRequest = useApiRequest();
|
||||||
|
|
||||||
|
return useQuery<GetPaymentLinkInvoicePdfResponse, Error>(
|
||||||
|
[GetPaymentLinkInvoicePdf, invoiceId],
|
||||||
|
() =>
|
||||||
|
apiRequest
|
||||||
|
.get(`/payment-links/${invoiceId}/invoice/pdf`)
|
||||||
|
.then((res) => res.data),
|
||||||
|
{
|
||||||
|
...options,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|||||||
@@ -33,9 +33,15 @@ export const useDownloadFile = (args: IArgs) => {
|
|||||||
return { ...mutation };
|
return { ...mutation };
|
||||||
};
|
};
|
||||||
|
|
||||||
export function downloadFile(data, filename, mime, bom) {
|
export function downloadFile(
|
||||||
|
data,
|
||||||
|
filename,
|
||||||
|
mime = 'application/octet-stream',
|
||||||
|
bom?: any,
|
||||||
|
) {
|
||||||
var blobData = typeof bom !== 'undefined' ? [bom, data] : [data];
|
var blobData = typeof bom !== 'undefined' ? [bom, data] : [data];
|
||||||
var blob = new Blob(blobData, { type: mime || 'application/octet-stream' });
|
var blob = new Blob(blobData, { type: mime });
|
||||||
|
|
||||||
if (typeof window.navigator.msSaveBlob !== 'undefined') {
|
if (typeof window.navigator.msSaveBlob !== 'undefined') {
|
||||||
// IE workaround for "HTML7007: One or more blob URLs were
|
// IE workaround for "HTML7007: One or more blob URLs were
|
||||||
// revoked by closing the blob for which they were created.
|
// revoked by closing the blob for which they were created.
|
||||||
|
|||||||
Reference in New Issue
Block a user