mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-17 21:30:31 +00:00
Merge pull request #689 from bigcapitalhq/download-payment-link-invoice-pdf
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.validationResult
|
||||
);
|
||||
router.get(
|
||||
'/:paymentLinkId/invoice/pdf',
|
||||
[param('paymentLinkId').exists()],
|
||||
this.validationResult,
|
||||
this.getPaymentLinkInvoicePdf.bind(this),
|
||||
this.validationResult
|
||||
);
|
||||
router.post(
|
||||
'/:paymentLinkId/stripe_checkout_session',
|
||||
[param('paymentLinkId').exists()],
|
||||
@@ -80,4 +87,31 @@ export class PublicSharableLinkController extends BaseController {
|
||||
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 { CreateInvoiceCheckoutSession } from './CreateInvoiceCheckoutSession';
|
||||
import { StripeInvoiceCheckoutSessionPOJO } from '@/interfaces/StripePayment';
|
||||
import { GetPaymentLinkInvoicePdf } from './GetPaymentLinkInvoicePdf';
|
||||
|
||||
@Service()
|
||||
export class PaymentLinksApplication {
|
||||
@@ -10,6 +11,9 @@ export class PaymentLinksApplication {
|
||||
|
||||
@Inject()
|
||||
private createInvoiceCheckoutSessionService: CreateInvoiceCheckoutSession;
|
||||
|
||||
@Inject()
|
||||
private getPaymentLinkInvoicePdfService: GetPaymentLinkInvoicePdf;
|
||||
|
||||
/**
|
||||
* Retrieves the invoice payment link.
|
||||
@@ -34,4 +38,16 @@ export class PaymentLinksApplication {
|
||||
paymentLinkId
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the sale invoice pdf of the given payment link id.
|
||||
* @param {number} tenantId
|
||||
* @param {number} paymentLinkId
|
||||
* @returns {Promise<Buffer> }
|
||||
*/
|
||||
public getPaymentLinkInvoicePdf(paymentLinkId: string): Promise<Buffer> {
|
||||
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 { AppToaster, Box, Group, Stack } from '@/components';
|
||||
import { usePaymentPortalBoot } from './PaymentPortalBoot';
|
||||
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 { downloadFile } from '@/hooks/useDownloadFile';
|
||||
import styles from './PaymentPortal.module.scss';
|
||||
|
||||
export function PaymentPortal() {
|
||||
@@ -15,10 +19,34 @@ export function PaymentPortal() {
|
||||
isLoading: isStripeCheckoutLoading,
|
||||
} = useCreateStripeCheckoutSession();
|
||||
|
||||
const {
|
||||
mutateAsync: generatePaymentLinkInvoice,
|
||||
isLoading: isInvoiceGenerating,
|
||||
} = useGeneratePaymentLinkInvoicePdf();
|
||||
|
||||
// Handles invoice preview button click.
|
||||
const handleInvoicePreviewBtnClick = () => {
|
||||
openDrawer(DRAWERS.PAYMENT_INVOICE_PREVIEW);
|
||||
};
|
||||
|
||||
// Handles invoice download button click.
|
||||
const handleInvoiceDownloadBtnClick = () => {
|
||||
generatePaymentLinkInvoice({ paymentLinkId: linkId })
|
||||
.then((data) => {
|
||||
downloadFile(
|
||||
data,
|
||||
`Invoice ${sharableLinkMeta?.invoiceNo}`,
|
||||
'application/pdf',
|
||||
);
|
||||
})
|
||||
.catch(() => {
|
||||
AppToaster.show({
|
||||
intent: Intent.DANGER,
|
||||
message: 'Something went wrong.',
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// handles the pay button click.
|
||||
const handlePayButtonClick = () => {
|
||||
createStripeCheckoutSession({ linkId })
|
||||
@@ -125,6 +153,8 @@ export function PaymentPortal() {
|
||||
<Button
|
||||
minimal
|
||||
className={clsx(styles.footerButton, styles.downloadInvoiceButton)}
|
||||
onClick={handleInvoiceDownloadBtnClick}
|
||||
loading={isInvoiceGenerating}
|
||||
>
|
||||
Download Invoice
|
||||
</Button>
|
||||
|
||||
@@ -11,6 +11,7 @@ import useApiRequest from '../useRequest';
|
||||
import { transformToCamelCase, transfromToSnakeCase } from '@/utils';
|
||||
|
||||
const GetPaymentLinkInvoice = 'GetPaymentLinkInvoice';
|
||||
const GetPaymentLinkInvoicePdf = 'GetPaymentLinkInvoicePdf';
|
||||
|
||||
// Create Payment Link
|
||||
// ------------------------------------
|
||||
@@ -170,3 +171,62 @@ export const useCreateStripeCheckoutSession = (
|
||||
{ ...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`, {
|
||||
responseType: 'blob',
|
||||
headers: { accept: 'application/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`, {
|
||||
responseType: 'blob',
|
||||
headers: { accept: 'application/pdf' },
|
||||
})
|
||||
.then((res) => res.data),
|
||||
{
|
||||
...options,
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
@@ -33,9 +33,15 @@ export const useDownloadFile = (args: IArgs) => {
|
||||
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 blob = new Blob(blobData, { type: mime || 'application/octet-stream' });
|
||||
var blob = new Blob(blobData, { type: mime });
|
||||
|
||||
if (typeof window.navigator.msSaveBlob !== 'undefined') {
|
||||
// IE workaround for "HTML7007: One or more blob URLs were
|
||||
// revoked by closing the blob for which they were created.
|
||||
|
||||
Reference in New Issue
Block a user