paint-brush
如何使用 NodeJS 和 Foxit PDF SDK 创建 PDF 发票 Web 应用程序经过@foxitsoftware
8,192 讀數
8,192 讀數

如何使用 NodeJS 和 Foxit PDF SDK 创建 PDF 发票 Web 应用程序

经过 Foxit Software9m2023/06/23
Read on Terminal Reader

太長; 讀書

数字发票已成为各个行业的普遍做法。 Web 应用程序开发人员经常面临以编程方式生成和发送 PDF 发票的任务。福昕的 __PDF 库为生成 PDF 文件提供了快速、安全的解决方案。在本教程中,我们将引导您完成构建 Web 应用程序的过程,该应用程序使您的计费部门能够有效管理未付发票。
featured image - 如何使用 NodeJS 和 Foxit PDF SDK 创建 PDF 发票 Web 应用程序
Foxit Software HackerNoon profile picture
0-item
1-item


生成付款发票是任何企业的一个重要方面,数字发票已成为各个行业的常见做法。在这种情况下,Web 应用程序开发人员经常面临以编程方式生成和发送 PDF 发票的任务。


无论您是自动化发票生成和通知流程,还是开发图形用户界面 (GUI) 来主动提醒客户未结发票,最初的技术挑战都在于生成 PDF 发票。虽然您可以开发用于 PDF 生成的自定义脚本,但这将是一项艰巨的任务。虽然基于网络的服务很方便,但如果您与客户签订了保密协议,则它们可能不适合,因为通过互联网将数据发送到第三方服务可能会引起担忧。


幸运的是,福昕PDF库提供快速、安全的 PDF 文件生成解决方案。通过利用他们的HTML 到 PDF 转换器,您可以将任何 HTML 文档(包括发票)转换为 PDF 文件,该文件可以附加到电子邮件中或供客户从您的 Web 应用程序下载。


本教程将指导您完成创建Node.js利用的应用程序福昕PDF SDK在 Web 应用程序中从 HTML 发票生成 PDF 发票。生成后,您将使用节点邮件程序通过 SMTP 将发票发送到客户的电子邮件地址。您可以按照下面概述的步骤操作或访问 Foxit 的 GitHub 存储库上的完整代码库


构建 Web 应用程序来创建和发送 PDF 发票

在本教程中,我们将引导您完成构建 Web 应用程序的过程,该应用程序使您的计费部门能够有效地管理未付发票。您将创建各种功能,包括用于跟进的内部工具、显示未结发票的页面以及每个发票的预览页面。用户可以选择向客户发送电子邮件提醒并附上发票。


对于这个项目,我们将利用表达网络框架,纯CSS用于造型,以及节点邮件程序用于电子邮件功能。


先决条件:


创建新的 Express 应用程序

要创建新的样板 Express Web 应用程序,您需要使用应用程序生成器


 npx express-generator --git --view=hbs


这将创建一个带有.gitignore文件的 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); ...


此应用程序中的大部分工作都在发票路由器内完成。


在创建路线之前,您需要一些可以使用的数据。在实际应用程序中,您可能会连接到数据库,但出于演示目的,请将发票数据添加到 JSON 文件中。


/data/invoices.json创建一个新文件并添加以下内容:


 [ { "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 } ]


您在上面看到的三张发票包含客户、计划和账单数据,这些数据将帮助您在下一部分中生成发票。


创建发票路线

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.hbsinvoice-single.hbs


另外,将以下内容添加到您的invoice-list.hbs文件中:


 <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.hbs文件并添加以下内容:


 <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>


将样式添加到应用程序的样式表并合并纯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/[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>


打开应用程序的public/style.css file并添加以下 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; }


虽然不是强制性的,但向发票添加样式可以增强其专业外观,因为福昕在生成 PDF 时会捕获 HTML 文档中的所有样式。

现在,您已准备好测试您的应用程序。请在命令行界面中run npm start 并打开 Web 浏览器到localhost:3000/invoices您将看到类似于以下内容的发票列表:


单击“查看”可预览每张发票。



在最后两个步骤中,您将使用 Foxit__ HTML 到 PDF 工具__ 生成 PDF 发票。生成发票后,您将在发送之前使用 Nodemailer 将其附加到电子邮件中。


使用 Foxit 生成 PDF

Foxit的SDK提供了一系列用于PDF创建和操作的功能,包括从HTML文档或URL生成PDF文件的能力。下载 HTML 并将其编译为 PDF 可执行文件的过程可用这里。从命令行成功执行演示后,您可以继续执行后续步骤。


Node 的child_process库包含一个名为exec()的函数,使您能够执行命令行函数。此方法对于从 Foxit 运行 C++ 可执行文件很有用。要执行 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} -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'); } }); });


在运行此代码之前,请确保更新htmlToPdfPath变量以反映htmltopdf可执行文件的正确路径。


要发送发票电子邮件提醒,请返回发票列表,然后单击任何特定发票的“电子邮件提醒”。此操作将触发 Node 应用程序调用htmltopdf可执行文件。反过来,可执行文件会将 Express 提供的 HTML 文档中的发票转换为 PDF 文件。您可以在 Web 应用程序的invoices/目录中找到生成的 PDF 文件。


现在您已经能够生成 PDF 发票了,最后一步是将这些发票发送给您的客户。

使用 Nodemailer 发送电子邮件

节点邮件程序提供方便的界面,允许您访问各种电子邮件传输层。 SMTP 是最常用的选项之一,但您也可以使用 Amazon SES 等服务或服务器的sendmail命令作为替代方案。


要测试 Nodemailer,您可以使用流传输的 JSON 选项,它允许您将消息记录到控制台。要设置消息并使用 Nodemailer 发送,请在/invoices/:id/email路径中的“PDF generated and save to...” console.log语句下方添加以下代码片段:


 ... // 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'); } }); ...


请刷新您的节点应用程序,然后单击任何发票的“电子邮件提醒”。这次,您将观察到在控制台中显示为 JSON 的完整电子邮件数据对象。


 { "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]>" }


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 凭据替换为生产邮件服务器。我们的网络应用程序现在能够根据我们的计费团队的要求生成 PDF 发票并自动发送给客户。


如果您需要在线提供发票并将其作为 PDF 发送的解决方案,我们的应用程序可以提供可靠的基础。虽然福昕的 HTML 转 PDF 工具是生成 PDF 的便捷高效的选项,但值得注意的是,它们还提供各种其他解决方案。和他们的适用于多个平台的软件开发套件 (SDK) ,将 PDF 功能集成到您的 Web、移动或桌面应用程序中时,Foxit 是理想的选择。


也发布在这里