Generating payment invoices is a crucial aspect of any business, and digital invoicing has become a common practice across various industries. In this context, web application developers often face the task of programmatically generating and sending PDF invoices.
Whether you’re automating the Invoice generation and notification process or developing a graphical user Interface (GUI) to proactively remind clients about outstanding Invoices, the initial technical challenge lies in generating PDF Invoices. While you could develop a custom script for PDF generation, that would be a significant undertaking. While web-based services are convenient, they may not be suitable if you have confidentiality agreements with your clients since sending data to a third-party service over the internet can pose concerns.
Fortunately, Foxit’s
This tutorial will guide you through the process of creating a
In this tutorial, we will walk you through the process of building a web application that enables your billing department to manage unpaid invoices effectively. You will create various features, including an internal tool for follow-up, a page displaying outstanding invoices, and a preview page for each Invoice. Users will have the option to send email reminders to clients with the attached Invoice.
For this project, we will be utilizing the
Prerequisites:
To create a new boilerplate Express web application, you need to use the app generator:
npx express-generator --git --view=hbs
This will create a web app with a.gitignore
file and
Next, you need to add the Nodemailer npm package and install Express’ dependencies:
npm i nodemailer && npm i
The default application generated by Express provides you with two route files: /routes/index.js
and /routes/users.js
. Remove the users.js
route and create a new route file called invoices.js
. Add this new route to your app.js
file and remove the usersRoute
:
...
var indexRouter = require('./routes/index');
var invoicesRouter = require('./routes/invoices');
var app = express();
...
app.use('/', indexRouter);
app.use('/invoices', invoicesRouter);
...
The bulk of the work in this application is within the invoices router.
Before you create the route, you’ll need some data you can use. In a real application, you’ll likely connect to a database, but for demo purposes, add your invoice data to a JSON file.
Create a new file at /data/invoices.json
and add the below:
[
{
"id": "47427759-9362-4f8e-bfe4-2d3733534e83",
"customer": "Bins and Sons",
"contact_name": "Verne McKim",
"contact_email": "[email protected]",
"address": "3 Burning Wood Street",
"city_state": "Memphis, TN 38118",
"plan_id": "41595-5514",
"plan_name": "Starter",
"subtotal": 499.99,
"fee": 50.00,
"total": 549.99
},
{
"id": "1afdd2fa-6353-437c-a923-e43baac506f4",
customer": "Koepp Group",
"contact_name": "Junia Pretious",
"contact_email": "[email protected]",
"address": "7170 Fairfield Hill",
"city_state": "Los Angeles, CA 90026",
"plan_id": "43419-355",
"plan_name": "Professional",
"amount": 999.99,
"fee": 50.00,
"total": 1049.99
},
{
"id": "59c216f8-7471-4ec2-a527-ab3641dc49aa",
"customer": "Lynch-Bednar",
"contact_name": "Evelin Stollenberg",
"contact_email": "[email protected]",
"address": "9951 Erie Place",
"city_state": "Chicago, IL 60605",
"plan_id": "63323-714",
"plan_name": "Starter",
"amount": 499.99,
"fee": 50.00,
"total": 549.99
}
]
The three invoices you can see above contain customer, plan, and billing data which will help you generate an invoice in the next section.
The routes/invoices.js
file will create three new routes within your application:
/invoices
– A list of all the invoices from the flat data file above./invoices/:id
– An invoice preview so users can see what the invoice will look like before they send it to the client./invoices/:id/email
– An endpoint that generates and sends the PDF invoice to the contact email which is on file.
Open the invoices.js
file and add the following code snippet to define the first two routes:
const express = require('express');
const router = express.Router();
const invoices = require('../data/invoices.json');
// Import exec to run the Foxit HTML to PDF executable
const { exec } = require('child_process');
// Import nodemailer to send emails
const nodemailer = require('nodemailer');
router.get('/', function(req, res) {
res.render('invoice-list', {
invoices: invoices,
// Accepts errors and successes as query string arguments
success: req.query['success'],
error: req.query['error'],
});
});
router.get('/:id', function(req, res) {
const invoice = invoices.find(invoice => invoice.id === req.params['id']);
// If the invoice doesn't exist, redirect the user back to the list page
if (!invoice) {
res.redirect('/invoices');
}
// Make the date format pretty
const date = new Date().toLocaleDateString("en", {
year:"numeric",
day:"2-digit",
month:"2-digit",
});
res.render('invoice-single', { invoice, date });
});
router.get('/:id/email', function(req, res) {
// Coming soon.
});
module.exports = router;
Your application is nearly ready to test, but you need to create the two view files first.
Express separates logic and presentation into routes/
and views/
. Now add two new files to the views/
directory: invoice-list.hbs
and invoice-single.hbs
.
Also, add the following to your invoice-list.hbs
file:
<h1><a href="/invoices">Unpaid Invoices</a></h1>
{{#if success}}
<p class="success"><strong>Success!</strong> The invoice has been sent to the client.</p>
{{/if}}
{{#if error}}
<p class="error"><strong>Whoops!</strong> Something went wrong and your invoice could not be sent.</p>
{{/if}}
{{#each invoices}}
<h3>{{this.customer}}</h3>
<p>ID: {{this.id}} <br/>
<a href="/invoices/{{this.id}}">View</a> | <a href="/invoices/{{this.id}}/email">Email Reminder</a>
</p>
{{/each}}
Open the invoice-single.hbs
file and add this:
<div class="pure-g">
<div class="pure-u-1-2">
<h1>Invoice</h1>
</div>
<div class="pure-u-1-2" style="text-align: right;">
<p class="muted">Issued on {{ date }}</p>
</div>
</div>
<div class="pure-g">
<div class="pure-u-1-2">
<h3>Provider</h3>
<p>
<strong>Tiller, Inc.</strong><br/>
1255 S. Clark<br/>
Chicago, IL 60608
</p>
</div>
<div class="pure-u-1-2" style="text-align: right;">
<h3>Billed to</h3>
<p>
<strong>{{invoice.customer}}</strong><br/>
{{invoice.contact_name}}<br/>
{{invoice.address}}<br/>
{{invoice.city_state}}
</p>
</div>
</div>
<table class="pure-table pure-table-horizontal">
<thead>
<tr>
<th>ID</th>
<th>Plan Name</th>
<th class="text-right">Amount</th>
</tr>
</thead>
<tbody>
<tr>
<td>{{invoice.plan_id}}</td>
<td>{{invoice.plan_name}}</td>
<td class="text-right">${{invoice.subtotal}}</td>
</tr>
<tr>
<td></td>
<td class="text-right">Subtotal:</td>
<td class="text-right">${{invoice.subtotal}}</td>
</tr>
<tr>
<td></td>
<td class="text-right">Taxes and Fees:</td>
<td class="text-right">${{invoice.fee}}</td>
</tr>
<tr class="bold">
<td></td>
<td class="text-right">Total:</td>
<td class="text-right">${{invoice.total}}</td>
</tr>
</tbody>
</table>
<div class="footer">
<p>Please make checks payable to <strong>Tiller, Inc</strong>. Invoices are due 30 days after date issued.</p>
<p>Thank you for your business!</p>
</div>
To add styles to your application’s stylesheet and incorporate the views/layout.hbs
file and replace its contents with the following code snippet. This will import Pure and create a single column grid layout:
<!DOCTYPE html>
<html>
<head>
<title>{{title}}</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="https://unpkg.com/[email protected]/build/pure-min.css" integrity="sha384-cg6SkqEOCV1NbJoCu11+bm0NvBRc8IYLRGXkmNrqUBfTjmMYwNKPWBTIKyw9mHNJ" crossorigin="anonymous">
<link rel='stylesheet' href='/stylesheets/style.css' />
</head>
<body>
<div class="container">
<div class="pure-g">
<div class="pure-u-1">
{{{body}}}
</div>
</div>
</div>
</body>
</html>
Open your application’spublic/style.css file
and add the following CSS code snippet:
body {
background-color: #f7f7f7;
color: #333333;
}
a {
color: #156d6a;
}
h1 a,
h2 a,
h3 a {
text-decoration: none;
}
table {
width: 100%;
}
.container {
background-color: #ffffff;
max-width: 700px;
margin: 0 auto;
padding: 30px;
}
.muted {
color: #999999;
}
.bold {
font-weight: bold;
}
.text-right {
text-align: right;
}
.footer p {
margin-top: 30px;
}
.success {
background-color: #c0f5f3;
color: #0d928d;
padding: 10px;
}
.error {
background-color: #f5c0c0;
color: #792525;
padding: 10px;
}
While it is not mandatory, adding styles to your invoices can enhance their professional appearance, as Foxit captures all the styling from your HTML document when generating PDFs.
Now, you are ready to test your application. Please run npm
start in your command line interface and open your web browser to localhost:3000/invoices
You will be presented with a list of invoices similar to the following:
Click on “View” to preview each invoice.
In the final two steps, you will utilize the Foxit__HTML to PDF tool__ to generate PDF invoices. Once the invoices are generated, you will then proceed to attach them to an email using Nodemailer before sending them.
Foxit’s SDK offers a range of functionalities for PDF creation and manipulation, including the ability to generate a PDF file from an HTML document or URL. The process of downloading and compiling the HTML to PDF executable is available
Node’s child_process
library includes a function called exec()
that enables you to execute command-line functions. This method is useful for running C++ executables from Foxit. To execute the HTML to PDF executable, please update your /:id/email
route with the following code:
...
router.get('/:id/email', function(req, res) {
// Set the executable path and output folder
const htmlToPdfPath = '/path/to/foxit/html2pdf';
const outputFolder = __dirname + '/../invoices/';
// Get the invoice
const invoice = invoices.find(invoice => invoice.id === req.params['id']);
if (!invoice) {
res.redirect('/invoices?error=1');
}
// Convert the HTML to PDF
exec(
`${htmlToPdfPath} -html /invoices/${req.params['id']} -o ${outputFolder}${req.params['id']}.pdf`,
(err, stdout, stderr) => {
if (err || stderr) {
console.error(err, stderr);
res.redirect('/invoices?error=1');
} else {
// For now: log the output file path
console.log(`PDF generated and saved to ${outputFolder}${req.params['id']}.pdf`);
res.redirect('/invoices?success=1');
}
});
});
Before running this code, please ensure that you update the htmlToPdfPath
variable to reflect the correct path to your htmltopdf
executable.
To send an email reminder for an invoice, please go back to your list of invoices and click on “Email Reminder” for any specific invoice. This action will trigger the Node app to call the htmltopdf
executable. The executable, in turn, will convert the invoice from the HTML document served by Express into a PDF file. You can locate the resulting PDF file in the invoices/
directory of your web application.
Now that you have the ability to generate PDF Invoices, the final step is to send these invoices to your customers.
sendmail
command as alternatives.
To test Nodemailer, you can utilize the console.log
statement in your /invoices/:id/email
route:
...
// Construct the message
const message = {
from: '[email protected]',
to: invoice.contact_email,
subject: 'Reminder: Your Invoice from Tiller, Inc. is Due',
html: `<p>Hey ${invoice.contact_name},</p><p>I just wanted to remind you that your invoice for last month's services is now due. I've attached it here for your convenience.</p><p>Thanks for your business!</p>`,
attachments: [
{
filename: 'invoice.pdf',
path: `${outputFolder}${req.params['id']}.pdf`,
}
]
};
// Use mailer to send invoice
nodemailer
.createTransport({jsonTransport: true})
.sendMail(message, function (err, info) {
if (err) {
res.redirect('/invoices?error=1');
} else {
console.log(info.message);
res.redirect('/invoices?success=1');
}
});
...
Please refresh your Node application and click on “Email Reminder” for any of the invoices. This time, you will observe the complete email data object displayed as JSON in your console.
{
"from": {
"address": "[email protected]",
"name": ""
},
"to": [
{
"address": "[email protected]",
"name": ""
}
],
"subject": "Reminder: Your Invoice from Tiller, Inc. is Due",
"html": "<p>Hey Junia Pretious,</p><p>I just wanted to remind you that your invoice for last month's services is now due. I've attached it here for your convenience.</p><p>Thanks for your business!</p>",
"attachments": [
{
"content": "JVBERi0xLjMKJcTl8uXrp...",
"filename": "invoice.pdf",
"contentType": "application/pdf",
"encoding": "base64"
}
],
"headers": {},
"messageId": "<[email protected]>"
}
Theattachments.content
string represents the encoded PDF file. For the sake of brevity, I have truncated it in the above example.
To test the email using a real createTransport({jsonTransport: true})
call with the following code snippet:
createTransport({
host: "smtp.mailtrap.io",
port: 2525,
auth: {
user: "<YOUR_MAILTRAP_USERID>",
pass: "<YOUR_MAILTRAP_PASS>"
}
})
Upon emailing the Invoice, Mailtrap will capture the output and provide you with the option to download the PDF attachment. If you access your Mailtrap account, you will find an email similar to the example below:
Once you’re ready to deploy your app to production, replace the Mailtrap SMTP credentials with a production mail server. Our web application now offers the capability to generate and automatically send PDF Invoices to clients upon request from our billing team.
If you require a solution for presenting Invoices online and sending them as PDFs, our application provides a reliable foundation. While Foxit’s HTML to PDF tool is a convenient and efficient option for generating PDFs, it’s important to note that they offer various other solutions as well. With their
Also published here.