串電子發票,第一張開出去通常很快:照加值中心的文件組好參數、送出、拿到發票號碼。真正難的是後面。
我在工作上串過電子發票,痛點集中在五個地方:
- 退款後的發票怎麼處理
- 開票失敗怎麼補
- 載具和統編怎麼檢查
- 金額對不起來
- 字軌更新
這五件事有一個共同點:它們都不在「開一張發票」的 happy path 上,所以第一版很容易沒做。
以下的規則以台灣的電子發票為準。API 名稱以綠界(ECPay)的 B2C 電子發票為例,其他加值中心的流程類似,名稱不同。
發票不是訂單的附屬品
先講一個觀念。電商的訂單可以改、可以取消、可以退款。發票不行。
發票開出去之後,就是報給國稅局的資料。 它不能直接改,只能用法規允許的方式處理:作廢、折讓、重開。而且開立後有上傳期限:B2C 發票要在 2 天內上傳,B2B 發票要在 7 天內上傳。
所以發票要當成一個獨立的狀態機來設計,不能只是訂單上的一個欄位。訂單狀態的變化,要對應到發票的某個動作:
| 訂單發生的事 | 發票要做的事 |
|---|---|
| 付款成功 | 開立 |
| 全額退款,還沒申報 | 作廢 |
| 全額退款,已經申報 | 開折讓(金額等於發票金額) |
| 部分退款 | 開折讓 |
| 買家要改統編或載具 | 作廢後重開(還沒申報才能做) |
這張表裡的每一列,都是一個痛點。
痛點一:退款後的發票
退款時,發票要作廢還是開折讓,看的是這一期的發票申報了沒有。
統一發票使用辦法第 20 條規定:發生退貨或折讓時,如果原發票的銷售額還沒申報,可以收回作廢;已經申報的,要開折讓證明單。
發票每兩個月一期,下一個單數月的 15 日前申報。加值中心通常會設一個更早的截止時間,例如綠界的作廢截止是單數月 13 日 23:59:59。過了這個時間,就只能開折讓。
對應的 API 也不同:
- 作廢:
/B2CInvoice/Invalid - 開折讓:
/B2CInvoice/Allowance,或要買家線上同意的/B2CInvoice/AllowanceByCollegiate - 作廢折讓:
/B2CInvoice/AllowanceInvalid
常見的問題是:退款流程和發票流程沒有連在一起。退款在後台按下去,錢退了,發票還是有效的。月底對帳時,發票的金額比實際收入多。
要做的事:
- 退款的流程裡,加一步「處理發票」。
- 這一步先判斷有沒有過作廢截止時間,再決定作廢或折讓。
- 部分退款一定是折讓。折讓的金額要跟退款金額一致,包含促銷分攤(見退款與對帳)。
痛點二:開票失敗怎麼補
開票會失敗:加值中心逾時、參數錯誤、字軌沒設定。失敗之後,大部分系統會排程重試。
重試最大的風險是重複開票。 情況是這樣:第一次送出時,加值中心其實開好了,但回應在網路上掉了。系統以為失敗,重試一次,同一筆訂單就有兩張發票。
要做的事:
- 每筆訂單給一個固定的商家自訂編號。 綠界叫
RelateNumber,同一個商家不能重複。重試時用同一個編號,加值中心會擋下重複的那一次。 - 重試前先查詢。 用自訂編號查這筆發票有沒有開過。開過了,就把結果補回自己的資料庫,不要再開。
- 失敗要讓人看得到。 開票失敗不能只寫在 log 裡。要有一個列表或通知,讓人知道哪些訂單還沒有發票。
- 重試要有上限。 超過上限就停,交給人處理。
痛點三:載具和統編
買家結帳時可以選三種方式拿發票:存到載具、捐贈、要統編。每一種都有格式規則:
- 手機條碼載具:
/開頭,加 7 個字元 - 自然人憑證載具:2 個英文字母,加 14 個數字
- 捐贈碼:3 到 7 位數字
- 統一編號:8 位數字,有檢查碼規則
格式錯了,開票就會失敗,然後掉進痛點二。
要做的事:
- 結帳時就檢查格式,不要等到開票才發現。
- 手機條碼可以先驗證。 綠界的
/B2CInvoice/CheckBarcode可以查詢手機條碼是否存在。這個 API 要呼叫財政部的系統,財政部維護時會失敗。所以綠界的文件也提醒:它只能當輔助驗證,不要因為它失敗就擋住開票。 - 統編、載具、捐贈的組合規則照加值中心的規格檢查。哪些可以一起用、哪些互斥,每家規格寫得不一樣,要逐條對。
痛點四:金額對不起來
加值中心會檢查:每個品項的金額加起來,要等於發票的總金額。 對不起來就開票失敗。
金額對不起來,常見的原因有三個:
- 運費沒放進品項。 訂單總額包含運費,品項只列商品,就差了一筆運費。
- 折扣沒放進品項。 訂單用了折扣碼,總額變少了,品項還是原價。折扣要列成一個負數的品項,或分攤到每個品項。
- 小數處理不一致。 品項金額算出小數後,有的地方四捨五入,有的地方直接截掉。差一塊錢,開票就失敗。
稅額也要算對。B2C 發票的金額含稅,稅額是「總額 ÷ 1.05 × 0.05」,再照規格處理小數。
要做的事:
- 發票品項從訂單的最後金額產生,包含運費和折扣。
- 全系統用同一種進位規則,在一個函式裡算,不要每個地方各自處理。
- 送出前自己先驗算一次:品項加總是否等於總額。不相等就不送,直接記錄錯誤。
痛點五:字軌更新
發票號碼不是自己編的。國稅局每兩個月配發一次號碼區段,叫字軌。每一期開始前,要把新一期的字軌設定到加值中心。
忘了設定,結果就是:單數月 1 日 0 點之後,所有的開票都失敗。
在綠界要做兩步:
- 用
AddInvoiceWordSetting新增下一期的字軌。 - 用
UpdateInvoiceWordStatus把它啟用。
要做的事:
- 把字軌設定排進行事曆,在每個雙數月的月底前完成。
- 用排程檢查下一期的字軌是否已經設定、啟用。沒有就提前通知。
- 字軌的號碼也會用完。 量大的商家要監控剩餘數量。
我自己的專案也漏了
講完工作上踩過的坑,回頭看我自己做的電商專案。發票模組有寫,但上面五個痛點,大部分都沒處理到:
| 痛點 | 我的專案 |
|---|---|
| 退款後的發票 | 沒做折讓。退款流程和發票沒有連起來 |
| 開票失敗怎麼補 | 有重試排程,但重試前沒有先查詢,可能重複開票;重試次數的紀錄也會被覆寫 |
| 載具和統編 | 有載具時,統編會被丟掉 |
| 金額對不起來 | 品項沒有包含運費和折扣;小數用截掉,沒有四捨五入 |
| 字軌更新 | 沒有處理 |
另外還有一個:付款成功後自動開發票的那段,失敗時把例外吞掉了,只寫了 log。對照痛點二的第 3 點,這筆訂單沒有發票,但不會有人知道。
這不是因為不知道發票要開。是因為開出第一張發票的時候,看起來就已經完成了。後面這五件事,都要等到退款、等到失敗、等到換期,才會出現。
收:串發票的檢查清單
- 發票是獨立的狀態機,不是訂單的一個欄位
- 退款流程會處理發票,並依截止時間選擇作廢或折讓
- 每筆發票有固定的自訂編號,重試前先查詢
- 開票失敗有人看得到,重試有上限
- 結帳時就檢查載具、捐贈碼、統編的格式
- 發票品項包含運費和折扣,進位規則全系統一致
- 送出前自己驗算品項加總
- 下一期的字軌有排程檢查
相關主題
- 退款與對帳:部分退款加上促銷時,金額怎麼分攤
- 促銷疊加規則:折扣怎麼算,決定了發票品項怎麼列
- 付款 callback:付款成功的通知可能重複,開票要能承受重複觸發