You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何通过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 前端实现方案

核心思路

  1. 页面添加触发按钮及可选份数配置项
  2. 点击按钮后通过AJAX调用Zoho API获取发票、装箱单的PDF Blob
  3. 创建隐藏iframe加载PDF,利用浏览器print() API按指定份数重复打印
  4. 自动处理打印完成后的资源清理

代码示例

<!-- 页面控件 -->
<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 后端代理实现方案

核心思路

  1. 前端按钮请求后端接口,传递发票ID、组织ID及打印份数
  2. 后端通过Zoho API获取PDF内容,转为Base64格式返回给前端
  3. 前端接收Base64数据后转Blob,再执行打印逻辑
  4. 后端集中管理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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.09 20:09:54