串電子發票,第一張開出去通常很快:照加值中心的文件組好參數、送出、拿到發票號碼。真正難的是後面。

我在工作上串過電子發票,痛點集中在五個地方:

  • 退款後的發票怎麼處理
  • 開票失敗怎麼補
  • 載具和統編怎麼檢查
  • 金額對不起來
  • 字軌更新

這五件事有一個共同點:它們都不在「開一張發票」的 happy path 上,所以第一版很容易沒做。

以下的規則以台灣的電子發票為準。API 名稱以綠界(ECPay)的 B2C 電子發票為例,其他加值中心的流程類似,名稱不同。

發票不是訂單的附屬品

先講一個觀念。電商的訂單可以改、可以取消、可以退款。發票不行。

發票開出去之後,就是報給國稅局的資料。 它不能直接改,只能用法規允許的方式處理:作廢、折讓、重開。而且開立後有上傳期限:B2C 發票要在 2 天內上傳,B2B 發票要在 7 天內上傳。

所以發票要當成一個獨立的狀態機來設計,不能只是訂單上的一個欄位。訂單狀態的變化,要對應到發票的某個動作:

訂單發生的事發票要做的事
付款成功開立
全額退款,還沒申報作廢
全額退款,已經申報開折讓(金額等於發票金額)
部分退款開折讓
買家要改統編或載具作廢後重開(還沒申報才能做)

這張表裡的每一列,都是一個痛點。

痛點一:退款後的發票

退款時,發票要作廢還是開折讓,看的是這一期的發票申報了沒有。

統一發票使用辦法第 20 條規定:發生退貨或折讓時,如果原發票的銷售額還沒申報,可以收回作廢;已經申報的,要開折讓證明單。

發票每兩個月一期,下一個單數月的 15 日前申報。加值中心通常會設一個更早的截止時間,例如綠界的作廢截止是單數月 13 日 23:59:59。過了這個時間,就只能開折讓。

對應的 API 也不同:

  • 作廢:/B2CInvoice/Invalid
  • 開折讓:/B2CInvoice/Allowance,或要買家線上同意的 /B2CInvoice/AllowanceByCollegiate
  • 作廢折讓:/B2CInvoice/AllowanceInvalid

常見的問題是:退款流程和發票流程沒有連在一起。退款在後台按下去,錢退了,發票還是有效的。月底對帳時,發票的金額比實際收入多。

要做的事:

  1. 退款的流程裡,加一步「處理發票」。
  2. 這一步先判斷有沒有過作廢截止時間,再決定作廢或折讓。
  3. 部分退款一定是折讓。折讓的金額要跟退款金額一致,包含促銷分攤(見退款與對帳)。

痛點二:開票失敗怎麼補

開票會失敗:加值中心逾時、參數錯誤、字軌沒設定。失敗之後,大部分系統會排程重試。

重試最大的風險是重複開票。 情況是這樣:第一次送出時,加值中心其實開好了,但回應在網路上掉了。系統以為失敗,重試一次,同一筆訂單就有兩張發票。

要做的事:

  1. 每筆訂單給一個固定的商家自訂編號。 綠界叫 RelateNumber,同一個商家不能重複。重試時用同一個編號,加值中心會擋下重複的那一次。
  2. 重試前先查詢。 用自訂編號查這筆發票有沒有開過。開過了,就把結果補回自己的資料庫,不要再開。
  3. 失敗要讓人看得到。 開票失敗不能只寫在 log 裡。要有一個列表或通知,讓人知道哪些訂單還沒有發票。
  4. 重試要有上限。 超過上限就停,交給人處理。

痛點三:載具和統編

買家結帳時可以選三種方式拿發票:存到載具、捐贈、要統編。每一種都有格式規則:

  • 手機條碼載具:/ 開頭,加 7 個字元
  • 自然人憑證載具:2 個英文字母,加 14 個數字
  • 捐贈碼:3 到 7 位數字
  • 統一編號:8 位數字,有檢查碼規則

格式錯了,開票就會失敗,然後掉進痛點二。

要做的事:

  1. 結帳時就檢查格式,不要等到開票才發現。
  2. 手機條碼可以先驗證。 綠界的 /B2CInvoice/CheckBarcode 可以查詢手機條碼是否存在。這個 API 要呼叫財政部的系統,財政部維護時會失敗。所以綠界的文件也提醒:它只能當輔助驗證,不要因為它失敗就擋住開票。
  3. 統編、載具、捐贈的組合規則照加值中心的規格檢查。哪些可以一起用、哪些互斥,每家規格寫得不一樣,要逐條對。

痛點四:金額對不起來

加值中心會檢查:每個品項的金額加起來,要等於發票的總金額。 對不起來就開票失敗。

金額對不起來,常見的原因有三個:

  • 運費沒放進品項。 訂單總額包含運費,品項只列商品,就差了一筆運費。
  • 折扣沒放進品項。 訂單用了折扣碼,總額變少了,品項還是原價。折扣要列成一個負數的品項,或分攤到每個品項。
  • 小數處理不一致。 品項金額算出小數後,有的地方四捨五入,有的地方直接截掉。差一塊錢,開票就失敗。

稅額也要算對。B2C 發票的金額含稅,稅額是「總額 ÷ 1.05 × 0.05」,再照規格處理小數。

要做的事:

  1. 發票品項從訂單的最後金額產生,包含運費和折扣。
  2. 全系統用同一種進位規則,在一個函式裡算,不要每個地方各自處理。
  3. 送出前自己先驗算一次:品項加總是否等於總額。不相等就不送,直接記錄錯誤。

痛點五:字軌更新

發票號碼不是自己編的。國稅局每兩個月配發一次號碼區段,叫字軌。每一期開始前,要把新一期的字軌設定到加值中心。

忘了設定,結果就是:單數月 1 日 0 點之後,所有的開票都失敗。

在綠界要做兩步:

  1. 用 AddInvoiceWordSetting 新增下一期的字軌。
  2. 用 UpdateInvoiceWordStatus 把它啟用。

要做的事:

  1. 把字軌設定排進行事曆,在每個雙數月的月底前完成。
  2. 用排程檢查下一期的字軌是否已經設定、啟用。沒有就提前通知。
  3. 字軌的號碼也會用完。 量大的商家要監控剩餘數量。

我自己的專案也漏了

講完工作上踩過的坑,回頭看我自己做的電商專案。發票模組有寫,但上面五個痛點,大部分都沒處理到:

痛點我的專案
退款後的發票沒做折讓。退款流程和發票沒有連起來
開票失敗怎麼補有重試排程,但重試前沒有先查詢,可能重複開票;重試次數的紀錄也會被覆寫
載具和統編有載具時,統編會被丟掉
金額對不起來品項沒有包含運費和折扣;小數用截掉,沒有四捨五入
字軌更新沒有處理

另外還有一個:付款成功後自動開發票的那段,失敗時把例外吞掉了,只寫了 log。對照痛點二的第 3 點,這筆訂單沒有發票,但不會有人知道。

這不是因為不知道發票要開。是因為開出第一張發票的時候,看起來就已經完成了。後面這五件事,都要等到退款、等到失敗、等到換期,才會出現。

收:串發票的檢查清單

  • 發票是獨立的狀態機,不是訂單的一個欄位
  • 退款流程會處理發票,並依截止時間選擇作廢或折讓
  • 每筆發票有固定的自訂編號,重試前先查詢
  • 開票失敗有人看得到,重試有上限
  • 結帳時就檢查載具、捐贈碼、統編的格式
  • 發票品項包含運費和折扣,進位規則全系統一致
  • 送出前自己驗算品項加總
  • 下一期的字軌有排程檢查

相關主題