# 統一編號驗證
:::info
updated at: 20251202
如果內容有誤,歡迎直接留言 :D
:::
:::success
super fast!
在建立使用者資料的時候,直接確認對方輸入的統編是否有效
後來的做法是直接打[政府資料開放平臺](https://data.gov.tw/dataset/9400)的api
如果有返回有效資料,視為可以使用的統一編號
:::
--
大概幾個月前被同事問到有沒有更新統一編號的驗證寫法,
這週實際灌資料時才踩到雷,雖然前面連長度都還沒有擋 :D
阿確幫我整理了新舊規則和特例:
| 時期 | 驗證條件 | 說明 |
| --- | ---------------- | --------------------------------- |
| 舊規則 | `sum % 10 === 0` | 用模 10 驗證 |
| 新規則 | `sum % 5 === 0` | 自 2023 年起逐步導入新演算法<br>為更好兼容並擴充編號邏輯 |
| 特例 | 第 7 位為 `7` | 若不通過,嘗試 `sum + 1` 再驗一次 |
## 先實作
問了一下,統一編號有使用特定的權重陣列來進行計算
阿確直接幫我生了純 js 和使用 lodash 的做法
除了從 antd input 內拿到的是 `string`,檢查時要注意型轉
其他大致上應該是不會有太多問題......吧
--
純 js 的寫法,但後來有改成回傳物件 `{valid: 是否通過驗證, reason: 錯誤訊息}` 的版本。
```javascript=
function isValidBan(ban) {
if (!/^[0-9]{8}$/.test(ban)) return false; // 1. 先檢查編碼長度
const weights = [1,2,1,2,1,2,4,1]; // 2. 各個欄位的權重
let sum = 0;
for (let i = 0; i < 8; i++) {
const prod = Number(ban[i]) * weights[i]; // 3. 數字x權重
sum += Math.floor(prod / 10) + (prod % 10); // 4. 取乘積 十位數 個位數 計算最後總和(同時兼容特例十位數和個位數)
}
if (ban[6] === '7') {
return sum % 5 === 0 || (sum + 1) % 5 === 0; // 對第七碼(index=6, 值為 7 時)特別處理
}
return sum % 5 === 0;
}
```
如果有用 jest ,可以補上單元測試,但我是直接在 console 內用 test cases 跑測試結果。
```javascript=
// isValidBan.test.ts
import { isValidBan } from './your-path-to-isValidBan';
const validCases = [
'04322708',
'70537075',
'42842476',
'86381788',
'80335062',
'50865818',
'52752608',
'26287090',
];
describe('isValidBan', () => {
it('should return true for valid統一編號 test cases', () => {
validCases.forEach((ban) => {
expect(isValidBan(ban)).toBe(true);
});
});
it('should return false for clearly invalid inputs', () => {
const invalidCases = [
'1234567', // too short
'abcdefgh', // not numbers
'11111111', // invalid check code
'99999999', // invalid check code
'', // empty
];
invalidCases.forEach((ban) => {
expect(isValidBan(ban)).toBe(false);
});
});
});
```
## 往後延伸
邏輯上知到使用特定算法是為了防偽,但好奇「權重陣列」是怎麼來的,
才認識「模數」Modulus 這個東西,延伸的話可以去看:
1. 檢查碼(Check Digit)設計原則
2. 模數校驗碼演算法演進史(Modulus Checksum Algorithms)
```
sum num x weight => 單一數字乘上權重,乘積加總用模數取餘數,用來判斷是否通過驗證
let sum = Σ(d[i] × w[i])
let isValid = (sum % N === 0)
```
--
阿確也幫我整理了常見模數演算法和應用
| 演算法 | 說明 | 典型應用 |
| ---------------- | --------------------------------- | -------------------- |
| **Mod 10(Luhn)** | 奇數偶數位數用不同權重(1 or 2),拆成個位數相加 | 信用卡、IMEI、國際銀行帳號 IBAN |
| **Mod 11** | 權重從右往左遞減(或遞增),mod 11,若餘數為 10 轉為 X | 身分證字號(台灣)、ISBN 書碼 |
| **Mod 97** | 較長欄位使用,通常用於跨國銀行號碼 | IBAN(國際銀行帳號) |
| **Mod 89** | 高錯誤偵測力,權重與字元映射方式複雜 | 部分航太與通訊標準 |
| **CRC(循環冗餘碼)** | 用於傳輸資料檢查,有多項式與位元操作,非數字演算法 | 網路封包、檔案校驗 |
--
台灣的統編設計看起來是特化版,特別定義了第七碼權重:
- 權重:[1, 2, 1, 2, 1, 2, 4, 1]
- 檢查方式:拆位數相加(類似 Luhn)
- 特例處理:第 7 位為 7 時,允許 sum + 1 再檢查
- 優點:計算快、能偵測常見錯誤(單字錯、鄰位調換)
## Reference
我引用阿確的引用:
- 財政部「營業人統一編號檢查碼邏輯說明」:
https://www.etax.nat.gov.tw ➜ 搜尋「統一編號檢查碼」
- 教育部全國教學資源平台《統一編號驗證邏輯簡介》
- 內部實務手冊(財政資訊中心系統開發準則)
{%preview https://hackmd.io/@0C9tvexQRlq2rpKZwMsejw/B1RGL7bPa %}
詳細一點的可以看這篇(看到公式視覺化覺得好像回到大學)
{%preview https://cynthiachuang.github.io/Check-Tax-ID-Number/ %}