Skip to main content
Version: 3.x

Proxyfing invoices

Customers can have access to their invoices from their accounts. By default, if the integration supports it, invoices can be accessed from order pages and have an online HTML version with a printable-friendly theme. This guide explains how to adapt the feature to your needs.

In a project, you may want to change the default source to retrieve invoices or prefer to provide a downloadable PDF document for them. This section documents how that could be achieved.

info

This guide will help you to reproduce the pdf-invoice example extension.

Feel free to use this example as a starting point to implement your own solution.

Front-Commerce Invoice

The front-commerce/invoice GraphQL module defines shared interfaces to represent Invoices:

  • FcInvoice: a generic invoice entity
  • FcDownloadableInvoice: invoices that can be downloaded by the Customer (implements FcInvoice)
  • FcDisplayableInvoice: invoices that can be displayed on the web by the Customer (implements FcInvoice)

Replace default invoices with downloadable invoice files

We will illustrate how one can replace the default Front-Commerce implementation to provide PDF files for invoices. It will highlight the existing extension points that might be useful to implement any other specific use cases you may face.

Customizing how invoices are resolved in your application consists in 3 steps:

  • adding a new Invoice type to the graphql schema
  • overriding the Order.invoices resolver to fetch invoices from your endpoint
  • making the file downloadable for the Customer

Add your new Invoice type to the graphql schema

This section assumes that you know how to Extend the GraphQL schema.

Since the invoice is aimed at being downloadable, the new invoice type will have to implement the FcDownloadableInvoice interface.

extensions/pdf-invoice/graphql/index.ts
import { createGraphQLModule } from "@front-commerce/core/graphql";

export default createGraphQLModule({
// ... rest of the module
typeDefs: /* GraphQL */ `
type AcmeInvoice implements FcInvoice {
id: ID
}

type AcmeInvoice implements FcDownloadableInvoice {
id: ID
download: DownloadLink
}
`,
// ... rest of the module
});

Fetch invoices from your endpoint

Override the Order.invoices resolver by providing a list coming from your own API (there can be multiple invoices for each order). In this example, we consider that all invoices are AcmeInvoice but Front-Commerce supports heterogeneous types:

extensions/pdf-invoice/graphql/resolvers.ts
export default {
Order: {
invoices: ({ entity_id }, _, { loaders }) => {
return loaders.AdminInvoice.loadByOrderId(entity_id);
},
},
FcInvoice: {
__resolveType: ({ __typename }) => {
return "AcmeInvoice";
},
},
AcmeInvoice: {
download: ({ id, entity_id }, _, { loaders }) => {
return loaders.AcmeInvoice.loadPdf(entity_id);
// should return { name: "The downloaded file name.pdf", url: "/invoices/fileXXX.pdf" }
},
},
};

Make invoice files downloadable for Customers

You must implement a custom route to expose the PDF file on a URL. The implementation of this route is up to you and depends on your context.

It might be a simple proxy, but if your remote data source does not implement access control you may have to roll your own authorization checks to ensure that Customers cannot access invoices that don't belong to them.

Here is an example of a custom route that proxy a Magento remote url with additional checks:

extensions/pdf-invoice/routes/invoices.download.$id.tsx
import { FrontCommerceApp } from "@front-commerce/remix";
import { type LoaderFunctionArgs } from "@remix-run/node";

export async function loader({ params, context }: LoaderFunctionArgs) {
const app = new FrontCommerceApp(context.frontCommerce);
const magentoEndpoint = app.config.magento.endpoint;
const invoiceId = params.id;
const invoiceUrl = `${magentoEndpoint}/invoices/${invoiceId}`;
try {
const invoice = await fetch(invoiceUrl);
if (invoice.status !== 200) {
return new Response("Not found", { status: 404 });
}
const invoiceFile = await invoice.arrayBuffer();
return new Response(invoiceFile);
} catch (error) {
return new Response("Internal server error", { status: 500 });
}
}
note

When the user is not authorized or the final path does not work, it will display a 404 page instead. This is kind of for a security reason but mostly because we don't want to force people to style a new error page!