# 統一編號驗證 :::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/ %}