Skip to content

訂單 #284427 取消後取貨完成、發票未開立排查報告

日期:2026-05-05 站台:HARU (letsharu.com) 撰寫:Nel Tseng

總結

顧客 3/29 取消訂單,但藍新與超商物流系統未同步取消,貨已寄達門市,顧客 4/2 仍前往取貨並付款,訂單因此自動恢復為「已完成」。

CTK 自製的金財通發票外掛 ctkpro-invoice 規定「曾經取消過的訂單不開發票」,本訂單因此漏開。每日 4 次的補開排程查詢條件設計上無法涵蓋此情境,亦未補上。CTK 交接文件將此情境列為「非系統問題」。

建議處理方向

  1. 手動轉訂單狀態:將 #284427 從「完成」切換為「處理中」再切回「完成」
  2. 請 CTK 處理
  3. 評估替換:重做金財通發票串接外掛,或改用其他電子發票服務商與對應外掛

詳細排查、程式碼追蹤、影響範圍與技術細節見以下章節。

一、問題描述

訂單 #284427 顧客以超商取貨付款下單,3/29 自行取消訂單,4/2 仍至超商取貨並完成付款,藍新回傳付款資料後,訂單狀態由「已取消」自動轉為「完成」。然而完成後發票未自動開立,且每日 4 次的補開排程亦未補開,金財通後台無對應發票。

二、訂單事件鏈

顧客:蔡秉軒 / 會員 ID 34426 / iPhone Safari

時間事件訂單狀態觸發來源
3/26 18:58:56顧客下單 (1,379 元,超商取貨付款,門市:東航)等待付款中顧客
3/26 19:00:08藍新第 1 次回傳:店家、藍新交易序號 26032619000531322等待付款中 → 處理中藍新 callback
3/26 19:00:11子主題 newebpay_cvscom_fix_status 攔截:店家有了但未含付款時間處理中 → 等待付款中子主題客製
3/29 14:18:49WC my-account 自助取消 (備註內容「顧客已取消訂單」)等待付款中 → 已取消WooCommerce 系統 (顧客觸發)
4/2 15:23:07顧客至東航門市取貨並付款 (藍新交易序號 26032619000531322)(WC 仍為已取消)顧客
4/2 15:25:42藍新第 2 次回傳:含付款時間 2026-04-02 15:23:07(備註寫入)藍新 callback
4/2 15:25:49「超商取貨付款已完成」已取消 → 完成藍新外掛

備註:發票類型為個人發票 (_billing_invoiceFlag_billing_needUBN_billing_UBN 皆為空,無載具 meta),金額 1,379 元有開立必要。

三、CTK 交接文件對此情境的說明

CTK 在 2026-03 交接文件 0326 會議記錄段「已知問題」中將此情境列為:

CTK 交接文件原文 (PDF 第 19 頁)

客人取消訂單但已取貨的發票未自動開立(非系統問題)

實際排查結果:此情境是 CTK 自製外掛 ctkpro-invoice程式邏輯設計問題,並非單純的人工流程或第三方因素。詳細分析見第四節。

四、排查過程

4-1 訂單 postmeta 比對

訂單 postmeta 中無任何發票相關欄位 (invoice_numberinvoice_date 皆缺),確認發票從未開立。

關鍵 meta:

欄位
_payment_methodnewebpay
_nwpSelectedPaymentCVSCOMPayed
_CVSCOMNotPayed0
_newebpayStoreName東航
_date_paid1774522808 (2026-03-26 19:00:08)
_date_completed1775114742 (2026-04-02 15:25:42)
second_time_gateway1 (藍新第 2 次 callback 已記錄完成)
invoice_number(不存在)

_date_paid 為藍新第 1 次 callback 寫入的時間,並非顧客實際付款時間。實際付款時間在訂單備註 (4/2 15:23:07),未寫入 postmeta。藍新交易序號 (26032619000531322) 也只在備註出現,未保存於 postmeta。

雙重確認 invoice meta:分別比對 invoice_number_invoice_numbereinvoice_numberbp_invoice_nocarrierbankpro_*,全部不存在。發票確實從未開立 (非寫入失敗或欄位名差異)。

4-2 ctkpro-invoice 開立邏輯追蹤

外掛主邏輯位於 app/Controllers/InvoiceController.phptrigger_invoice_status 方法,hook 點為 woocommerce_order_status_changed

php
public function trigger_invoice_status($order_id, $old_status, $new_status)
{
    $do_not_pass = ['cancelled', 'failed', 'refund-requested',
                    'refund-approved', 'refund-cancelled', 'refunded'];
    $pass = ['completed'];

    // 原始狀態已經為關閉等特殊狀態: 不觸發
    if (in_array($old_status, $do_not_pass)) {
        return;
    }

    // 新狀態為完成時觸發開發票
    if (in_array($new_status, $pass)) {
        $this->invoiceService->create_invoice($order_id);
    }
}

判斷規則:

  • old_statuscancelled / failed / refund-* 任一者 → 直接 return,不開發票
  • new_statuscompleted 才觸發開立

訂單 #284427 的最後一次狀態變化為 cancelled → completedold_status = 'cancelled' 命中第一道條件,直接 return 不進入開立流程。

4-3 補開排程邏輯追蹤

排程主邏輯位於 app/Controllers/ScheduleController.phpctk_check_invoice 方法,每日 08:20 / 14:20 / 20:20 / 02:20 各執行一次:

php
$stamp_now = $time - (8 * 60 * 60);
// 至一小時內的訂單先無視(可能正在跑流程或發票開一半)
$stamp_end = $stamp_now - (1 * 60 * 60);
$stamp_begin = $stamp_end - (8 * 60 * 60);

$args = [
    'date_paid' => "$stamp_begin...$stamp_end",
    'return' => 'ids',
];
$orders = wc_get_orders($args);
foreach ($orders as $order_id) {
    $invoice_number = get_post_meta($order_id, 'invoice_number', true) ?? '';
    $status = get_post_status($order_id);
    if (!$invoice_number && 'wc-completed' === $status) {
        $this->invoiceService->create_invoice($order_id);
    }
}

判斷規則:

  • 查詢條件:date_paid 落在「執行時間 -9 小時 ~ 執行時間 -1 小時」這 8 小時窗口
  • 命中後:訂單狀態為 wc-completedinvoice_number 為空 → 補開
  • 排程時間 02:20 / 08:20 / 14:20 / 20:20 為台北時區 (cron timestamp 以 UTC 儲存)

對訂單 #284427 而言:

  • _date_paid = 2026-03-26 19:00:08 (藍新第 1 次 callback 寫入)
  • 真正完成時間為 4/2 15:25:42,相隔 7 天
  • 排程窗口最多回溯 9 小時,#284427 的 _date_paid (3/26) 早於任何 4 月排程的查詢範圍
  • 實測 6 個時段 (4/2 02:20 / 08:20 / 14:20 / 20:20、4/3 02:20 / 08:20) 模擬排程查詢,#284427 全部 has_284427=NO

排程設計上僅能補開「付款日當天 9 小時內因故未開立」的訂單,付款日與完成日跨日的情境不在排程涵蓋範圍。

4-4 InvoiceService::create_invoice 額外風險

讀完 app/Services/InvoiceService.php 後另外發現兩項與本案相關的設計弱點:

  1. API 失敗無重試機制:金財通 API 回錯誤時,create_invoice 僅寫一條 order note,不更新任何 meta。代表下一次排程窗口若再命中該訂單會重試;但若窗口已過 (如 #284427),永遠不會再重試。
  2. get_date_completed()->format() 未檢查 null:若訂單尚未真正落定 date_completed 即觸發,會 fatal error。本案訂單 date_completed 完整,未發生此問題,但屬潛在風險。

外掛無 logger 機制 (debug.log 不存在、wc-logs 無 ctkpro-invoice 條目),無法從 log 量化過去排程實際補開了多少筆,亦無法回頭辨識「漏網」與「API 失敗」的比例。

五、判定結果

直接原因

訂單 #284427 的最後一次狀態變化為 cancelled → completedctkpro-invoice 即時開立邏輯規則「old_status 為 cancelled 時直接 return」攔下,發票未自動開立。事後亦無人工或批次再次切換訂單狀態,未觸發任何後續開立。

根本原因

CTK 自製外掛 ctkpro-invoice 的兩層發票機制設計覆蓋不全:

  1. 即時開立機制 (InvoiceController)cancelled 列入 old_status 黑名單,「先取消後完成」的訂單第一次 trigger 時必定被擋。
  2. 補開排程機制 (ScheduleController):以 _date_paid 為基準、8 小時窗口。_date_paid 為藍新第 1 次 callback (選店時) 寫入,跨日完成的訂單永遠落不進排程窗口。

兩層機制都擋掉後,唯一補救路徑為人工介入:客服或批次任務後續再切一次訂單狀態 (例如 完成 → 保留 → 完成完成 → 處理中 → 完成),新的 old_status 不在黑名單,即觸發開立。

與 CTK 交接文件說明對照

CTK 交接文件將此情境標註為「非系統問題」,從上述設計推斷,這句話的實際意義可能是「需靠人工再切一次狀態才會補開,自動流程不處理」。

影響範圍

過去 6 個月發生「已取消 → 完成」轉換的訂單共 5 筆 (以 order note 含「已取消 變更為 完成」字串為盤點條件):

訂單下單日取貨完成日後續狀態變更發票補開觸發
#28442703-2604-02NULL漏開
#28419803-1903-2103-25 大量編輯 完成→保留→完成ZG84814128保留→完成 觸發
#28322902-2002-2204-01 完成→處理中→完成ZG84814245處理中→完成 觸發
#28239401-2301-3001-30 等待付款中→完成XE23797277等待付款中→完成 觸發
#28169401-0701-12XE23796885不明 (與 #284427 流程相同卻有發票)

5 筆中:

  • 3 筆(#284198 / #283229 / #282394):事後有人工或批次切換狀態,重新觸發開立而成功補開
  • 1 筆(#281694):流程與 #284427 幾乎相同 (取消 → 取貨完成 → 無後續變更),但仍有發票。原因不明,可能涉及外掛歷史版本差異或其他補救路徑,需請 CTK 確認
  • 1 筆(#284427):漏開

無法 100% 還原 #281694 的補開機制,代表此漏洞的觸發條件不可預測,未來仍有再次漏開的可能性。

HPOS 相容性確認

ctkpro-invoice 依賴 get_post_meta() / get_post_status() 等舊 API。已驗證 HARU 站未啟用 HPOS (無 wpk6_wc_orders 表),訂單存於 wpk6_posts + wpk6_postmeta。若日後啟用 HPOS,此外掛的即時開立與補開排程整套會失效,需同步重構。

六、建議處理方向

問題源自 CTK 自製外掛 ctkpro-invoice 的兩處邏輯設計。我方不直接修改該外掛,避免動到後責任歸屬轉移。

1. 請 CTK 處理

外掛由 CTK 開發與維護,邏輯細節、原始設計意圖、後續修補責任皆在原廠。

2. 評估替換方案

若 CTK 無法處理或不再支援,可評估以下方向:

  • 重做金財通發票串接外掛:保留現有金財通服務商,重新實作串接層
  • 改用其他發票服務商:評估市場上其他電子發票服務 (如綠界、ezPay、SmilePay 等) 與對應的成熟外掛,整體替換金財通

3. 過渡期人工處理 #284427

在處理方向定案前,#284427 一筆可選擇下列任一方式補開:

  • 方式 A:登入金財通後台手動開立
  • 方式 B:由後台將該訂單狀態從「完成」切換為「處理中」再切回「完成」,會觸發 ctkpro-invoice 自動開立 (此為過去 6 個月其他 4 筆同類訂單的實際補救方式)

過去 6 個月「先取消後完成」共 5 筆,僅 #284427 漏網,其餘 4 筆皆已透過事後狀態切換成功補開,可作為操作參考。