結帳頁、發票資料、B2B 註冊表單都會要求填統一編號。前端先檢查檢查碼,可以在使用者送出前就擋下打錯的號碼。網路上很多範例還停在「總和被 10 整除」的舊規則,這套規則在 2023 年之後會把合法的新統編判成錯誤。本文整理財政部公告的現行規則,附上經過測試的 TypeScript 函式,最後說明 WebberUI 的 tax-id-input 元件怎麼處理。
規則來源
財政部財政資訊中心在 2021 年 12 月 22 日發布〈營利事業統一編號檢查碼邏輯修正說明〉,內容有三點:
- 統一編號預估在 2024 年(民國 113 年)用罄,為了擴充號碼並與既有號碼相容,檢查邏輯由「可被 10 整除」改為「可被 5 整除」。
- 使用統編檢核程式的公私部門,需在 2023 年 3 月 31 日前完成修改。
- 2023 年 4 月起,視舊號碼剩餘數量逐步釋出新產製的號碼。
公告附件〈營利事業統一編號檢查碼邏輯修正說明(附件)〉列出完整算法與範例號碼,本文的數字都取自這份附件。2025 年 1 月,財政資訊中心另發布〈店家收銀機(POS)系統無法輸入統一編號處理指南〉,說明 2023 年 12 月起陸續釋出的新號碼,在仍使用舊邏輯的 POS 上會無法輸入,並提供 04595252、10458570 兩組號碼供業者測試。
檢查碼算法
統一編號固定 8 位數字,計算分三步:
- 8 位數字依序乘上權數
1, 2, 1, 2, 1, 2, 4, 1。 - 每個乘積取「十位數 + 個位數」,例如 18 取 1 + 8 = 9。
- 把 8 個結果加總,總和能被 5 整除即為有效。
以附件中的 04595252 為例:
| 位數 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
|---|---|---|---|---|---|---|---|---|
| 號碼 | 0 | 4 | 5 | 9 | 5 | 2 | 5 | 2 |
| 權數 | 1 | 2 | 1 | 2 | 1 | 2 | 4 | 1 |
| 乘積 | 0 | 8 | 5 | 18 | 5 | 4 | 20 | 2 |
| 十位 + 個位 | 0 | 8 | 5 | 9 | 5 | 4 | 2 | 2 |
總和 35,能被 5 整除,所以有效。舊規則要求被 10 整除,35 不符合,這就是舊程式拒收新號碼的原因。反過來說,能被 10 整除的數一定能被 5 整除,所以新規則完全涵蓋舊號碼,不需要為舊統編另外寫判斷。
第 7 碼為 7 的特例
第 7 位的權數是 4。當第 7 碼是 7,乘積 28 的兩位數相加得到 10,還是兩位數。附件的規定是:這一位可以取 1,也可以取 0,兩種總和只要其中一個能被 5 整除就有效。
以 10458570 為例,第 7 位取 1 時總和 16,取 0 時總和 15。15 能被 5 整除,所以有效;在舊規則下 16 與 15 都不能被 10 整除,會被判為無效。
TypeScript 驗證函式
const WEIGHTS = [1, 2, 1, 2, 1, 2, 4, 1] as const;
/**
* 驗證 8 碼統一編號。
* divisor 預設 5(2023 年起的現行規則);傳 10 可模擬舊規則,用來測試既有系統。
*/
export function isValidUBN(input: string, divisor: 5 | 10 = 5): boolean {
if (!/^\d{8}$/.test(input)) return false;
let sum = 0;
for (let i = 0; i < 8; i++) {
const product = Number(input[i]) * WEIGHTS[i];
// 乘積的十位數加個位數,例如 18 → 1 + 8 = 9
sum += Math.floor(product / 10) + (product % 10);
}
if (sum % divisor === 0) return true;
// 第 7 碼為 7:7 × 4 = 28,2 + 8 = 10,這一位依規定可取 1 或 0。
// 迴圈加進去的是 10;取 0 時與 sum 同餘,取 1 時等於 sum - 9。
return input[6] === "7" && (sum - 9) % divisor === 0;
}這個函式只接受已經清洗過的 8 碼字串。如果欄位允許貼上 0459-5252 或含空白的值,先用 input.replace(/\D/g, "") 去掉非數字。
我們在 Node.js 用附件與 POS 指南裡的號碼跑過這個函式,結果如下:
| 號碼 | 現行規則(÷5) | 舊規則(÷10) |
|---|---|---|
04595257 | 有效 | 有效 |
04595252 | 有效 | 無效 |
10458575 | 有效 | 有效 |
10458574 | 有效 | 有效 |
10458570 | 有效 | 無效 |
04595253 | 無效 | 無效 |
10458571 | 無效 | 無效 |
另外隨機產生 200 萬組 8 碼數字,與 WebberUI 元件內建的 validateTaxId 逐一比對,結果全部一致。
實作上容易出錯的地方
- 用數字型別儲存。
04595252存成 number 會變成4595252,長度檢查直接失敗。資料庫欄位與 API 都應該用字串。 - 還在用舊規則。 套件或後端如果是 2023 年以前寫的,先用
04595252測一次。 - 檢查碼通過不代表公司存在。 檢查碼只能擋下打錯的號碼,要確認登記狀態,需要查詢財政部的稅籍登記資料。
用 WebberUI 的 tax-id-input 元件
如果你的專案使用 React 與 Tailwind,可以直接安裝 tax-id-input。它把上面的檢核邏輯包成一個 8 格輸入欄位:滿 8 碼自動驗證,通過時顯示綠色勾號,失敗時整列格子搖晃並說明原因。
把這個效果裝進你的專案
在專案資料夾執行指令,CLI 會加入原始碼與依賴套件,再依下方使用範例引用元件。
npx webberui@latest add tax-id-input用 WebberUI CLI 安裝:
npx webberui@latest add https://webberui.com/r/tax-id-input.json或在 components.json 設定 registries 後,改用 @webberui/tax-id-input 安裝。WebberUI 的元件可取得原始碼,安裝後檔案會放進你的 components/ui/,可以直接修改。
import { TaxIdInput, validateTaxId } from "@/components/ui/tax-id-input";
<TaxIdInput
label="統一編號"
onChange={(value) => console.log(value)}
onValid={(ok, value) => ok && console.log(`${value} 可開立發票`)}
/>
// 檢核函式可單獨使用(適合表單送出前再驗一次)
validateTaxId("12345675"); // true(演算法測試號碼,非真實公司)
validateTaxId("12345678"); // false元件內建的 validateTaxId 寫法和上面的函式略有不同:第 7 碼為 7 時,它檢查 (sum + 1) % 5 === 0。sum + 1 與 sum - 9 相差 10,除以 5 的餘數相同,所以兩種寫法結果一致。元件也會先用 sanitizeTaxId 去掉非數字並截到 8 碼,貼上 0459-5252 會自動變成 04595252。
其他細節:
- 實際輸入由一個原生
input承接(inputMode="numeric"),手機會跳出數字鍵盤,8 個格子只負責顯示。 - 驗證訊息放在
aria-live="polite"區域,失敗時設定aria-invalid,螢幕閱讀器會讀出狀態。 - 系統開啟「減少動態效果」時,停用搖晃與脈衝動畫,只保留顏色與文字回饋。
完整 props 與手動安裝方式見統一編號驗證輸入框文件。同一套表單常會一起用到身分證字號欄位,可以參考身分證字號驗證規則與 React 實作。
重點整理
- 權數
1,2,1,2,1,2,4,1,乘積取十位加個位後加總。 - 現行規則是總和被 5 整除;2023 年 3 月 31 日是公私部門修改檢核程式的期限。
- 第 7 碼為 7 時,該位可取 1 或 0,任一總和符合即有效。
- 用
04595252與10458570測試你的系統是否已改用新規則。