Tạo hóa đơn thanh toán là một khía cạnh quan trọng của bất kỳ doanh nghiệp nào và lập hóa đơn kỹ thuật số đã trở thành một thông lệ phổ biến trong các ngành khác nhau. Trong bối cảnh này, các nhà phát triển ứng dụng web thường phải đối mặt với nhiệm vụ tạo và gửi hóa đơn PDF theo chương trình.
Cho dù bạn đang tự động hóa quy trình tạo và thông báo Hóa đơn hay phát triển Giao diện người dùng đồ họa (GUI) để chủ động nhắc nhở khách hàng về các Hóa đơn chưa thanh toán, thách thức kỹ thuật ban đầu nằm ở việc tạo Hóa đơn PDF. Mặc dù bạn có thể phát triển tập lệnh tùy chỉnh để tạo PDF, nhưng đó sẽ là một công việc quan trọng. Mặc dù các dịch vụ dựa trên web thuận tiện nhưng chúng có thể không phù hợp nếu bạn có thỏa thuận bảo mật với khách hàng của mình vì việc gửi dữ liệu đến dịch vụ của bên thứ ba qua internet có thể gây ra mối lo ngại.
May mắn thay, Foxit's
Hướng dẫn này sẽ hướng dẫn bạn qua quá trình tạo một
Trong hướng dẫn này, chúng tôi sẽ hướng dẫn bạn quy trình xây dựng một ứng dụng web cho phép bộ phận thanh toán của bạn quản lý các hóa đơn chưa thanh toán một cách hiệu quả. Bạn sẽ tạo nhiều tính năng khác nhau, bao gồm công cụ nội bộ để theo dõi, trang hiển thị hóa đơn chưa thanh toán và trang xem trước cho từng Hóa đơn. Người dùng sẽ có tùy chọn gửi email nhắc nhở cho khách hàng bằng Hóa đơn đính kèm.
Đối với dự án này, chúng tôi sẽ sử dụng
điều kiện tiên quyết:
Để tạo một ứng dụng web bản soạn sẵn Express mới, bạn cần sử dụng trình tạo ứng dụng :
npx express-generator --git --view=hbs
Thao tác này sẽ tạo một ứng dụng web có tệp .gitignore
và
Tiếp theo, bạn cần thêm gói npm Nodemailer và cài đặt các phụ thuộc Express':
npm i nodemailer && npm i
Ứng dụng mặc định do Express tạo ra cung cấp cho bạn hai tệp định tuyến: /routes/index.js
và /routes/users.js
. Xóa định tuyến users.js
và tạo một tệp định tuyến mới có tên là invoices.js
. Thêm route mới này vào tệp app.js
của bạn và xóa usersRoute
:
... var indexRouter = require('./routes/index'); var invoicesRouter = require('./routes/invoices'); var app = express(); ... app.use('/', indexRouter); app.use('/invoices', invoicesRouter); ...
Phần lớn công việc trong ứng dụng này nằm trong bộ định tuyến hóa đơn.
Trước khi tạo tuyến đường, bạn sẽ cần một số dữ liệu có thể sử dụng. Trong ứng dụng thực tế, bạn có thể sẽ kết nối với cơ sở dữ liệu, nhưng với mục đích minh họa, hãy thêm dữ liệu hóa đơn của bạn vào tệp JSON.
Tạo một tệp mới tại /data/invoices.json
và thêm vào bên dưới:
[ { "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 } ]
Ba hóa đơn bạn có thể thấy ở trên chứa dữ liệu khách hàng, gói và thanh toán sẽ giúp bạn tạo hóa đơn trong phần tiếp theo.
Tệp routes/invoices.js
sẽ tạo ba tuyến mới trong ứng dụng của bạn:
/invoices
– Danh sách tất cả các hóa đơn từ tệp dữ liệu phẳng ở trên./invoices/:id
– Bản xem trước hóa đơn để người dùng có thể xem hóa đơn trông như thế nào trước khi gửi cho khách hàng./invoices/:id/email
– Điểm cuối tạo và gửi hóa đơn PDF tới email liên hệ có trong hồ sơ.
Mở tệp hóa invoices.js
và thêm đoạn mã sau để xác định hai tuyến đường đầu tiên:
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;
Ứng dụng của bạn gần như đã sẵn sàng để thử nghiệm, nhưng trước tiên bạn cần tạo hai tệp dạng xem.
Express tách biệt logic và cách trình bày thành routes/
và views/
. Bây giờ thêm hai tệp mới vào thư mục views/
: invoice-list.hbs
vàvoice invoice-single.hbs
.
Ngoài ra, hãy thêm phần sau vào invoice-list.hbs
của bạn:
<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}}
Mở tệp hóa đơn điện invoice-single.hbs
và thêm tệp này:
<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>
Để thêm kiểu vào biểu định kiểu ứng dụng của bạn và kết hợpviews/layout.hbs
và thay thế nội dung của tệp bằng đoạn mã sau. Thao tác này sẽ nhập Pure và tạo bố cục lưới cột đơn:
<!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>
Mở public/style.css file
của ứng dụng của bạn và thêm đoạn mã CSS sau:
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; }
Mặc dù không bắt buộc nhưng việc thêm kiểu dáng vào hóa đơn của bạn có thể nâng cao vẻ ngoài chuyên nghiệp của chúng, vì Foxit nắm bắt tất cả kiểu dáng từ tài liệu HTML của bạn khi tạo tệp PDF.
Bây giờ, bạn đã sẵn sàng để kiểm tra ứng dụng của mình. Vui lòng run npm
start trong giao diện dòng lệnh của bạn và mở trình duyệt web của bạn tới localhost:3000/invoices
Bạn sẽ thấy một danh sách các hóa đơn tương tự như sau:
Nhấp vào “Xem” để xem trước từng hóa đơn.
Trong hai bước cuối cùng, bạn sẽ sử dụng công cụ Foxit__ HTML to PDF __ để tạo hóa đơn PDF. Sau khi hóa đơn được tạo, bạn sẽ tiến hành đính kèm chúng vào email bằng Nodemailer trước khi gửi chúng.
SDK của Foxit cung cấp nhiều chức năng để tạo và thao tác PDF, bao gồm khả năng tạo tệp PDF từ tài liệu HTML hoặc URL. Quá trình tải xuống và biên dịch tệp thực thi HTML sang PDF có sẵn
Thư viện child_process
của nút bao gồm một hàm gọi là exec()
cho phép bạn thực thi các hàm dòng lệnh. Phương pháp này rất hữu ích để chạy các tệp thực thi C++ từ Foxit. Để thực thi tệp thực thi HTML sang PDF, vui lòng cập nhật tuyến đường /:id/email
của bạn bằng mã sau:
... 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'); } }); });
Trước khi chạy mã này, vui lòng đảm bảo rằng bạn cập nhật biến htmlToPdfPath
để phản ánh đúng đường dẫn tới tệp thực thi htmltopdf
của bạn.
Để gửi lời nhắc qua email cho hóa đơn, vui lòng quay lại danh sách hóa đơn của bạn và nhấp vào “Lời nhắc qua email” cho bất kỳ hóa đơn cụ thể nào. Hành động này sẽ kích hoạt ứng dụng Node gọi tệp thực thi htmltopdf
. Đổi lại, tệp thực thi sẽ chuyển đổi hóa đơn từ tài liệu HTML do Express cung cấp thành tệp PDF. Bạn có thể định vị tệp PDF kết quả trong thư mục invoices/
của ứng dụng web của mình.
Bây giờ bạn đã có khả năng tạo Hóa đơn PDF, bước cuối cùng là gửi các hóa đơn này cho khách hàng của bạn.
sendmail
của máy chủ làm các tùy chọn thay thế.
Để kiểm tra Nodemailer, bạn có thể sử dụngconsole.log
trong định tuyến /invoices/:id/email
của bạn:
... // 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'); } }); ...
Vui lòng làm mới ứng dụng Node của bạn và nhấp vào “Lời nhắc qua email” cho bất kỳ hóa đơn nào. Lần này, bạn sẽ quan sát đối tượng dữ liệu email hoàn chỉnh được hiển thị dưới dạng JSON trong bảng điều khiển của mình.
{ "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]>" }
Chuỗi attachments.content
đại diện cho tệp PDF được mã hóa. Để cho ngắn gọn, tôi đã cắt bớt nó trong ví dụ trên.
Để kiểm tra email bằng cách sử dụng thực tếcreateTransport({jsonTransport: true})
bằng đoạn mã sau:
createTransport({ host: "smtp.mailtrap.io", port: 2525, auth: { user: "<YOUR_MAILTRAP_USERID>", pass: "<YOUR_MAILTRAP_PASS>" } })
Khi gửi Hóa đơn qua email, Mailtrap sẽ chụp đầu ra và cung cấp cho bạn tùy chọn tải xuống tệp đính kèm PDF. Nếu bạn truy cập vào tài khoản Mailtrap của mình, bạn sẽ tìm thấy một email tương tự như ví dụ bên dưới:
Khi bạn đã sẵn sàng triển khai ứng dụng của mình vào sản xuất, hãy thay thế thông tin đăng nhập SMTP của Mailtrap bằng máy chủ thư sản xuất. Ứng dụng web của chúng tôi hiện cung cấp khả năng tạo và tự động gửi Hóa đơn PDF cho khách hàng theo yêu cầu từ nhóm thanh toán của chúng tôi.
Nếu bạn yêu cầu một giải pháp để trình bày Hóa đơn trực tuyến và gửi chúng dưới dạng PDF, ứng dụng của chúng tôi sẽ cung cấp một nền tảng đáng tin cậy. Mặc dù công cụ HTML to PDF của Foxit là một tùy chọn thuận tiện và hiệu quả để tạo tệp PDF, nhưng điều quan trọng cần lưu ý là họ cũng cung cấp nhiều giải pháp khác. Với họ
Cũng được xuất bản ở đây .