Bannerify
所有文章

用 PHP 從 JSON 生成動態發票 PDF

發票只要設計一次,之後每一份都由帳務資料生成。本文提供 PHP 範例,示範用 JSON 產生表格、QR 付款碼,以及可分享的 PDF 託管連結。

· Duc An

發票是每個產品遲早都得生成的文件,也是最不好玩的一項。Dompdf 和 TCPDF 要你用 HTML 和 CSS 描述版面,結果只能做到一半;FPDF 得用座標一個個定位文字;而為了一頁有表格的文件架設無頭 Chrome 工作程序,基礎設施的負擔又太大。

版面並不是難處。發票每次看起來都一樣,會變的是資料:寄給誰、有哪些品項、總額多少、稅率多少。

所以這裡的做法是:發票只設計一次,存成範本,再把帳務資料以 JSON 送到 PDF 生成 API。API 會回傳完成的 PDF。

你需要準備

  • 一個範本,包含你的標誌、地址區塊、品項表格、總額區塊和頁尾。請在編輯器中製作,並替每個圖層命名,因為要靠名稱來指定圖層。
  • 一組 API 金鑰,從 儀表板 → API 金鑰 取得,並存放在伺服器端。
  • 範本 ID,格式像 tpl_kd93mz。

呼叫方式

端點是 createPdf。它接收你的資料,並回傳 PDF 的位元組內容。

<?php

$payload = [
    'apiKey' => getenv('BANNERIFY_API_KEY'),
    'templateId' => 'tpl_invoice',
    'modifications' => [
        ['name' => 'invoice-number', 'text' => 'INV-2026-0042'],
        ['name' => 'invoice-date', 'text' => '8 October 2026'],
        ['name' => 'customer-name', 'text' => 'Acme Studio'],
        ['name' => 'customer-address', 'text' => "12 Rue de Rivoli\n75001 Paris"],
        [
            'name' => 'line-items',
            'columns' => [
                ['key' => 'item', 'header' => 'Item'],
                ['key' => 'qty', 'header' => 'Qty', 'align' => 'right'],
                ['key' => 'amount', 'header' => 'Amount', 'align' => 'right'],
            ],
            'rows' => [
                ['item' => 'Automate plan, October', 'qty' => 1, 'amount' => '$29.00'],
                ['item' => 'Extra renders', 'qty' => 5, 'amount' => '$15.00'],
            ],
        ],
        ['name' => 'subtotal', 'text' => '$44.00'],
        ['name' => 'tax', 'text' => '$8.80'],
        ['name' => 'total', 'text' => '$52.80'],
        ['name' => 'pay-qr', 'qrcode' => 'https://pay.example.com/inv/2026-0042'],
    ],
];

$ch = curl_init('https://api.bannerify.co/v1/templates/createPdf');

curl_setopt_array($ch, [
    CURLOPT_POST => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
    CURLOPT_POSTFIELDS => json_encode($payload),
    CURLOPT_TIMEOUT => 30,
]);

$pdf = curl_exec($ch);
$status = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

if ($pdf === false || $status !== 200) {
    throw new RuntimeException("Invoice render failed with status {$status}");
}

file_put_contents("/invoices/INV-2026-0042.pdf", $pdf);

表格圖層是最有意思的部分。你送出 columns 和 rows,你在編輯器中設定的樣式(斑馬紋、列高、對齊方式、表頭樣式)就會套用到送進來的任何資料。新增一個品項時,不需要動版面。表格圖層也能用在圖片上,例如價目表和報表:請見表格生成 API。

資料從哪裡來

上面的資料結構刻意設計得很單調,因為重點就在這裡:它可以直接對應到資料庫裡的資料列。

$lineItems = [];

foreach ($order['items'] as $item) {
    $lineItems[] = [
        'item' => $item['description'],
        'qty' => $item['quantity'],
        'amount' => '$' . number_format($item['subtotal'], 2),
    ];
}

$modifications[] = ['name' => 'line-items', 'rows' => $lineItems];

金額和日期請在送出之前,先在你這一側格式化好。範本不會猜測幣別或地區設定,而這些邏輯你的帳務程式碼裡大概已經有了。

直接串流,或儲存起來

交付檔案有兩種方式,取決於你要不要保留它:

直接串流給瀏覽器。 回應本身就是 PDF,所以你只要加上正確的標頭,就能直接輸出:

header('Content-Type: application/pdf');
header('Content-Disposition: inline; filename="INV-2026-0042.pdf"');
echo $pdf;

儲存起來並取得網址。 createStoredPdf 會上傳渲染結果,並回傳一個可以放進電子郵件的連結:

{ "url": "https://cdn.bannerify.co/invoices/..." }

發票要用電子郵件寄出,而你不想自己管理檔案儲存、會過期的連結或 S3 權限時,就用這個方式。

幾個要注意的地方

  • 圖層名稱必須完全一致。 line-items 和 line_items 是不同的圖層,名稱對不上的話,發票上那個部分就會保留範本中的原始值。
  • 用最長的真實發票測試。 每個帳務系統遲早都會出現一位有四十個品項的客戶,所以請在問題找上你之前,先檢查那份文件。
  • 讓生成具有冪等性。 用發票編號當檔名,檔案已經存在就跳過。重新渲染同一張發票應該什麼都不做,而不是再多扣一次額度。
  • API 金鑰只放在伺服器端。 發票生成發生在你的帳務程式碼中,絕對不要放在瀏覽器裡。

為什麼不用 PDF 程式庫

你完全可以用程式庫來做,很多團隊也是這樣做的。差別在於版面放在哪裡。用程式庫的話,版面在 PHP 裡,設計師碰不了,每次調整品牌樣式都要部署一次,再跑一輪測試。用範本的話,版面在編輯器裡,PHP 只負責搬運資料。

迭代也比較快:打開範本,把總額區塊往上移兩公分,儲存。這是一個兩分鐘的修改,而不是一個 pull request。

接下來

免費方案每月包含 100 次生成、一個範本,以及 1GB 的圖片或檔案儲存空間,足以讓你把這個功能上線,並用真實的發票測試。Automate 方案從每月 $29 起,可生成 3,000 次。詳情請見價格。

想查看端點的參數,請見 create PDF 參考文件。modifications 物件的整體結構,包括圖片、QR code、條碼和圖表資料列,請見元素。同樣的做法也適用於收據、出貨標籤和證書,這正是 PDF 生成 API 的設計目的。

前 100 張圖片,免費送你。

做一個範本、取得 API 金鑰,幾分鐘內就能生成第一張圖片。