taiwan-validator 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2022 Gary Lai
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.en.md ADDED
@@ -0,0 +1,214 @@
1
+ # Taiwan Validator
2
+
3
+ A comprehensive TypeScript validator for Taiwan identification numbers and codes.
4
+
5
+ [![CI](https://github.com/imgarylai/taiwan-validator/actions/workflows/test.yml/badge.svg)](https://github.com/imgarylai/taiwan-validator/actions/workflows/test.yml)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
7
+
8
+ [繁體中文](README.md)
9
+
10
+ ## Features
11
+
12
+ - ✅ National ID validation (身分證字號) - Both old and new formats
13
+ - ✅ Business Uniform Number validation (統一編號)
14
+ - ✅ Resident Certificate validation (居留證號) - Both old and new formats
15
+ - ✅ Mobile Phone Number validation (手機號碼)
16
+ - ✅ Citizen Digital Certificate validation (自然人憑證)
17
+ - ✅ e-Invoice Mobile Barcode validation (電子發票手機條碼)
18
+ - ✅ e-Invoice Donation Code validation (電子發票捐贈碼)
19
+ - 📘 Full TypeScript support with type definitions
20
+ - 🧪 Thoroughly tested with 100% coverage
21
+ - 📦 Tree-shakeable ESM and CommonJS support
22
+ - 🚀 Zero dependencies
23
+
24
+ ## Installation
25
+
26
+ ```bash
27
+ npm install taiwan-validator
28
+ ```
29
+
30
+ ## Usage
31
+
32
+ ```typescript
33
+ import {
34
+ validateNationalId,
35
+ validateBusinessNumber,
36
+ validateResidentCertificate,
37
+ validateMobilePhone,
38
+ validateCitizenCertificate,
39
+ validateEInvoiceMobileBarcode,
40
+ validateEInvoiceDonationCode,
41
+ } from "taiwan-validator";
42
+
43
+ // National ID (身分證字號)
44
+ validateNationalId("A123456789"); // { isValid: true }
45
+ validateNationalId("AA23456786"); // { isValid: true } - New format
46
+
47
+ // Business Number (統一編號)
48
+ validateBusinessNumber("12345676"); // { isValid: true }
49
+
50
+ // Resident Certificate (居留證號)
51
+ validateResidentCertificate("A823456783"); // { isValid: true } - Old format
52
+ validateResidentCertificate("AA23456786"); // { isValid: true } - New format
53
+
54
+ // Mobile Phone (手機號碼)
55
+ validateMobilePhone("0912345678"); // { isValid: true }
56
+ validateMobilePhone("0912-345-678"); // { isValid: true } - With separators
57
+
58
+ // Citizen Digital Certificate (自然人憑證)
59
+ validateCitizenCertificate("AB12345678901234"); // { isValid: true }
60
+
61
+ // e-Invoice Mobile Barcode (電子發票手機條碼)
62
+ validateEInvoiceMobileBarcode("/ABCD123"); // { isValid: true }
63
+
64
+ // e-Invoice Donation Code (電子發票捐贈碼)
65
+ validateEInvoiceDonationCode("12345"); // { isValid: true }
66
+ ```
67
+
68
+ ## API Documentation
69
+
70
+ ### `validateNationalId(id: string, format?: 'old' | 'new'): ValidationResult`
71
+
72
+ Validates Taiwan National ID (身分證字號).
73
+
74
+ - **Old format**: 1 letter + 9 digits (e.g., `A123456789`)
75
+ - **New format**: 2 letters + 8 digits (e.g., `AA23456786`)
76
+
77
+ ```typescript
78
+ validateNationalId("A123456789"); // Auto-detect format
79
+ validateNationalId("A123456789", "old"); // Explicitly check old format
80
+ validateNationalId("AA23456786", "new"); // Explicitly check new format
81
+ ```
82
+
83
+ ### `validateBusinessNumber(number: string): ValidationResult`
84
+
85
+ Validates Taiwan Business Uniform Number (統一編號).
86
+
87
+ - **Format**: 8 digits with checksum validation
88
+
89
+ ```typescript
90
+ validateBusinessNumber("12345676");
91
+ ```
92
+
93
+ ### `validateResidentCertificate(id: string, format?: 'old' | 'new'): ValidationResult`
94
+
95
+ Validates Taiwan Resident Certificate (居留證號).
96
+
97
+ - **Old format**: 1 letter (A-D) + 9 digits starting with 8 or 9
98
+ - **New format**: 2 letters + 8 digits
99
+
100
+ ```typescript
101
+ validateResidentCertificate("A823456783"); // Auto-detect format
102
+ validateResidentCertificate("A823456783", "old"); // Old format
103
+ validateResidentCertificate("AA23456786", "new"); // New format
104
+ ```
105
+
106
+ ### `validateMobilePhone(phone: string): ValidationResult`
107
+
108
+ Validates Taiwan mobile phone number (手機號碼).
109
+
110
+ - **Format**: 10 digits starting with 09
111
+
112
+ ```typescript
113
+ validateMobilePhone("0912345678");
114
+ validateMobilePhone("0912-345-678"); // Accepts separators
115
+ ```
116
+
117
+ ### `validateCitizenCertificate(certNumber: string): ValidationResult`
118
+
119
+ Validates Taiwan Citizen Digital Certificate Number (自然人憑證).
120
+
121
+ - **Format**: 2 uppercase letters + 14 digits
122
+
123
+ ```typescript
124
+ validateCitizenCertificate("AB12345678901234");
125
+ ```
126
+
127
+ ### `validateEInvoiceMobileBarcode(barcode: string): ValidationResult`
128
+
129
+ Validates Taiwan e-Invoice Mobile Barcode (電子發票手機條碼).
130
+
131
+ - **Format**: `/` + 7 characters (A-Z, 0-9, +, -, .)
132
+
133
+ ```typescript
134
+ validateEInvoiceMobileBarcode("/ABCD123");
135
+ ```
136
+
137
+ ### `validateEInvoiceDonationCode(code: string): ValidationResult`
138
+
139
+ Validates Taiwan e-Invoice Donation Code (電子發票捐贈碼).
140
+
141
+ - **Format**: 3-7 digits
142
+
143
+ ```typescript
144
+ validateEInvoiceDonationCode("12345");
145
+ ```
146
+
147
+ ### Return Type
148
+
149
+ All validation functions return a `ValidationResult` object:
150
+
151
+ ```typescript
152
+ interface ValidationResult {
153
+ isValid: boolean;
154
+ message?: string; // Error message when isValid is false
155
+ }
156
+ ```
157
+
158
+ ## Development
159
+
160
+ ### Setup
161
+
162
+ ```bash
163
+ # Clone the repository
164
+ git clone https://github.com/imgarylai/taiwan-validator.git
165
+ cd taiwan-validator
166
+
167
+ # Install dependencies
168
+ npm install
169
+
170
+ # Run tests
171
+ npm test
172
+
173
+ # Run tests with coverage
174
+ npm run test:coverage
175
+
176
+ # Build the package
177
+ npm run build
178
+
179
+ # Development mode (watch)
180
+ npm run dev
181
+ ```
182
+
183
+ ### Available Scripts
184
+
185
+ - `npm run build` - Build the package with tsup
186
+ - `npm run dev` - Watch mode for development
187
+ - `npm test` - Run tests
188
+ - `npm run test:coverage` - Run tests with coverage
189
+ - `npm run lint` - Lint the code
190
+ - `npm run type-check` - Check types
191
+ - `npm run docs` - Generate documentation
192
+ - `npm run clean` - Clean build outputs
193
+
194
+ ## Contributing
195
+
196
+ Contributions are welcome! Please feel free to submit a Pull Request.
197
+
198
+ 1. Fork the repository
199
+ 2. Create your feature branch (`git checkout -b feature/amazing-feature`)
200
+ 3. Commit your changes using conventional commits (`git commit -m 'feat: add amazing feature'`)
201
+ 4. Push to the branch (`git push origin feature/amazing-feature`)
202
+ 5. Open a Pull Request
203
+
204
+ ## License
205
+
206
+ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
207
+
208
+ ## Author
209
+
210
+ Gary Lai - [@imgarylai](https://github.com/imgarylai)
211
+
212
+ ## Acknowledgments
213
+
214
+ This package implements the official validation algorithms for Taiwan identification numbers and codes.
package/README.md ADDED
@@ -0,0 +1,214 @@
1
+ # Taiwan Validator
2
+
3
+ 一個完整的台灣身分證件與代碼驗證 TypeScript 套件。
4
+
5
+ [![CI](https://github.com/imgarylai/taiwan-validator/actions/workflows/test.yml/badge.svg)](https://github.com/imgarylai/taiwan-validator/actions/workflows/test.yml)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
7
+
8
+ [English](README.en.md)
9
+
10
+ ## 功能特色
11
+
12
+ - ✅ 身分證字號驗證 - 支援新舊格式
13
+ - ✅ 統一編號驗證
14
+ - ✅ 居留證號驗證 - 支援新舊格式
15
+ - ✅ 手機號碼驗證
16
+ - ✅ 自然人憑證驗證
17
+ - ✅ 電子發票手機條碼驗證
18
+ - ✅ 電子發票捐贈碼驗證
19
+ - 📘 完整的 TypeScript 型別定義
20
+ - 🧪 完整測試覆蓋率
21
+ - 📦 支援 ESM 和 CommonJS 且可 Tree-shaking
22
+ - 🚀 零依賴
23
+
24
+ ## 安裝
25
+
26
+ ```bash
27
+ npm install taiwan-validator
28
+ ```
29
+
30
+ ## 使用方式
31
+
32
+ ```typescript
33
+ import {
34
+ validateNationalId,
35
+ validateBusinessNumber,
36
+ validateResidentCertificate,
37
+ validateMobilePhone,
38
+ validateCitizenCertificate,
39
+ validateEInvoiceMobileBarcode,
40
+ validateEInvoiceDonationCode,
41
+ } from "taiwan-validator";
42
+
43
+ // 身分證字號
44
+ validateNationalId("A123456789"); // { isValid: true }
45
+ validateNationalId("AA23456786"); // { isValid: true } - 新式格式
46
+
47
+ // 統一編號
48
+ validateBusinessNumber("12345676"); // { isValid: true }
49
+
50
+ // 居留證號
51
+ validateResidentCertificate("A823456783"); // { isValid: true } - 舊式
52
+ validateResidentCertificate("AA23456786"); // { isValid: true } - 新式
53
+
54
+ // 手機號碼
55
+ validateMobilePhone("0912345678"); // { isValid: true }
56
+ validateMobilePhone("0912-345-678"); // { isValid: true } - 含分隔符號
57
+
58
+ // 自然人憑證
59
+ validateCitizenCertificate("AB12345678901234"); // { isValid: true }
60
+
61
+ // 電子發票手機條碼
62
+ validateEInvoiceMobileBarcode("/ABCD123"); // { isValid: true }
63
+
64
+ // 電子發票捐贈碼
65
+ validateEInvoiceDonationCode("12345"); // { isValid: true }
66
+ ```
67
+
68
+ ## API 文件
69
+
70
+ ### `validateNationalId(id: string, format?: 'old' | 'new'): ValidationResult`
71
+
72
+ 驗證台灣身分證字號。
73
+
74
+ - **舊式格式**:1 個英文字母 + 9 個數字(例如:`A123456789`)
75
+ - **新式格式**:2 個英文字母 + 8 個數字(例如:`AA23456786`)
76
+
77
+ ```typescript
78
+ validateNationalId("A123456789"); // 自動偵測格式
79
+ validateNationalId("A123456789", "old"); // 明確指定舊式格式
80
+ validateNationalId("AA23456786", "new"); // 明確指定新式格式
81
+ ```
82
+
83
+ ### `validateBusinessNumber(number: string): ValidationResult`
84
+
85
+ 驗證台灣統一編號。
86
+
87
+ - **格式**:8 位數字,含檢查碼驗證
88
+
89
+ ```typescript
90
+ validateBusinessNumber("12345676");
91
+ ```
92
+
93
+ ### `validateResidentCertificate(id: string, format?: 'old' | 'new'): ValidationResult`
94
+
95
+ 驗證台灣居留證號。
96
+
97
+ - **舊式格式**:1 個英文字母(A-D)+ 9 個數字(第二位為 8 或 9)
98
+ - **新式格式**:2 個英文字母 + 8 個數字
99
+
100
+ ```typescript
101
+ validateResidentCertificate("A823456783"); // 自動偵測格式
102
+ validateResidentCertificate("A823456783", "old"); // 舊式格式
103
+ validateResidentCertificate("AA23456786", "new"); // 新式格式
104
+ ```
105
+
106
+ ### `validateMobilePhone(phone: string): ValidationResult`
107
+
108
+ 驗證台灣手機號碼。
109
+
110
+ - **格式**:10 位數字,以 09 開頭
111
+
112
+ ```typescript
113
+ validateMobilePhone("0912345678");
114
+ validateMobilePhone("0912-345-678"); // 接受分隔符號
115
+ ```
116
+
117
+ ### `validateCitizenCertificate(certNumber: string): ValidationResult`
118
+
119
+ 驗證台灣自然人憑證號碼。
120
+
121
+ - **格式**:2 個大寫英文字母 + 14 位數字
122
+
123
+ ```typescript
124
+ validateCitizenCertificate("AB12345678901234");
125
+ ```
126
+
127
+ ### `validateEInvoiceMobileBarcode(barcode: string): ValidationResult`
128
+
129
+ 驗證台灣電子發票手機條碼。
130
+
131
+ - **格式**:`/` + 7 個字元(A-Z、0-9、+、-、.)
132
+
133
+ ```typescript
134
+ validateEInvoiceMobileBarcode("/ABCD123");
135
+ ```
136
+
137
+ ### `validateEInvoiceDonationCode(code: string): ValidationResult`
138
+
139
+ 驗證台灣電子發票捐贈碼。
140
+
141
+ - **格式**:3-7 位數字
142
+
143
+ ```typescript
144
+ validateEInvoiceDonationCode("12345");
145
+ ```
146
+
147
+ ### 回傳型別
148
+
149
+ 所有驗證函式都會回傳 `ValidationResult` 物件:
150
+
151
+ ```typescript
152
+ interface ValidationResult {
153
+ isValid: boolean;
154
+ message?: string; // 當 isValid 為 false 時的錯誤訊息
155
+ }
156
+ ```
157
+
158
+ ## 開發
159
+
160
+ ### 設定
161
+
162
+ ```bash
163
+ # 複製專案
164
+ git clone https://github.com/imgarylai/taiwan-validator.git
165
+ cd taiwan-validator
166
+
167
+ # 安裝相依套件
168
+ npm install
169
+
170
+ # 執行測試
171
+ npm test
172
+
173
+ # 執行測試並產生覆蓋率報告
174
+ npm run test:coverage
175
+
176
+ # 建置套件
177
+ npm run build
178
+
179
+ # 開發模式(監看)
180
+ npm run dev
181
+ ```
182
+
183
+ ### 可用指令
184
+
185
+ - `npm run build` - 使用 tsup 建置套件
186
+ - `npm run dev` - 開發模式(監看)
187
+ - `npm test` - 執行測試
188
+ - `npm run test:coverage` - 執行測試並產生覆蓋率報告
189
+ - `npm run lint` - 程式碼檢查
190
+ - `npm run type-check` - 型別檢查
191
+ - `npm run docs` - 產生文件
192
+ - `npm run clean` - 清除建置輸出
193
+
194
+ ## 貢獻
195
+
196
+ 歡迎貢獻!請隨時提交 Pull Request。
197
+
198
+ 1. Fork 此專案
199
+ 2. 建立你的功能分支 (`git checkout -b feature/amazing-feature`)
200
+ 3. 使用 conventional commits 提交你的變更 (`git commit -m 'feat: add amazing feature'`)
201
+ 4. 推送到分支 (`git push origin feature/amazing-feature`)
202
+ 5. 開啟一個 Pull Request
203
+
204
+ ## 授權
205
+
206
+ 本專案使用 MIT 授權 - 詳見 [LICENSE](LICENSE) 檔案。
207
+
208
+ ## 作者
209
+
210
+ Gary Lai - [@imgarylai](https://github.com/imgarylai)
211
+
212
+ ## 致謝
213
+
214
+ 本套件實作了台灣官方的身分證件與代碼驗證演算法。
package/dist/index.cjs ADDED
@@ -0,0 +1,2 @@
1
+ 'use strict';var V={A:10,B:11,C:12,D:13,E:14,F:15,G:16,H:17,I:34,J:18,K:19,L:20,M:21,N:22,O:35,P:23,Q:24,R:25,S:26,T:27,U:28,V:29,W:32,X:30,Y:31,Z:33};function R(t){if(!/^[A-Z][12]\d{8}$/.test(t))return false;let s=t[0],i=t.slice(1),r=V[s],e=Math.floor(r/10),a=r%10,o=[1,9,8,7,6,5,4,3,2,1,1];return [e,a,...i.split("").map(Number)].reduce((d,u,f)=>d+u*o[f],0)%10===0}function v(t){if(!/^[A-Z]{2}\d{8}$/.test(t))return false;let s=t[0],i=t[1],r=t.slice(2),e=V[s],a=V[i],o=Math.floor(e/10),l=e%10,c=Math.floor(a/10),d=a%10,u=[1,9,8,7,6,5,4,3,2,1,1,1];return [o,l,c,d,...r.split("").map(Number)].reduce((m,p,g)=>m+p*u[g],0)%10===0}function I(t){return /^[A-Z][12]\d{8}$/.test(t)?"old":/^[A-Z]{2}\d{8}$/.test(t)?"new":null}function x(t,n){if(!t||typeof t!="string")return {isValid:false,message:"\u8EAB\u5206\u8B49\u5B57\u865F\u5FC5\u9808\u70BA\u975E\u7A7A\u5B57\u4E32"};let s=t.trim().toUpperCase();if(n==="old"){let e=R(s);return {isValid:e,message:e?void 0:"\u7121\u6548\u7684\u820A\u5F0F\u8EAB\u5206\u8B49\u5B57\u865F"}}if(n==="new"){let e=v(s);return {isValid:e,message:e?void 0:"\u7121\u6548\u7684\u65B0\u5F0F\u8EAB\u5206\u8B49\u5B57\u865F"}}let i=I(s);if(!i)return {isValid:false,message:"\u7121\u6548\u7684\u8EAB\u5206\u8B49\u5B57\u865F\u683C\u5F0F"};let r=i==="old"?R(s):v(s);return {isValid:r,message:r?void 0:`\u7121\u6548\u7684${i==="old"?"\u820A\u5F0F":"\u65B0\u5F0F"}\u8EAB\u5206\u8B49\u5B57\u865F`}}function A(t){if(!t||typeof t!="string")return {isValid:false,message:"\u7D71\u4E00\u7DE8\u865F\u5FC5\u9808\u70BA\u975E\u7A7A\u5B57\u4E32"};let n=t.trim();if(!/^\d{8}$/.test(n))return {isValid:false,message:"\u7D71\u4E00\u7DE8\u865F\u5FC5\u9808\u70BA8\u4F4D\u6578\u5B57"};let i=n.split("").map(Number),r=[1,2,1,2,1,2,4,1],e=0;for(let o=0;o<8;o++){let l=i[o]*r[o];l>=10&&(l=Math.floor(l/10)+l%10),e+=l;}let a=e%10===0||i[6]===7&&e%10===1;return {isValid:a,message:a?void 0:"\u7D71\u4E00\u7DE8\u865F\u6AA2\u67E5\u78BC\u932F\u8AA4"}}var y={A:10,B:11,C:12,D:13,E:14,F:15,G:16,H:17,I:34,J:18,K:19,L:20,M:21,N:22,O:35,P:23,Q:24,R:25,S:26,T:27,U:28,V:29,W:32,X:30,Y:31,Z:33};function b(t){if(!/^[A-D][89]\d{8}$/.test(t))return false;let s=t[0],i=t.slice(1),r=y[s],e=Math.floor(r/10),a=r%10,o=[1,9,8,7,6,5,4,3,2,1,1];return [e,a,...i.split("").map(Number)].reduce((d,u,f)=>d+u*o[f],0)%10===0}function C(t){if(!/^[A-Z]{2}\d{8}$/.test(t))return false;let s=t[0],i=t[1],r=t.slice(2),e=y[s],a=y[i],o=Math.floor(e/10),l=e%10,c=Math.floor(a/10),d=a%10,u=[1,9,8,7,6,5,4,3,2,1,1,1];return [o,l,c,d,...r.split("").map(Number)].reduce((m,p,g)=>m+p*u[g],0)%10===0}function M(t){return /^[A-D][89]\d{8}$/.test(t)?"old":/^[A-Z]{2}\d{8}$/.test(t)?"new":null}function $(t,n){if(!t||typeof t!="string")return {isValid:false,message:"\u5C45\u7559\u8B49\u865F\u5FC5\u9808\u70BA\u975E\u7A7A\u5B57\u4E32"};let s=t.trim().toUpperCase();if(n==="old"){let e=b(s);return {isValid:e,message:e?void 0:"\u7121\u6548\u7684\u820A\u5F0F\u5C45\u7559\u8B49\u865F"}}if(n==="new"){let e=C(s);return {isValid:e,message:e?void 0:"\u7121\u6548\u7684\u65B0\u5F0F\u5C45\u7559\u8B49\u865F"}}let i=M(s);if(!i)return {isValid:false,message:"\u7121\u6548\u7684\u5C45\u7559\u8B49\u865F\u683C\u5F0F"};let r=i==="old"?b(s):C(s);return {isValid:r,message:r?void 0:`\u7121\u6548\u7684${i==="old"?"\u820A\u5F0F":"\u65B0\u5F0F"}\u5C45\u7559\u8B49\u865F`}}function T(t){if(!t||typeof t!="string")return {isValid:false,message:"\u624B\u6A5F\u865F\u78BC\u5FC5\u9808\u70BA\u975E\u7A7A\u5B57\u4E32"};let n=t.trim().replace(/[\s\-()]/g,"");return /^09\d{8}$/.test(n)?{isValid:true}:{isValid:false,message:"\u624B\u6A5F\u865F\u78BC\u5FC5\u9808\u4EE509\u958B\u982D\u4E14\u70BA10\u4F4D\u6578\u5B57"}}function h(t){if(!t||typeof t!="string")return {isValid:false,message:"\u81EA\u7136\u4EBA\u6191\u8B49\u7DE8\u865F\u5FC5\u9808\u70BA\u975E\u7A7A\u5B57\u4E32"};let n=t.trim().toUpperCase();return /^[A-Z]{2}\d{14}$/.test(n)?{isValid:true}:{isValid:false,message:"\u81EA\u7136\u4EBA\u6191\u8B49\u7DE8\u865F\u5FC5\u9808\u70BA2\u500B\u82F1\u6587\u5B57\u6BCD\u52A0\u4E0A14\u4F4D\u6578\u5B57"}}function L(t){if(!t||typeof t!="string")return {isValid:false,message:"\u624B\u6A5F\u689D\u78BC\u5FC5\u9808\u70BA\u975E\u7A7A\u5B57\u4E32"};let n=t.trim().toUpperCase();return /^\/[A-Z0-9+.-]{7}$/.test(n)?{isValid:true}:{isValid:false,message:"\u624B\u6A5F\u689D\u78BC\u5FC5\u9808\u4EE5 / \u958B\u982D\uFF0C\u5F8C\u63A57\u500B\u6709\u6548\u5B57\u5143\uFF08A-Z\u30010-9\u3001+\u3001-\u3001.\uFF09"}}function w(t){if(!t||typeof t!="string")return {isValid:false,message:"\u6350\u8D08\u78BC\u5FC5\u9808\u70BA\u975E\u7A7A\u5B57\u4E32"};let n=t.trim();return /^\d{3,7}$/.test(n)?{isValid:true}:{isValid:false,message:"\u6350\u8D08\u78BC\u5FC5\u9808\u70BA3\u81F37\u4F4D\u6578\u5B57"}}exports.validateBusinessNumber=A;exports.validateCitizenCertificate=h;exports.validateEInvoiceDonationCode=w;exports.validateEInvoiceMobileBarcode=L;exports.validateMobilePhone=T;exports.validateNationalId=x;exports.validateResidentCertificate=$;//# sourceMappingURL=index.cjs.map
2
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/validators/national-id.ts","../src/validators/business-number.ts","../src/validators/resident-certificate.ts","../src/validators/mobile-phone.ts","../src/validators/citizen-certificate.ts","../src/validators/einvoice-mobile-barcode.ts","../src/validators/einvoice-donation-code.ts"],"names":["LETTER_MAPPING","validateOldFormat","id","letter","numbers","letterValue","d1","d2","weights","acc","digit","index","validateNewFormat","firstLetter","secondLetter","firstLetterValue","secondLetterValue","d3","d4","detectFormat","validateNationalId","format","normalizedId","isValid","detectedFormat","validateBusinessNumber","number","normalized","digits","sum","i","product","validateResidentCertificate","validateMobilePhone","phone","validateCitizenCertificate","certNumber","validateEInvoiceMobileBarcode","barcode","validateEInvoiceDonationCode","code"],"mappings":"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","file":"index.cjs","sourcesContent":["import type { ValidationResult, NationalIdType } from \"../types\";\n\n/**\n * 字母對應數字表(用於身分證字號驗證)\n */\nconst LETTER_MAPPING: Record<string, number> = {\n A: 10,\n B: 11,\n C: 12,\n D: 13,\n E: 14,\n F: 15,\n G: 16,\n H: 17,\n I: 34,\n J: 18,\n K: 19,\n L: 20,\n M: 21,\n N: 22,\n O: 35,\n P: 23,\n Q: 24,\n R: 25,\n S: 26,\n T: 27,\n U: 28,\n V: 29,\n W: 32,\n X: 30,\n Y: 31,\n Z: 33,\n};\n\n/**\n * 驗證舊式身分證字號格式(1個字母 + 9個數字)\n * 格式:A123456789\n * - 第一個字元:地區代碼(字母)\n * - 第二個字元:性別(1 = 男性,2 = 女性)\n * - 最後一個字元:檢查碼\n */\nfunction validateOldFormat(id: string): boolean {\n const pattern = /^[A-Z][12]\\d{8}$/;\n if (!pattern.test(id)) {\n return false;\n }\n\n const letter = id[0] as string;\n const numbers = id.slice(1);\n\n const letterValue = LETTER_MAPPING[letter] as number;\n\n // 計算檢查碼\n const d1 = Math.floor(letterValue / 10);\n const d2 = letterValue % 10;\n\n const weights = [1, 9, 8, 7, 6, 5, 4, 3, 2, 1, 1];\n const digits = [d1, d2, ...numbers.split(\"\").map(Number)];\n\n const sum = digits.reduce(\n (acc, digit, index) => acc + digit * weights[index]!,\n 0,\n );\n\n return sum % 10 === 0;\n}\n\n/**\n * 驗證新式身分證字號格式(2個字母 + 8個數字)\n * 格式:AA12345678\n * - 第一個字元:地區代碼(字母)\n * - 第二個字元:性別(8 = 男性,9 = 女性)以字母表示\n * - 最後一個字元:檢查碼\n */\nfunction validateNewFormat(id: string): boolean {\n const pattern = /^[A-Z]{2}\\d{8}$/;\n if (!pattern.test(id)) {\n return false;\n }\n\n const firstLetter = id[0] as string;\n const secondLetter = id[1] as string;\n const numbers = id.slice(2);\n\n const firstLetterValue = LETTER_MAPPING[firstLetter] as number;\n const secondLetterValue = LETTER_MAPPING[secondLetter] as number;\n\n // 計算新式格式的檢查碼\n const d1 = Math.floor(firstLetterValue / 10);\n const d2 = firstLetterValue % 10;\n const d3 = Math.floor(secondLetterValue / 10);\n const d4 = secondLetterValue % 10;\n\n const weights = [1, 9, 8, 7, 6, 5, 4, 3, 2, 1, 1, 1];\n const digits = [d1, d2, d3, d4, ...numbers.split(\"\").map(Number)];\n\n const sum = digits.reduce(\n (acc, digit, index) => acc + digit * weights[index]!,\n 0,\n );\n\n return sum % 10 === 0;\n}\n\n/**\n * 偵測身分證字號格式類型\n */\nfunction detectFormat(id: string): NationalIdType | null {\n if (/^[A-Z][12]\\d{8}$/.test(id)) {\n return \"old\";\n }\n if (/^[A-Z]{2}\\d{8}$/.test(id)) {\n return \"new\";\n }\n return null;\n}\n\n/**\n * 驗證台灣身分證字號(支援新舊格式)\n * @param id - 要驗證的身分證字號\n * @param format - 可選:指定格式類型('old' 或 'new')\n * @returns 驗證結果\n *\n * @example\n * ```typescript\n * validateNationalId('A123456789'); // 舊式格式\n * validateNationalId('AA12345678'); // 新式格式\n * ```\n */\nexport function validateNationalId(\n id: string,\n format?: NationalIdType,\n): ValidationResult {\n if (!id || typeof id !== \"string\") {\n return {\n isValid: false,\n message: \"身分證字號必須為非空字串\",\n };\n }\n\n const normalizedId = id.trim().toUpperCase();\n\n // 如果指定了格式,只驗證該格式\n if (format === \"old\") {\n const isValid = validateOldFormat(normalizedId);\n return {\n isValid,\n message: isValid ? undefined : \"無效的舊式身分證字號\",\n };\n }\n\n if (format === \"new\") {\n const isValid = validateNewFormat(normalizedId);\n return {\n isValid,\n message: isValid ? undefined : \"無效的新式身分證字號\",\n };\n }\n\n // 自動偵測格式\n const detectedFormat = detectFormat(normalizedId);\n\n if (!detectedFormat) {\n return {\n isValid: false,\n message: \"無效的身分證字號格式\",\n };\n }\n\n const isValid =\n detectedFormat === \"old\"\n ? validateOldFormat(normalizedId)\n : validateNewFormat(normalizedId);\n\n return {\n isValid,\n message: isValid\n ? undefined\n : `無效的${detectedFormat === \"old\" ? \"舊式\" : \"新式\"}身分證字號`,\n };\n}\n","import type { ValidationResult } from \"../types\";\n\n/**\n * 驗證台灣營利事業統一編號\n * 格式:8位數字\n * 使用加權檢查碼演算法\n *\n * @param number - 要驗證的統一編號\n * @returns 驗證結果\n *\n * @example\n * ```typescript\n * validateBusinessNumber('12345678');\n * ```\n */\nexport function validateBusinessNumber(number: string): ValidationResult {\n if (!number || typeof number !== \"string\") {\n return {\n isValid: false,\n message: \"統一編號必須為非空字串\",\n };\n }\n\n const normalized = number.trim();\n\n // 檢查是否為8位數字\n const pattern = /^\\d{8}$/;\n if (!pattern.test(normalized)) {\n return {\n isValid: false,\n message: \"統一編號必須為8位數字\",\n };\n }\n\n const digits = normalized.split(\"\").map(Number);\n const weights = [1, 2, 1, 2, 1, 2, 4, 1];\n\n // 計算加權總和\n let sum = 0;\n for (let i = 0; i < 8; i++) {\n let product = digits[i]! * weights[i]!;\n\n // 如果乘積為兩位數,將十位數和個位數相加\n if (product >= 10) {\n product = Math.floor(product / 10) + (product % 10);\n }\n\n sum += product;\n }\n\n // 特殊情況:第7位數字為7時\n // 如果第7位數字為7且總和除以10的餘數為1,也視為有效\n const isValid = sum % 10 === 0 || (digits[6]! === 7 && sum % 10 === 1);\n\n return {\n isValid,\n message: isValid ? undefined : \"統一編號檢查碼錯誤\",\n };\n}\n","import type { ValidationResult, ResidentCertificateType } from \"../types\";\n\n/**\n * 字母對應數字表(用於居留證號驗證)\n */\nconst LETTER_MAPPING: Record<string, number> = {\n A: 10,\n B: 11,\n C: 12,\n D: 13,\n E: 14,\n F: 15,\n G: 16,\n H: 17,\n I: 34,\n J: 18,\n K: 19,\n L: 20,\n M: 21,\n N: 22,\n O: 35,\n P: 23,\n Q: 24,\n R: 25,\n S: 26,\n T: 27,\n U: 28,\n V: 29,\n W: 32,\n X: 30,\n Y: 31,\n Z: 33,\n};\n\n/**\n * 驗證舊式居留證號格式(1個字母 + 9個數字)\n * 格式:A800000000\n * - 第一個字元:地區代碼(A、B、C 或 D 表示外國人士)\n * - 第二個字元:性別/類型(8 = 男性,9 = 女性)\n * - 最後一個字元:檢查碼\n */\nfunction validateOldFormat(id: string): boolean {\n const pattern = /^[A-D][89]\\d{8}$/;\n if (!pattern.test(id)) {\n return false;\n }\n\n const letter = id[0] as string;\n const numbers = id.slice(1);\n\n const letterValue = LETTER_MAPPING[letter] as number;\n\n // 計算檢查碼(與身分證字號相同的演算法)\n const d1 = Math.floor(letterValue / 10);\n const d2 = letterValue % 10;\n\n const weights = [1, 9, 8, 7, 6, 5, 4, 3, 2, 1, 1];\n const digits = [d1, d2, ...numbers.split(\"\").map(Number)];\n\n const sum = digits.reduce(\n (acc, digit, index) => acc + digit * weights[index]!,\n 0,\n );\n\n return sum % 10 === 0;\n}\n\n/**\n * 驗證新式居留證號格式(2個字母 + 8個數字)\n * 格式:AA12345678\n * - 第一個字元:地區代碼(字母)\n * - 第二個字元:性別(8 = 男性,9 = 女性)以字母表示\n * - 最後一個字元:檢查碼\n */\nfunction validateNewFormat(id: string): boolean {\n const pattern = /^[A-Z]{2}\\d{8}$/;\n if (!pattern.test(id)) {\n return false;\n }\n\n const firstLetter = id[0] as string;\n const secondLetter = id[1] as string;\n const numbers = id.slice(2);\n\n const firstLetterValue = LETTER_MAPPING[firstLetter] as number;\n const secondLetterValue = LETTER_MAPPING[secondLetter] as number;\n\n // 計算新式格式的檢查碼\n const d1 = Math.floor(firstLetterValue / 10);\n const d2 = firstLetterValue % 10;\n const d3 = Math.floor(secondLetterValue / 10);\n const d4 = secondLetterValue % 10;\n\n const weights = [1, 9, 8, 7, 6, 5, 4, 3, 2, 1, 1, 1];\n const digits = [d1, d2, d3, d4, ...numbers.split(\"\").map(Number)];\n\n const sum = digits.reduce(\n (acc, digit, index) => acc + digit * weights[index]!,\n 0,\n );\n\n return sum % 10 === 0;\n}\n\n/**\n * 偵測居留證號格式類型\n */\nfunction detectFormat(id: string): ResidentCertificateType | null {\n if (/^[A-D][89]\\d{8}$/.test(id)) {\n return \"old\";\n }\n if (/^[A-Z]{2}\\d{8}$/.test(id)) {\n return \"new\";\n }\n return null;\n}\n\n/**\n * 驗證台灣居留證號(支援新舊格式)\n * @param id - 要驗證的居留證號\n * @param format - 可選:指定格式類型('old' 或 'new')\n * @returns 驗證結果\n *\n * @example\n * ```typescript\n * validateResidentCertificate('A800000000'); // 舊式格式\n * validateResidentCertificate('AA12345678'); // 新式格式\n * ```\n */\nexport function validateResidentCertificate(\n id: string,\n format?: ResidentCertificateType,\n): ValidationResult {\n if (!id || typeof id !== \"string\") {\n return {\n isValid: false,\n message: \"居留證號必須為非空字串\",\n };\n }\n\n const normalizedId = id.trim().toUpperCase();\n\n // 如果指定了格式,只驗證該格式\n if (format === \"old\") {\n const isValid = validateOldFormat(normalizedId);\n return {\n isValid,\n message: isValid ? undefined : \"無效的舊式居留證號\",\n };\n }\n\n if (format === \"new\") {\n const isValid = validateNewFormat(normalizedId);\n return {\n isValid,\n message: isValid ? undefined : \"無效的新式居留證號\",\n };\n }\n\n // 自動偵測格式\n const detectedFormat = detectFormat(normalizedId);\n\n if (!detectedFormat) {\n return {\n isValid: false,\n message: \"無效的居留證號格式\",\n };\n }\n\n const isValid =\n detectedFormat === \"old\"\n ? validateOldFormat(normalizedId)\n : validateNewFormat(normalizedId);\n\n return {\n isValid,\n message: isValid\n ? undefined\n : `無效的${detectedFormat === \"old\" ? \"舊式\" : \"新式\"}居留證號`,\n };\n}\n","import type { ValidationResult } from \"../types\";\n\n/**\n * 驗證台灣手機號碼\n * 格式:09XXXXXXXX(10位數字,以09開頭)\n *\n * @param phone - 要驗證的手機號碼\n * @returns 驗證結果\n *\n * @example\n * ```typescript\n * validateMobilePhone('0912345678');\n * validateMobilePhone('0912-345-678'); // 含分隔符號\n * ```\n */\nexport function validateMobilePhone(phone: string): ValidationResult {\n if (!phone || typeof phone !== \"string\") {\n return {\n isValid: false,\n message: \"手機號碼必須為非空字串\",\n };\n }\n\n // 移除常見的分隔符號(空格、破折號、括號)\n const normalized = phone.trim().replace(/[\\s\\-()]/g, \"\");\n\n // 檢查是否符合台灣手機號碼格式\n // 以09開頭且總共10位數字\n const pattern = /^09\\d{8}$/;\n\n if (!pattern.test(normalized)) {\n return {\n isValid: false,\n message: \"手機號碼必須以09開頭且為10位數字\",\n };\n }\n\n return {\n isValid: true,\n };\n}\n","import type { ValidationResult } from \"../types\";\n\n/**\n * 驗證台灣自然人憑證編號\n * 格式:2個大寫英文字母 + 14位數字\n * 範例:AB12345678901234\n *\n * @param certNumber - 要驗證的自然人憑證編號\n * @returns 驗證結果\n *\n * @example\n * ```typescript\n * validateCitizenCertificate('AB12345678901234');\n * ```\n */\nexport function validateCitizenCertificate(\n certNumber: string,\n): ValidationResult {\n if (!certNumber || typeof certNumber !== \"string\") {\n return {\n isValid: false,\n message: \"自然人憑證編號必須為非空字串\",\n };\n }\n\n const normalized = certNumber.trim().toUpperCase();\n\n // 檢查格式:2個字母 + 14位數字\n const pattern = /^[A-Z]{2}\\d{14}$/;\n\n if (!pattern.test(normalized)) {\n return {\n isValid: false,\n message: \"自然人憑證編號必須為2個英文字母加上14位數字\",\n };\n }\n\n return {\n isValid: true,\n };\n}\n","import type { ValidationResult } from \"../types\";\n\n/**\n * 驗證台灣電子發票手機條碼\n * 格式:/ + 7個字元(大寫英文字母、數字、+、-、.)\n * 範例:/ABCD123\n *\n * 手機條碼用於將電子發票儲存在手機載具中\n *\n * @param barcode - 要驗證的手機條碼\n * @returns 驗證結果\n *\n * @example\n * ```typescript\n * validateEInvoiceMobileBarcode('/ABCD123');\n * validateEInvoiceMobileBarcode('/1234567');\n * ```\n */\nexport function validateEInvoiceMobileBarcode(\n barcode: string,\n): ValidationResult {\n if (!barcode || typeof barcode !== \"string\") {\n return {\n isValid: false,\n message: \"手機條碼必須為非空字串\",\n };\n }\n\n const normalized = barcode.trim().toUpperCase();\n\n // 檢查格式:以 / 開頭,後接7個字元\n // 有效字元:A-Z、0-9、+、-、.\n const pattern = /^\\/[A-Z0-9+.-]{7}$/;\n\n if (!pattern.test(normalized)) {\n return {\n isValid: false,\n message: \"手機條碼必須以 / 開頭,後接7個有效字元(A-Z、0-9、+、-、.)\",\n };\n }\n\n return {\n isValid: true,\n };\n}\n","import type { ValidationResult } from \"../types\";\n\n/**\n * 驗證台灣電子發票捐贈碼\n * 格式:3-7位數字\n * 範例:123、12345、1234567\n *\n * 捐贈碼用於將電子發票捐贈給已註冊的慈善機構\n *\n * @param code - 要驗證的捐贈碼\n * @returns 驗證結果\n *\n * @example\n * ```typescript\n * validateEInvoiceDonationCode('123');\n * validateEInvoiceDonationCode('12345');\n * ```\n */\nexport function validateEInvoiceDonationCode(code: string): ValidationResult {\n if (!code || typeof code !== \"string\") {\n return {\n isValid: false,\n message: \"捐贈碼必須為非空字串\",\n };\n }\n\n const normalized = code.trim();\n\n // 檢查格式:3-7位數字\n const pattern = /^\\d{3,7}$/;\n\n if (!pattern.test(normalized)) {\n return {\n isValid: false,\n message: \"捐贈碼必須為3至7位數字\",\n };\n }\n\n return {\n isValid: true,\n };\n}\n"]}
@@ -0,0 +1,23 @@
1
+ interface ValidationResult {
2
+ isValid: boolean;
3
+ message?: string | undefined;
4
+ }
5
+ type Gender = "male" | "female";
6
+ type ResidentCertificateType = "old" | "new";
7
+ type NationalIdType = "old" | "new";
8
+
9
+ declare function validateNationalId(id: string, format?: NationalIdType): ValidationResult;
10
+
11
+ declare function validateBusinessNumber(number: string): ValidationResult;
12
+
13
+ declare function validateResidentCertificate(id: string, format?: ResidentCertificateType): ValidationResult;
14
+
15
+ declare function validateMobilePhone(phone: string): ValidationResult;
16
+
17
+ declare function validateCitizenCertificate(certNumber: string): ValidationResult;
18
+
19
+ declare function validateEInvoiceMobileBarcode(barcode: string): ValidationResult;
20
+
21
+ declare function validateEInvoiceDonationCode(code: string): ValidationResult;
22
+
23
+ export { type Gender, type NationalIdType, type ResidentCertificateType, type ValidationResult, validateBusinessNumber, validateCitizenCertificate, validateEInvoiceDonationCode, validateEInvoiceMobileBarcode, validateMobilePhone, validateNationalId, validateResidentCertificate };
@@ -0,0 +1,23 @@
1
+ interface ValidationResult {
2
+ isValid: boolean;
3
+ message?: string | undefined;
4
+ }
5
+ type Gender = "male" | "female";
6
+ type ResidentCertificateType = "old" | "new";
7
+ type NationalIdType = "old" | "new";
8
+
9
+ declare function validateNationalId(id: string, format?: NationalIdType): ValidationResult;
10
+
11
+ declare function validateBusinessNumber(number: string): ValidationResult;
12
+
13
+ declare function validateResidentCertificate(id: string, format?: ResidentCertificateType): ValidationResult;
14
+
15
+ declare function validateMobilePhone(phone: string): ValidationResult;
16
+
17
+ declare function validateCitizenCertificate(certNumber: string): ValidationResult;
18
+
19
+ declare function validateEInvoiceMobileBarcode(barcode: string): ValidationResult;
20
+
21
+ declare function validateEInvoiceDonationCode(code: string): ValidationResult;
22
+
23
+ export { type Gender, type NationalIdType, type ResidentCertificateType, type ValidationResult, validateBusinessNumber, validateCitizenCertificate, validateEInvoiceDonationCode, validateEInvoiceMobileBarcode, validateMobilePhone, validateNationalId, validateResidentCertificate };
package/dist/index.mjs ADDED
@@ -0,0 +1,2 @@
1
+ var V={A:10,B:11,C:12,D:13,E:14,F:15,G:16,H:17,I:34,J:18,K:19,L:20,M:21,N:22,O:35,P:23,Q:24,R:25,S:26,T:27,U:28,V:29,W:32,X:30,Y:31,Z:33};function R(t){if(!/^[A-Z][12]\d{8}$/.test(t))return false;let s=t[0],i=t.slice(1),r=V[s],e=Math.floor(r/10),a=r%10,o=[1,9,8,7,6,5,4,3,2,1,1];return [e,a,...i.split("").map(Number)].reduce((d,u,f)=>d+u*o[f],0)%10===0}function v(t){if(!/^[A-Z]{2}\d{8}$/.test(t))return false;let s=t[0],i=t[1],r=t.slice(2),e=V[s],a=V[i],o=Math.floor(e/10),l=e%10,c=Math.floor(a/10),d=a%10,u=[1,9,8,7,6,5,4,3,2,1,1,1];return [o,l,c,d,...r.split("").map(Number)].reduce((m,p,g)=>m+p*u[g],0)%10===0}function I(t){return /^[A-Z][12]\d{8}$/.test(t)?"old":/^[A-Z]{2}\d{8}$/.test(t)?"new":null}function x(t,n){if(!t||typeof t!="string")return {isValid:false,message:"\u8EAB\u5206\u8B49\u5B57\u865F\u5FC5\u9808\u70BA\u975E\u7A7A\u5B57\u4E32"};let s=t.trim().toUpperCase();if(n==="old"){let e=R(s);return {isValid:e,message:e?void 0:"\u7121\u6548\u7684\u820A\u5F0F\u8EAB\u5206\u8B49\u5B57\u865F"}}if(n==="new"){let e=v(s);return {isValid:e,message:e?void 0:"\u7121\u6548\u7684\u65B0\u5F0F\u8EAB\u5206\u8B49\u5B57\u865F"}}let i=I(s);if(!i)return {isValid:false,message:"\u7121\u6548\u7684\u8EAB\u5206\u8B49\u5B57\u865F\u683C\u5F0F"};let r=i==="old"?R(s):v(s);return {isValid:r,message:r?void 0:`\u7121\u6548\u7684${i==="old"?"\u820A\u5F0F":"\u65B0\u5F0F"}\u8EAB\u5206\u8B49\u5B57\u865F`}}function A(t){if(!t||typeof t!="string")return {isValid:false,message:"\u7D71\u4E00\u7DE8\u865F\u5FC5\u9808\u70BA\u975E\u7A7A\u5B57\u4E32"};let n=t.trim();if(!/^\d{8}$/.test(n))return {isValid:false,message:"\u7D71\u4E00\u7DE8\u865F\u5FC5\u9808\u70BA8\u4F4D\u6578\u5B57"};let i=n.split("").map(Number),r=[1,2,1,2,1,2,4,1],e=0;for(let o=0;o<8;o++){let l=i[o]*r[o];l>=10&&(l=Math.floor(l/10)+l%10),e+=l;}let a=e%10===0||i[6]===7&&e%10===1;return {isValid:a,message:a?void 0:"\u7D71\u4E00\u7DE8\u865F\u6AA2\u67E5\u78BC\u932F\u8AA4"}}var y={A:10,B:11,C:12,D:13,E:14,F:15,G:16,H:17,I:34,J:18,K:19,L:20,M:21,N:22,O:35,P:23,Q:24,R:25,S:26,T:27,U:28,V:29,W:32,X:30,Y:31,Z:33};function b(t){if(!/^[A-D][89]\d{8}$/.test(t))return false;let s=t[0],i=t.slice(1),r=y[s],e=Math.floor(r/10),a=r%10,o=[1,9,8,7,6,5,4,3,2,1,1];return [e,a,...i.split("").map(Number)].reduce((d,u,f)=>d+u*o[f],0)%10===0}function C(t){if(!/^[A-Z]{2}\d{8}$/.test(t))return false;let s=t[0],i=t[1],r=t.slice(2),e=y[s],a=y[i],o=Math.floor(e/10),l=e%10,c=Math.floor(a/10),d=a%10,u=[1,9,8,7,6,5,4,3,2,1,1,1];return [o,l,c,d,...r.split("").map(Number)].reduce((m,p,g)=>m+p*u[g],0)%10===0}function M(t){return /^[A-D][89]\d{8}$/.test(t)?"old":/^[A-Z]{2}\d{8}$/.test(t)?"new":null}function $(t,n){if(!t||typeof t!="string")return {isValid:false,message:"\u5C45\u7559\u8B49\u865F\u5FC5\u9808\u70BA\u975E\u7A7A\u5B57\u4E32"};let s=t.trim().toUpperCase();if(n==="old"){let e=b(s);return {isValid:e,message:e?void 0:"\u7121\u6548\u7684\u820A\u5F0F\u5C45\u7559\u8B49\u865F"}}if(n==="new"){let e=C(s);return {isValid:e,message:e?void 0:"\u7121\u6548\u7684\u65B0\u5F0F\u5C45\u7559\u8B49\u865F"}}let i=M(s);if(!i)return {isValid:false,message:"\u7121\u6548\u7684\u5C45\u7559\u8B49\u865F\u683C\u5F0F"};let r=i==="old"?b(s):C(s);return {isValid:r,message:r?void 0:`\u7121\u6548\u7684${i==="old"?"\u820A\u5F0F":"\u65B0\u5F0F"}\u5C45\u7559\u8B49\u865F`}}function T(t){if(!t||typeof t!="string")return {isValid:false,message:"\u624B\u6A5F\u865F\u78BC\u5FC5\u9808\u70BA\u975E\u7A7A\u5B57\u4E32"};let n=t.trim().replace(/[\s\-()]/g,"");return /^09\d{8}$/.test(n)?{isValid:true}:{isValid:false,message:"\u624B\u6A5F\u865F\u78BC\u5FC5\u9808\u4EE509\u958B\u982D\u4E14\u70BA10\u4F4D\u6578\u5B57"}}function h(t){if(!t||typeof t!="string")return {isValid:false,message:"\u81EA\u7136\u4EBA\u6191\u8B49\u7DE8\u865F\u5FC5\u9808\u70BA\u975E\u7A7A\u5B57\u4E32"};let n=t.trim().toUpperCase();return /^[A-Z]{2}\d{14}$/.test(n)?{isValid:true}:{isValid:false,message:"\u81EA\u7136\u4EBA\u6191\u8B49\u7DE8\u865F\u5FC5\u9808\u70BA2\u500B\u82F1\u6587\u5B57\u6BCD\u52A0\u4E0A14\u4F4D\u6578\u5B57"}}function L(t){if(!t||typeof t!="string")return {isValid:false,message:"\u624B\u6A5F\u689D\u78BC\u5FC5\u9808\u70BA\u975E\u7A7A\u5B57\u4E32"};let n=t.trim().toUpperCase();return /^\/[A-Z0-9+.-]{7}$/.test(n)?{isValid:true}:{isValid:false,message:"\u624B\u6A5F\u689D\u78BC\u5FC5\u9808\u4EE5 / \u958B\u982D\uFF0C\u5F8C\u63A57\u500B\u6709\u6548\u5B57\u5143\uFF08A-Z\u30010-9\u3001+\u3001-\u3001.\uFF09"}}function w(t){if(!t||typeof t!="string")return {isValid:false,message:"\u6350\u8D08\u78BC\u5FC5\u9808\u70BA\u975E\u7A7A\u5B57\u4E32"};let n=t.trim();return /^\d{3,7}$/.test(n)?{isValid:true}:{isValid:false,message:"\u6350\u8D08\u78BC\u5FC5\u9808\u70BA3\u81F37\u4F4D\u6578\u5B57"}}export{A as validateBusinessNumber,h as validateCitizenCertificate,w as validateEInvoiceDonationCode,L as validateEInvoiceMobileBarcode,T as validateMobilePhone,x as validateNationalId,$ as validateResidentCertificate};//# sourceMappingURL=index.mjs.map
2
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/validators/national-id.ts","../src/validators/business-number.ts","../src/validators/resident-certificate.ts","../src/validators/mobile-phone.ts","../src/validators/citizen-certificate.ts","../src/validators/einvoice-mobile-barcode.ts","../src/validators/einvoice-donation-code.ts"],"names":["LETTER_MAPPING","validateOldFormat","id","letter","numbers","letterValue","d1","d2","weights","acc","digit","index","validateNewFormat","firstLetter","secondLetter","firstLetterValue","secondLetterValue","d3","d4","detectFormat","validateNationalId","format","normalizedId","isValid","detectedFormat","validateBusinessNumber","number","normalized","digits","sum","i","product","validateResidentCertificate","validateMobilePhone","phone","validateCitizenCertificate","certNumber","validateEInvoiceMobileBarcode","barcode","validateEInvoiceDonationCode","code"],"mappings":"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","file":"index.mjs","sourcesContent":["import type { ValidationResult, NationalIdType } from \"../types\";\n\n/**\n * 字母對應數字表(用於身分證字號驗證)\n */\nconst LETTER_MAPPING: Record<string, number> = {\n A: 10,\n B: 11,\n C: 12,\n D: 13,\n E: 14,\n F: 15,\n G: 16,\n H: 17,\n I: 34,\n J: 18,\n K: 19,\n L: 20,\n M: 21,\n N: 22,\n O: 35,\n P: 23,\n Q: 24,\n R: 25,\n S: 26,\n T: 27,\n U: 28,\n V: 29,\n W: 32,\n X: 30,\n Y: 31,\n Z: 33,\n};\n\n/**\n * 驗證舊式身分證字號格式(1個字母 + 9個數字)\n * 格式:A123456789\n * - 第一個字元:地區代碼(字母)\n * - 第二個字元:性別(1 = 男性,2 = 女性)\n * - 最後一個字元:檢查碼\n */\nfunction validateOldFormat(id: string): boolean {\n const pattern = /^[A-Z][12]\\d{8}$/;\n if (!pattern.test(id)) {\n return false;\n }\n\n const letter = id[0] as string;\n const numbers = id.slice(1);\n\n const letterValue = LETTER_MAPPING[letter] as number;\n\n // 計算檢查碼\n const d1 = Math.floor(letterValue / 10);\n const d2 = letterValue % 10;\n\n const weights = [1, 9, 8, 7, 6, 5, 4, 3, 2, 1, 1];\n const digits = [d1, d2, ...numbers.split(\"\").map(Number)];\n\n const sum = digits.reduce(\n (acc, digit, index) => acc + digit * weights[index]!,\n 0,\n );\n\n return sum % 10 === 0;\n}\n\n/**\n * 驗證新式身分證字號格式(2個字母 + 8個數字)\n * 格式:AA12345678\n * - 第一個字元:地區代碼(字母)\n * - 第二個字元:性別(8 = 男性,9 = 女性)以字母表示\n * - 最後一個字元:檢查碼\n */\nfunction validateNewFormat(id: string): boolean {\n const pattern = /^[A-Z]{2}\\d{8}$/;\n if (!pattern.test(id)) {\n return false;\n }\n\n const firstLetter = id[0] as string;\n const secondLetter = id[1] as string;\n const numbers = id.slice(2);\n\n const firstLetterValue = LETTER_MAPPING[firstLetter] as number;\n const secondLetterValue = LETTER_MAPPING[secondLetter] as number;\n\n // 計算新式格式的檢查碼\n const d1 = Math.floor(firstLetterValue / 10);\n const d2 = firstLetterValue % 10;\n const d3 = Math.floor(secondLetterValue / 10);\n const d4 = secondLetterValue % 10;\n\n const weights = [1, 9, 8, 7, 6, 5, 4, 3, 2, 1, 1, 1];\n const digits = [d1, d2, d3, d4, ...numbers.split(\"\").map(Number)];\n\n const sum = digits.reduce(\n (acc, digit, index) => acc + digit * weights[index]!,\n 0,\n );\n\n return sum % 10 === 0;\n}\n\n/**\n * 偵測身分證字號格式類型\n */\nfunction detectFormat(id: string): NationalIdType | null {\n if (/^[A-Z][12]\\d{8}$/.test(id)) {\n return \"old\";\n }\n if (/^[A-Z]{2}\\d{8}$/.test(id)) {\n return \"new\";\n }\n return null;\n}\n\n/**\n * 驗證台灣身分證字號(支援新舊格式)\n * @param id - 要驗證的身分證字號\n * @param format - 可選:指定格式類型('old' 或 'new')\n * @returns 驗證結果\n *\n * @example\n * ```typescript\n * validateNationalId('A123456789'); // 舊式格式\n * validateNationalId('AA12345678'); // 新式格式\n * ```\n */\nexport function validateNationalId(\n id: string,\n format?: NationalIdType,\n): ValidationResult {\n if (!id || typeof id !== \"string\") {\n return {\n isValid: false,\n message: \"身分證字號必須為非空字串\",\n };\n }\n\n const normalizedId = id.trim().toUpperCase();\n\n // 如果指定了格式,只驗證該格式\n if (format === \"old\") {\n const isValid = validateOldFormat(normalizedId);\n return {\n isValid,\n message: isValid ? undefined : \"無效的舊式身分證字號\",\n };\n }\n\n if (format === \"new\") {\n const isValid = validateNewFormat(normalizedId);\n return {\n isValid,\n message: isValid ? undefined : \"無效的新式身分證字號\",\n };\n }\n\n // 自動偵測格式\n const detectedFormat = detectFormat(normalizedId);\n\n if (!detectedFormat) {\n return {\n isValid: false,\n message: \"無效的身分證字號格式\",\n };\n }\n\n const isValid =\n detectedFormat === \"old\"\n ? validateOldFormat(normalizedId)\n : validateNewFormat(normalizedId);\n\n return {\n isValid,\n message: isValid\n ? undefined\n : `無效的${detectedFormat === \"old\" ? \"舊式\" : \"新式\"}身分證字號`,\n };\n}\n","import type { ValidationResult } from \"../types\";\n\n/**\n * 驗證台灣營利事業統一編號\n * 格式:8位數字\n * 使用加權檢查碼演算法\n *\n * @param number - 要驗證的統一編號\n * @returns 驗證結果\n *\n * @example\n * ```typescript\n * validateBusinessNumber('12345678');\n * ```\n */\nexport function validateBusinessNumber(number: string): ValidationResult {\n if (!number || typeof number !== \"string\") {\n return {\n isValid: false,\n message: \"統一編號必須為非空字串\",\n };\n }\n\n const normalized = number.trim();\n\n // 檢查是否為8位數字\n const pattern = /^\\d{8}$/;\n if (!pattern.test(normalized)) {\n return {\n isValid: false,\n message: \"統一編號必須為8位數字\",\n };\n }\n\n const digits = normalized.split(\"\").map(Number);\n const weights = [1, 2, 1, 2, 1, 2, 4, 1];\n\n // 計算加權總和\n let sum = 0;\n for (let i = 0; i < 8; i++) {\n let product = digits[i]! * weights[i]!;\n\n // 如果乘積為兩位數,將十位數和個位數相加\n if (product >= 10) {\n product = Math.floor(product / 10) + (product % 10);\n }\n\n sum += product;\n }\n\n // 特殊情況:第7位數字為7時\n // 如果第7位數字為7且總和除以10的餘數為1,也視為有效\n const isValid = sum % 10 === 0 || (digits[6]! === 7 && sum % 10 === 1);\n\n return {\n isValid,\n message: isValid ? undefined : \"統一編號檢查碼錯誤\",\n };\n}\n","import type { ValidationResult, ResidentCertificateType } from \"../types\";\n\n/**\n * 字母對應數字表(用於居留證號驗證)\n */\nconst LETTER_MAPPING: Record<string, number> = {\n A: 10,\n B: 11,\n C: 12,\n D: 13,\n E: 14,\n F: 15,\n G: 16,\n H: 17,\n I: 34,\n J: 18,\n K: 19,\n L: 20,\n M: 21,\n N: 22,\n O: 35,\n P: 23,\n Q: 24,\n R: 25,\n S: 26,\n T: 27,\n U: 28,\n V: 29,\n W: 32,\n X: 30,\n Y: 31,\n Z: 33,\n};\n\n/**\n * 驗證舊式居留證號格式(1個字母 + 9個數字)\n * 格式:A800000000\n * - 第一個字元:地區代碼(A、B、C 或 D 表示外國人士)\n * - 第二個字元:性別/類型(8 = 男性,9 = 女性)\n * - 最後一個字元:檢查碼\n */\nfunction validateOldFormat(id: string): boolean {\n const pattern = /^[A-D][89]\\d{8}$/;\n if (!pattern.test(id)) {\n return false;\n }\n\n const letter = id[0] as string;\n const numbers = id.slice(1);\n\n const letterValue = LETTER_MAPPING[letter] as number;\n\n // 計算檢查碼(與身分證字號相同的演算法)\n const d1 = Math.floor(letterValue / 10);\n const d2 = letterValue % 10;\n\n const weights = [1, 9, 8, 7, 6, 5, 4, 3, 2, 1, 1];\n const digits = [d1, d2, ...numbers.split(\"\").map(Number)];\n\n const sum = digits.reduce(\n (acc, digit, index) => acc + digit * weights[index]!,\n 0,\n );\n\n return sum % 10 === 0;\n}\n\n/**\n * 驗證新式居留證號格式(2個字母 + 8個數字)\n * 格式:AA12345678\n * - 第一個字元:地區代碼(字母)\n * - 第二個字元:性別(8 = 男性,9 = 女性)以字母表示\n * - 最後一個字元:檢查碼\n */\nfunction validateNewFormat(id: string): boolean {\n const pattern = /^[A-Z]{2}\\d{8}$/;\n if (!pattern.test(id)) {\n return false;\n }\n\n const firstLetter = id[0] as string;\n const secondLetter = id[1] as string;\n const numbers = id.slice(2);\n\n const firstLetterValue = LETTER_MAPPING[firstLetter] as number;\n const secondLetterValue = LETTER_MAPPING[secondLetter] as number;\n\n // 計算新式格式的檢查碼\n const d1 = Math.floor(firstLetterValue / 10);\n const d2 = firstLetterValue % 10;\n const d3 = Math.floor(secondLetterValue / 10);\n const d4 = secondLetterValue % 10;\n\n const weights = [1, 9, 8, 7, 6, 5, 4, 3, 2, 1, 1, 1];\n const digits = [d1, d2, d3, d4, ...numbers.split(\"\").map(Number)];\n\n const sum = digits.reduce(\n (acc, digit, index) => acc + digit * weights[index]!,\n 0,\n );\n\n return sum % 10 === 0;\n}\n\n/**\n * 偵測居留證號格式類型\n */\nfunction detectFormat(id: string): ResidentCertificateType | null {\n if (/^[A-D][89]\\d{8}$/.test(id)) {\n return \"old\";\n }\n if (/^[A-Z]{2}\\d{8}$/.test(id)) {\n return \"new\";\n }\n return null;\n}\n\n/**\n * 驗證台灣居留證號(支援新舊格式)\n * @param id - 要驗證的居留證號\n * @param format - 可選:指定格式類型('old' 或 'new')\n * @returns 驗證結果\n *\n * @example\n * ```typescript\n * validateResidentCertificate('A800000000'); // 舊式格式\n * validateResidentCertificate('AA12345678'); // 新式格式\n * ```\n */\nexport function validateResidentCertificate(\n id: string,\n format?: ResidentCertificateType,\n): ValidationResult {\n if (!id || typeof id !== \"string\") {\n return {\n isValid: false,\n message: \"居留證號必須為非空字串\",\n };\n }\n\n const normalizedId = id.trim().toUpperCase();\n\n // 如果指定了格式,只驗證該格式\n if (format === \"old\") {\n const isValid = validateOldFormat(normalizedId);\n return {\n isValid,\n message: isValid ? undefined : \"無效的舊式居留證號\",\n };\n }\n\n if (format === \"new\") {\n const isValid = validateNewFormat(normalizedId);\n return {\n isValid,\n message: isValid ? undefined : \"無效的新式居留證號\",\n };\n }\n\n // 自動偵測格式\n const detectedFormat = detectFormat(normalizedId);\n\n if (!detectedFormat) {\n return {\n isValid: false,\n message: \"無效的居留證號格式\",\n };\n }\n\n const isValid =\n detectedFormat === \"old\"\n ? validateOldFormat(normalizedId)\n : validateNewFormat(normalizedId);\n\n return {\n isValid,\n message: isValid\n ? undefined\n : `無效的${detectedFormat === \"old\" ? \"舊式\" : \"新式\"}居留證號`,\n };\n}\n","import type { ValidationResult } from \"../types\";\n\n/**\n * 驗證台灣手機號碼\n * 格式:09XXXXXXXX(10位數字,以09開頭)\n *\n * @param phone - 要驗證的手機號碼\n * @returns 驗證結果\n *\n * @example\n * ```typescript\n * validateMobilePhone('0912345678');\n * validateMobilePhone('0912-345-678'); // 含分隔符號\n * ```\n */\nexport function validateMobilePhone(phone: string): ValidationResult {\n if (!phone || typeof phone !== \"string\") {\n return {\n isValid: false,\n message: \"手機號碼必須為非空字串\",\n };\n }\n\n // 移除常見的分隔符號(空格、破折號、括號)\n const normalized = phone.trim().replace(/[\\s\\-()]/g, \"\");\n\n // 檢查是否符合台灣手機號碼格式\n // 以09開頭且總共10位數字\n const pattern = /^09\\d{8}$/;\n\n if (!pattern.test(normalized)) {\n return {\n isValid: false,\n message: \"手機號碼必須以09開頭且為10位數字\",\n };\n }\n\n return {\n isValid: true,\n };\n}\n","import type { ValidationResult } from \"../types\";\n\n/**\n * 驗證台灣自然人憑證編號\n * 格式:2個大寫英文字母 + 14位數字\n * 範例:AB12345678901234\n *\n * @param certNumber - 要驗證的自然人憑證編號\n * @returns 驗證結果\n *\n * @example\n * ```typescript\n * validateCitizenCertificate('AB12345678901234');\n * ```\n */\nexport function validateCitizenCertificate(\n certNumber: string,\n): ValidationResult {\n if (!certNumber || typeof certNumber !== \"string\") {\n return {\n isValid: false,\n message: \"自然人憑證編號必須為非空字串\",\n };\n }\n\n const normalized = certNumber.trim().toUpperCase();\n\n // 檢查格式:2個字母 + 14位數字\n const pattern = /^[A-Z]{2}\\d{14}$/;\n\n if (!pattern.test(normalized)) {\n return {\n isValid: false,\n message: \"自然人憑證編號必須為2個英文字母加上14位數字\",\n };\n }\n\n return {\n isValid: true,\n };\n}\n","import type { ValidationResult } from \"../types\";\n\n/**\n * 驗證台灣電子發票手機條碼\n * 格式:/ + 7個字元(大寫英文字母、數字、+、-、.)\n * 範例:/ABCD123\n *\n * 手機條碼用於將電子發票儲存在手機載具中\n *\n * @param barcode - 要驗證的手機條碼\n * @returns 驗證結果\n *\n * @example\n * ```typescript\n * validateEInvoiceMobileBarcode('/ABCD123');\n * validateEInvoiceMobileBarcode('/1234567');\n * ```\n */\nexport function validateEInvoiceMobileBarcode(\n barcode: string,\n): ValidationResult {\n if (!barcode || typeof barcode !== \"string\") {\n return {\n isValid: false,\n message: \"手機條碼必須為非空字串\",\n };\n }\n\n const normalized = barcode.trim().toUpperCase();\n\n // 檢查格式:以 / 開頭,後接7個字元\n // 有效字元:A-Z、0-9、+、-、.\n const pattern = /^\\/[A-Z0-9+.-]{7}$/;\n\n if (!pattern.test(normalized)) {\n return {\n isValid: false,\n message: \"手機條碼必須以 / 開頭,後接7個有效字元(A-Z、0-9、+、-、.)\",\n };\n }\n\n return {\n isValid: true,\n };\n}\n","import type { ValidationResult } from \"../types\";\n\n/**\n * 驗證台灣電子發票捐贈碼\n * 格式:3-7位數字\n * 範例:123、12345、1234567\n *\n * 捐贈碼用於將電子發票捐贈給已註冊的慈善機構\n *\n * @param code - 要驗證的捐贈碼\n * @returns 驗證結果\n *\n * @example\n * ```typescript\n * validateEInvoiceDonationCode('123');\n * validateEInvoiceDonationCode('12345');\n * ```\n */\nexport function validateEInvoiceDonationCode(code: string): ValidationResult {\n if (!code || typeof code !== \"string\") {\n return {\n isValid: false,\n message: \"捐贈碼必須為非空字串\",\n };\n }\n\n const normalized = code.trim();\n\n // 檢查格式:3-7位數字\n const pattern = /^\\d{3,7}$/;\n\n if (!pattern.test(normalized)) {\n return {\n isValid: false,\n message: \"捐贈碼必須為3至7位數字\",\n };\n }\n\n return {\n isValid: true,\n };\n}\n"]}