如何通过API在Zoho Books中用PHP/JS导出发票PDF并实现打印?
实现一键打印指定份数的发票与装箱单方案
需求说明
- 在发票详情页添加按钮,点击后无需用户额外交互,直接打印2份发票和1份装箱单
- 支持调整打印份数为可选扩展功能
- 现有代码仅能通过Zoho Books API导出发票PDF,需扩展实现「导出+自动打印」完整逻辑
现有发票PDF导出代码
invoiceID = invoice.get("invoice_id"); invoicedate = invoice.get("date").toDate(); organizationID = organization.get("organization_id"); invoiceIdList = List(); invoiceIdList.add(invoiceID); json = Map(); response = invokeurl[ url :"https://books.zoho.com/api/v3/invoices/pdf?invoice_ids=" + invoiceIdList + "&organization_id=" + organizationID type :GET connection:"books" ]; info response;
JavaScript 前端实现方案
核心思路
- 页面添加触发按钮及可选份数配置项
- 点击按钮后通过AJAX调用Zoho API获取发票、装箱单的PDF Blob
- 创建隐藏iframe加载PDF,利用浏览器
print()API按指定份数重复打印 - 自动处理打印完成后的资源清理
代码示例
<!-- 页面控件 --> <div class="print-controls"> <label>发票份数:<input type="number" id="invoiceCount" value="2" min="1"></label> <label>装箱单份数:<input type="number" id="packingSlipCount" value="1" min="1"></label> <button id="printBtn">一键打印</button> </div> <script> const printBtn = document.getElementById('printBtn'); const invoiceCountInput = document.getElementById('invoiceCount'); const packingSlipCountInput = document.getElementById('packingSlipCount'); // 从Zoho API获取PDF Blob async function fetchPdfBlob(apiUrl) { const response = await fetch(apiUrl, { method: 'GET', headers: { 'Authorization': 'Zoho-oauthtoken YOUR_ACCESS_TOKEN', // 替换为实际Zoho令牌 'Accept': 'application/pdf' } }); if (!response.ok) throw new Error('PDF获取失败'); return await response.blob(); } // 按指定份数打印PDF Blob function printPdf(blob, printCount) { return new Promise((resolve) => { const blobUrl = URL.createObjectURL(blob); const iframe = document.createElement('iframe'); iframe.style.display = 'none'; iframe.src = blobUrl; iframe.onload = () => { let printedTimes = 0; const handlePrintFinish = () => { printedTimes++; if (printedTimes >= printCount) { iframe.contentWindow.removeEventListener('afterprint', handlePrintFinish); URL.revokeObjectURL(blobUrl); document.body.removeChild(iframe); resolve(); } else { iframe.contentWindow.print(); } }; iframe.contentWindow.addEventListener('afterprint', handlePrintFinish); iframe.contentWindow.print(); }; document.body.appendChild(iframe); }); } // 按钮点击事件处理 printBtn.addEventListener('click', async () => { const invoiceID = invoice.get("invoice_id"); const organizationID = organization.get("organization_id"); const invoiceCount = parseInt(invoiceCountInput.value); const packingSlipCount = parseInt(packingSlipCountInput.value); try { // 打印发票 const invoicePdfUrl = `https://books.zoho.com/api/v3/invoices/pdf?invoice_ids=${invoiceID}&organization_id=${organizationID}`; const invoiceBlob = await fetchPdfBlob(invoicePdfUrl); await printPdf(invoiceBlob, invoiceCount); // 打印装箱单(替换为实际Zoho装箱单API地址) const packingSlipPdfUrl = `https://books.zoho.com/api/v3/packing_slips/pdf?invoice_id=${invoiceID}&organization_id=${organizationID}`; const packingSlipBlob = await fetchPdfBlob(packingSlipPdfUrl); await printPdf(packingSlipBlob, packingSlipCount); alert('打印任务已完成'); } catch (err) { console.error('打印出错:', err); alert('打印失败,请检查API配置或网络连接'); } }); </script>
注意事项
- 前端直接调用Zoho API时,需确保令牌权限正确且避免暴露敏感信息(建议通过后端代理API请求)
- 部分浏览器会限制自动打印,需引导用户允许打印权限
- 装箱单API地址需根据Zoho Books官方文档调整为实际可用接口
PHP 后端代理实现方案
核心思路
- 前端按钮请求后端接口,传递发票ID、组织ID及打印份数
- 后端通过Zoho API获取PDF内容,转为Base64格式返回给前端
- 前端接收Base64数据后转Blob,再执行打印逻辑
- 后端集中管理API令牌,提升安全性
代码示例
后端接口(print_handler.php)
<?php // Zoho API配置 $zohoToken = 'YOUR_ZOHO_ACCESS_TOKEN'; $orgId = $_GET['org_id'] ?? ''; $invoiceId = $_GET['invoice_id'] ?? ''; $invoiceCount = $_GET['invoice_count'] ? (int)$_GET['invoice_count'] : 2; $packingSlipCount = $_GET['packing_slip_count'] ? (int)$_GET['packing_slip_count'] : 1; // 封装Zoho API请求函数 function getZohoPdf($url, $token) { $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_HTTPHEADER, [ "Authorization: Zoho-oauthtoken {$token}", "Accept: application/pdf" ]); $response = curl_exec($ch); curl_close($ch); return $response; } // 获取发票PDF $invoicePdf = getZohoPdf( "https://books.zoho.com/api/v3/invoices/pdf?invoice_ids={$invoiceId}&organization_id={$orgId}", $zohoToken ); // 获取装箱单PDF(替换为实际接口) $packingSlipPdf = getZohoPdf( "https://books.zoho.com/api/v3/packing_slips/pdf?invoice_id={$invoiceId}&organization_id={$orgId}", $zohoToken ); // 返回Base64编码的PDF数据 echo json_encode([ 'invoice_pdf' => base64_encode($invoicePdf), 'invoice_count' => $invoiceCount, 'packing_slip_pdf' => base64_encode($packingSlipPdf), 'packing_slip_count' => $packingSlipCount ]); ?>
前端调用代码
<div class="print-controls"> <label>发票份数:<input type="number" id="invoiceCount" value="2" min="1"></label> <label>装箱单份数:<input type="number" id="packingSlipCount" value="1" min="1"></label> <button id="printBtn">一键打印</button> </div> <script> const printBtn = document.getElementById('printBtn'); const invoiceCountInput = document.getElementById('invoiceCount'); const packingSlipCountInput = document.getElementById('packingSlipCount'); // Base64转Blob function base64ToBlob(base64, contentType) { const byteChars = atob(base64); const byteArrays = []; for (let i = 0; i < byteChars.length; i += 512) { const slice = byteChars.slice(i, i + 512); const byteNumbers = Array.from(slice, c => c.charCodeAt(0)); byteArrays.push(new Uint8Array(byteNumbers)); } return new Blob(byteArrays, { type: contentType }); } // 打印PDF函数(同前端方案) function printPdf(blob, printCount) { return new Promise((resolve) => { const blobUrl = URL.createObjectURL(blob); const iframe = document.createElement('iframe'); iframe.style.display = 'none'; iframe.src = blobUrl; iframe.onload = () => { let printedTimes = 0; const handlePrintFinish = () => { printedTimes++; if (printedTimes >= printCount) { iframe.contentWindow.removeEventListener('afterprint', handlePrintFinish); URL.revokeObjectURL(blobUrl); document.body.removeChild(iframe); resolve(); } else { iframe.contentWindow.print(); } }; iframe.contentWindow.addEventListener('afterprint', handlePrintFinish); iframe.contentWindow.print(); }; document.body.appendChild(iframe); }); } // 按钮点击事件 printBtn.addEventListener('click', async () => { const invoiceID = invoice.get("invoice_id"); const organizationID = organization.get("organization_id"); const invoiceCount = parseInt(invoiceCountInput.value); const packingSlipCount = parseInt(packingSlipCountInput.value); try { // 请求后端接口 const response = await fetch(`print_handler.php?invoice_id=${invoiceID}&org_id=${organizationID}&invoice_count=${invoiceCount}&packing_slip_count=${packingSlipCount}`); const data = await response.json(); // 打印发票 const invoiceBlob = base64ToBlob(data.invoice_pdf, 'application/pdf'); await printPdf(invoiceBlob, data.invoice_count); // 打印装箱单 const packingSlipBlob = base64ToBlob(data.packing_slip_pdf, 'application/pdf'); await printPdf(packingSlipBlob, data.packing_slip_count); alert('打印完成'); } catch (err) { console.error('打印出错:', err); alert('打印失败,请检查后端配置'); } }); </script>
注意事项
- 后端需确保Zoho令牌的安全性,避免硬编码在可公开访问的文件中
- 添加API请求失败的重试逻辑和错误处理
- 前端打印逻辑需兼容不同浏览器的行为差异
内容的提问来源于stack exchange,提问作者hassamshaikh
相关产品推荐
相关产品推荐

