如何创建 PDF 发票 Web 应用程序





5.00/5 (4投票s)
在本教程中,您将了解如何创建一个使用 Foxit PDF SDK 的 NodeJS 应用程序,该应用程序可将 Web 应用中的 HTML 发票转换为 PDF 发票。
收款是任何业务中最关键的功能之一,而数字发票正变得越来越普遍。有鉴于此,Web 应用程序开发人员经常被 T 务于以编程方式生成和发送 PDF 发票。
无论您是自动化发票生成和通知流程,还是构建一个允许您的团队主动提醒客户未付款发票的 GUI,您将面临的第一个技术障碍是生成 PDF 发票。虽然您可以编写自定义的 PDF 生成脚本,但这是一项巨大的工程。基于 Web 的服务很方便,但如果您与客户签订了保密协议,通过 Internet 向第三方服务发送数据可能会有问题。
幸运的是,Foxit 的 PDF 工具允许您快速安全地生成 PDF 文件。使用其HTML 转 PDF 转换器,您可以将任何 HTML 文档(包括发票)转换为 PDF 文件,然后您可以将其附加到电子邮件中或允许客户从您的 Web 应用程序下载。
在本教程中,您将学习如何创建一个 NodeJS 应用程序,该应用程序使用 Foxit PDF SDK 将 Web 应用中的 HTML 发票转换为 PDF 发票。创建完成后,您将使用 Nodemailer 通过 SMTP 将发票发送到客户的电子邮件地址。您可以按照下面的每个步骤操作,或者 [在 GitHub 上下载完成的代码库)。
访问 Foxit PDF SDK Web 演示,通过探索配置和功能来亲眼看看.
构建用于创建和发送 PDF 发票的 Web 应用程序
在本教程中,您将创建一个内部工具来帮助您的账单部门跟进未付款发票。您将创建一个列出所有未付款发票的页面,以及一个预览每张发票的页面。用户将能够单击一个链接,向每位客户发送带有附件发票的电子邮件提醒。
您将使用 Express Web 框架,Pure CSS 进行样式设置,以及 Nodemailer 发送电子邮件。
必备组件
- NodeJS 版本 8+ 和 NPM 版本 5+
- Foxit SDK(此处提供免费试用下载)
- Foxit HTML 转 PDF 转换加载项
- Mailtrap(如果您想测试 SMTP 电子邮件传输)
创建新的 Express 应用
要创建一个新的样板 Express Web 应用程序,请使用 应用程序生成器
npx express-generator --git --view=hbs
这将创建一个带有 .gitignore 文件和 Handlebars 模板文件的 Web 应用程序。
接下来,添加 Nodemailer npm 包并安装 Express 的依赖项
npm i nodemailer && npm i
Express 生成的默认应用程序带有两个路由文件:/routes/index.js 和 /routes/users.js。删除 users.js 路由并创建一个名为 invoices.js 的新路由文件。将此新路由添加到您的 app.js 文件中,并删除 usersRoute
...
var indexRouter = require('./routes/index');
var invoicesRouter = require('./routes/invoices');
var app = express();
...
app.use('/', indexRouter);
app.use('/invoices', invoicesRouter);
...
invoices 路由器将是您在此应用程序中完成大部分工作的地方。
在创建路由之前,您需要一些数据。在实际应用程序中,您很可能会连接到数据库,但出于演示目的,您将把发票数据添加到 JSON 文件中。
在 /data/invoices.json 创建一个新文件并添加以下内容
[ { "id": "47427759-9362-4f8e-bfe4-2d3733534e83", "customer": "Bins and Sons", "contact_name": "Verne McKim", "contact_email": "vmckim0@example.com", "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": "jpretious1@example.com", "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": "estollenberg2@example.com", "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 } ]
这三张发票包含客户、计划和账单数据,这将帮助您在下一节中生成发票。
创建发票路由
routes/invoices.js 文件将在您的应用程序中创建三个新路由
- /invoices - 来自上面平面数据文件的所有发票列表。
- /invoices/:id - 发票预览,以便用户在发送给客户之前可以看到发票的外观。
- /invoices/:id/email - 一个端点,用于生成 PDF 发票并将其发送给记录在案的联系电子邮件。
最后一个路由将在稍后处理,但您可以先添加前两个路由。打开 invoices.js 文件并添加以下内容
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;
您的应用程序已准备好进行测试,但首先,您需要创建两个视图文件。
添加视图和样式
Express 将逻辑和表示分离到 routes/ 和 views/ 中。在 views/ 目录中添加两个新文件:invoice-list.js 和 invoice-single.js。
将以下内容添加到您的 invoice-list.js 文件中
<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}}
打开 invoice-single.js 文件并添加此内容
<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>
接下来,您需要为应用程序的样式表添加样式,并加载 Pure CSS 模块使其看起来不错。打开 views/layout.hbs 文件,用以下内容替换它以导入 Pure 并创建一个单列网格布局
<!DOCTYPE html>
<html>
<head>
<title>{{title}}</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="https://unpkg.com/purecss@2.0.3/build/pure-min.css" 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>
打开应用程序的 public/style.css 文件并添加以下内容
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;
}
虽然您不必添加样式,但它会让您的发票看起来更专业,因为 Foxit 在生成 PDF 时会捕获 HTML 文档中的所有样式。
在浏览器中尝试我们的 SDK Web 演示,无需下载或登录.
此时,您已准备好测试应用程序。从命令行运行 npm start
,然后在 Web 浏览器中打开 localhost:3000/invoices。您应该会看到如下的发票列表
单击“查看”以预览每张发票
在最后两个步骤中,您将使用 Foxit HTML 转 PDF 工具在通过 Nodemailer 将它们附加到电子邮件之前生成 PDF 发票。
使用 Foxit 生成 PDF
您可以使用 Foxit 的 SDK 进行各种 PDF 创建和操作操作,但一个常见用例是从 HTML 文档或 URL 生成 PDF 文件。下载和编译 HTML 到 PDF 可执行文件的过程在此处进行了文档记录。一旦您成功从命令行运行了演示,就可以继续。
Node 的 child_process
库包含一个名为 exec()
的函数,该函数允许您执行命令行函数。这是运行用 C++ 编写的 Foxit 可执行文件的便捷方法。要运行 HTML 转 PDF 可执行文件,请更新您的 /:id/email
路由,将其替换为以下内容
...
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} -h localhost:3000/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');
}
});
});
在运行此代码之前,请确保更新 htmlToPdfPath
以指向您的 htmltopdf
可执行文件。
返回发票列表并单击任何发票上的“发送电子邮件提醒”,Node 应用程序将调用 htmltopdf
可执行文件。该可执行文件将把您的发票从 Express 提供的 HTML 文档转换为 PDF 文件。您可以在 Web 应用程序的 invoices/
目录中找到 PDF 文件。
既然您已经能够生成 PDF 发票,最后一步就是将这些发票发送给您的客户。
使用 Nodemailer 发送电子邮件
Nodemailer 提供了一个方便的接口来访问许多电子邮件传输层。SMTP 是最普遍的传输层之一,但您也可以使用 Amazon SES 或您的服务器的 sendmail
命令。
要测试 Nodemailer,您可以使用 [流传输的 JSON 选项,它允许您将消息记录到控制台。要设置消息并使用 Nodemailer 发送,请在 /invoices/:id/email
路由中的“PDF 已生成并保存到…”console.log
语句正下方添加以下内容
... // Construct the message const message = { from: 'accounting@example.com', 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'); } }); ...
刷新您的 Node 应用程序并单击任何发票上的“发送电子邮件提醒”。这一次,您将在控制台中看到整个电子邮件数据对象作为 JSON
{ "from": { "address": "accounting@example.com", "name": "" }, "to": [ { "address": "jpretious1@example.com", "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": "<65ea9109-8d5a-295e-9295-8e98e1b2c667@example.com>" }
attachments.content
字符串是您的编码 PDF 文件,因此出于简洁起见,我在上面对其进行了截断。
要使用实际的 SMTP 服务器测试电子邮件,您可以使用 Mailtrap。假设您有一个帐户,请将 createTransport({jsonTransport: true})
调用替换为以下内容
createTransport({ host: "smtp.mailtrap.io", port: 2525, auth: { user: "<YOUR_MAILTRAP_USERID>", pass: "<YOUR_MAILTRAP_PASS>" } })
现在,当您发送发票电子邮件时,Mailtrap 将捕获输出并允许您下载 PDF 附件。在您的 Mailtrap 帐户中,您应该会看到如下电子邮件
当您准备好将应用程序部署到生产环境时,请将 Mailtrap SMTP 凭据替换为生产邮件服务器。您的 Web 应用程序现在可以在您的账单团队想要跟进时生成 PDF 发票并发送给客户。
结论
如果您需要在线展示发票并将其作为 PDF 发送,以上应用程序应该能为您提供一个有用的起点。Foxit 的 HTML 转 PDF 工具是生成 PDF 的一种方便且性能良好的方法,但这并不是他们提供的唯一解决方案。
在您选择的平台(Web、Windows、Android、iOS、Linux、UWP 或 Mac)上尝试 Foxit PDF SDK 的先进技术。立即注册 免费的三十天试用。
当您需要将 PDF 支持构建到您的 Web、移动或桌面应用程序中时,Foxit 是明确的选择。