finprim 0.1.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/dist/index.mjs ADDED
@@ -0,0 +1,326 @@
1
+ // src/iban.ts
2
+ var IBAN_LENGTHS = {
3
+ AL: 28,
4
+ AD: 24,
5
+ AT: 20,
6
+ AZ: 28,
7
+ BH: 22,
8
+ BE: 16,
9
+ BA: 20,
10
+ BR: 29,
11
+ BG: 22,
12
+ CR: 22,
13
+ HR: 21,
14
+ CY: 28,
15
+ CZ: 24,
16
+ DK: 18,
17
+ DO: 28,
18
+ EE: 20,
19
+ FI: 18,
20
+ FR: 27,
21
+ GE: 22,
22
+ DE: 22,
23
+ GI: 23,
24
+ GR: 27,
25
+ GT: 28,
26
+ HU: 28,
27
+ IS: 26,
28
+ IE: 22,
29
+ IL: 23,
30
+ IT: 27,
31
+ JO: 30,
32
+ KZ: 20,
33
+ KW: 30,
34
+ LV: 21,
35
+ LB: 28,
36
+ LI: 21,
37
+ LT: 20,
38
+ LU: 20,
39
+ MK: 19,
40
+ MT: 31,
41
+ MR: 27,
42
+ MU: 30,
43
+ MC: 27,
44
+ MD: 24,
45
+ ME: 22,
46
+ NL: 18,
47
+ NO: 15,
48
+ PK: 24,
49
+ PS: 29,
50
+ PL: 28,
51
+ PT: 25,
52
+ QA: 29,
53
+ RO: 24,
54
+ SM: 27,
55
+ SA: 24,
56
+ RS: 22,
57
+ SK: 24,
58
+ SI: 19,
59
+ ES: 24,
60
+ SE: 24,
61
+ CH: 21,
62
+ TN: 24,
63
+ TR: 26,
64
+ AE: 23,
65
+ GB: 22,
66
+ VG: 24
67
+ };
68
+ function mod97(value) {
69
+ let remainder = 0;
70
+ for (const char of value) {
71
+ remainder = (remainder * 10 + parseInt(char, 10)) % 97;
72
+ }
73
+ return remainder;
74
+ }
75
+ function ibanToDigits(iban) {
76
+ const rearranged = iban.slice(4) + iban.slice(0, 4);
77
+ return rearranged.split("").map((char) => {
78
+ const code = char.charCodeAt(0);
79
+ return code >= 65 && code <= 90 ? (code - 55).toString() : char;
80
+ }).join("");
81
+ }
82
+ function formatIBANString(iban) {
83
+ return iban.replace(/(.{4})/g, "$1 ").trim();
84
+ }
85
+ function validateIBAN(input) {
86
+ if (!input || typeof input !== "string") {
87
+ return { valid: false, error: "Input must be a non-empty string" };
88
+ }
89
+ const cleaned = input.replace(/\s/g, "").toUpperCase();
90
+ if (cleaned.length < 4) {
91
+ return { valid: false, error: "IBAN is too short" };
92
+ }
93
+ const countryCode = cleaned.slice(0, 2);
94
+ if (!/^[A-Z]{2}$/.test(countryCode)) {
95
+ return { valid: false, error: "IBAN must start with a 2-letter country code" };
96
+ }
97
+ const expectedLength = IBAN_LENGTHS[countryCode];
98
+ if (!expectedLength) {
99
+ return { valid: false, error: `Unsupported country code: ${countryCode}` };
100
+ }
101
+ if (cleaned.length !== expectedLength) {
102
+ return {
103
+ valid: false,
104
+ error: `Invalid length for ${countryCode} IBAN. Expected ${expectedLength} characters, got ${cleaned.length}`
105
+ };
106
+ }
107
+ if (!/^[A-Z0-9]+$/.test(cleaned)) {
108
+ return { valid: false, error: "IBAN contains invalid characters" };
109
+ }
110
+ const digits = ibanToDigits(cleaned);
111
+ if (mod97(digits) !== 1) {
112
+ return { valid: false, error: "IBAN checksum is invalid" };
113
+ }
114
+ return {
115
+ valid: true,
116
+ value: cleaned,
117
+ formatted: formatIBANString(cleaned)
118
+ };
119
+ }
120
+
121
+ // src/sortcode.ts
122
+ function validateUKSortCode(input) {
123
+ if (!input || typeof input !== "string") {
124
+ return { valid: false, error: "Input must be a non-empty string" };
125
+ }
126
+ const cleaned = input.replace(/[-\s]/g, "");
127
+ if (!/^\d{6}$/.test(cleaned)) {
128
+ return {
129
+ valid: false,
130
+ error: "Sort code must be exactly 6 digits. Accepted formats: 60-16-13, 601613, 60 16 13"
131
+ };
132
+ }
133
+ const formatted = `${cleaned.slice(0, 2)}-${cleaned.slice(2, 4)}-${cleaned.slice(4, 6)}`;
134
+ return {
135
+ valid: true,
136
+ value: cleaned,
137
+ formatted
138
+ };
139
+ }
140
+ function validateUKAccountNumber(input) {
141
+ if (!input || typeof input !== "string") {
142
+ return { valid: false, error: "Input must be a non-empty string" };
143
+ }
144
+ const cleaned = input.replace(/\s/g, "");
145
+ if (!/^\d{8}$/.test(cleaned)) {
146
+ return {
147
+ valid: false,
148
+ error: "UK account number must be exactly 8 digits"
149
+ };
150
+ }
151
+ const formatted = `${cleaned.slice(0, 4)} ${cleaned.slice(4, 8)}`;
152
+ return {
153
+ valid: true,
154
+ value: cleaned,
155
+ formatted
156
+ };
157
+ }
158
+
159
+ // src/currency.ts
160
+ var SUPPORTED_CURRENCIES = [
161
+ "GBP",
162
+ "EUR",
163
+ "USD",
164
+ "JPY",
165
+ "CHF",
166
+ "CAD",
167
+ "AUD",
168
+ "NZD"
169
+ ];
170
+ var CURRENCY_LOCALES = {
171
+ GBP: "en-GB",
172
+ EUR: "de-DE",
173
+ USD: "en-US",
174
+ JPY: "ja-JP",
175
+ CHF: "de-CH",
176
+ CAD: "en-CA",
177
+ AUD: "en-AU",
178
+ NZD: "en-NZ"
179
+ };
180
+ var SYMBOL_MAP = {
181
+ "\xA3": "GBP",
182
+ "\u20AC": "EUR",
183
+ "$": "USD",
184
+ "\xA5": "JPY",
185
+ "CHF": "CHF"
186
+ };
187
+ function validateCurrencyCode(input) {
188
+ if (!input || typeof input !== "string") {
189
+ return { valid: false, error: "Input must be a non-empty string" };
190
+ }
191
+ const upper = input.toUpperCase();
192
+ if (!SUPPORTED_CURRENCIES.includes(upper)) {
193
+ return {
194
+ valid: false,
195
+ error: `Unsupported currency code: ${input}. Supported: ${SUPPORTED_CURRENCIES.join(", ")}`
196
+ };
197
+ }
198
+ return {
199
+ valid: true,
200
+ value: upper,
201
+ formatted: upper
202
+ };
203
+ }
204
+ function formatCurrency(amount, currency, locale) {
205
+ const resolvedLocale = locale ?? CURRENCY_LOCALES[currency] ?? "en-GB";
206
+ return new Intl.NumberFormat(resolvedLocale, {
207
+ style: "currency",
208
+ currency,
209
+ minimumFractionDigits: currency === "JPY" ? 0 : 2,
210
+ maximumFractionDigits: currency === "JPY" ? 0 : 2
211
+ }).format(amount);
212
+ }
213
+ function parseMoney(input) {
214
+ if (!input || typeof input !== "string") {
215
+ return { valid: false, error: "Input must be a non-empty string" };
216
+ }
217
+ let currency;
218
+ let cleaned = input.trim();
219
+ for (const [symbol, code] of Object.entries(SYMBOL_MAP)) {
220
+ if (cleaned.startsWith(symbol) || cleaned.endsWith(symbol)) {
221
+ currency = code;
222
+ cleaned = cleaned.replace(symbol, "").trim();
223
+ break;
224
+ }
225
+ }
226
+ if (!currency) {
227
+ return { valid: false, error: "Could not detect currency from input. Expected a symbol like \xA3, \u20AC, $, \xA5" };
228
+ }
229
+ const normalised = cleaned.replace(/,/g, "");
230
+ const amount = parseFloat(normalised);
231
+ if (isNaN(amount)) {
232
+ return { valid: false, error: `Could not parse amount from: "${cleaned}"` };
233
+ }
234
+ return {
235
+ valid: true,
236
+ amount,
237
+ currency,
238
+ formatted: formatCurrency(amount, currency)
239
+ };
240
+ }
241
+
242
+ // src/bic.ts
243
+ var BIC_REGEX = /^[A-Z]{4}[A-Z]{2}[A-Z0-9]{2}([A-Z0-9]{3})?$/;
244
+ function validateBIC(input) {
245
+ if (!input || typeof input !== "string") {
246
+ return { valid: false, error: "Input must be a non-empty string" };
247
+ }
248
+ const cleaned = input.replace(/\s/g, "").toUpperCase();
249
+ if (cleaned.length !== 8 && cleaned.length !== 11) {
250
+ return {
251
+ valid: false,
252
+ error: `BIC must be 8 or 11 characters. Got ${cleaned.length}`
253
+ };
254
+ }
255
+ if (!BIC_REGEX.test(cleaned)) {
256
+ return {
257
+ valid: false,
258
+ error: "Invalid BIC format. Expected: 4 letters + 2 letters + 2 alphanumeric + optional 3 alphanumeric"
259
+ };
260
+ }
261
+ const bankCode = cleaned.slice(0, 4);
262
+ const countryCode = cleaned.slice(4, 6);
263
+ const location = cleaned.slice(6, 8);
264
+ const branch = cleaned.length === 11 ? cleaned.slice(8, 11) : "XXX";
265
+ return {
266
+ valid: true,
267
+ value: cleaned,
268
+ formatted: `${bankCode} ${countryCode} ${location} ${branch}`
269
+ };
270
+ }
271
+
272
+ // src/card.ts
273
+ function detectNetwork(digits) {
274
+ if (/^4/.test(digits)) return "Visa";
275
+ if (/^5[1-5]/.test(digits) || /^2[2-7]/.test(digits)) return "Mastercard";
276
+ if (/^3[47]/.test(digits)) return "Amex";
277
+ if (/^6(?:011|5)/.test(digits)) return "Discover";
278
+ return "Unknown";
279
+ }
280
+ function formatCardNumber(digits, network) {
281
+ if (network === "Amex") {
282
+ return `${digits.slice(0, 4)} ${digits.slice(4, 10)} ${digits.slice(10, 15)}`;
283
+ }
284
+ return digits.replace(/(.{4})/g, "$1 ").trim();
285
+ }
286
+ function validateCardNumber(input) {
287
+ if (!input || typeof input !== "string") {
288
+ return { valid: false, error: "Input must be a non-empty string" };
289
+ }
290
+ const digits = input.replace(/[\s-]/g, "");
291
+ if (!/^\d+$/.test(digits)) {
292
+ return { valid: false, error: "Card number must contain only digits" };
293
+ }
294
+ if (digits.length < 13 || digits.length > 19) {
295
+ return {
296
+ valid: false,
297
+ error: `Card number length invalid. Expected 13-19 digits, got ${digits.length}`
298
+ };
299
+ }
300
+ let sum = 0;
301
+ let shouldDouble = false;
302
+ for (let i = digits.length - 1; i >= 0; i--) {
303
+ let digit = parseInt(digits[i], 10);
304
+ if (shouldDouble) {
305
+ digit *= 2;
306
+ if (digit > 9) digit -= 9;
307
+ }
308
+ sum += digit;
309
+ shouldDouble = !shouldDouble;
310
+ }
311
+ if (sum % 10 !== 0) {
312
+ return { valid: false, error: "Card number failed Luhn check \u2014 this is not a valid card number" };
313
+ }
314
+ const network = detectNetwork(digits);
315
+ return {
316
+ valid: true,
317
+ value: digits,
318
+ formatted: formatCardNumber(digits, network),
319
+ network,
320
+ last4: digits.slice(-4)
321
+ };
322
+ }
323
+
324
+ export { SUPPORTED_CURRENCIES, formatCurrency, parseMoney, validateBIC, validateCardNumber, validateCurrencyCode, validateIBAN, validateUKAccountNumber, validateUKSortCode };
325
+ //# sourceMappingURL=index.mjs.map
326
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/iban.ts","../src/sortcode.ts","../src/currency.ts","../src/bic.ts","../src/card.ts"],"names":[],"mappings":";AAGA,IAAM,YAAA,GAAuC;AAAA,EAC3C,EAAA,EAAI,EAAA;AAAA,EAAI,EAAA,EAAI,EAAA;AAAA,EAAI,EAAA,EAAI,EAAA;AAAA,EAAI,EAAA,EAAI,EAAA;AAAA,EAAI,EAAA,EAAI,EAAA;AAAA,EAAI,EAAA,EAAI,EAAA;AAAA,EAAI,EAAA,EAAI,EAAA;AAAA,EAAI,EAAA,EAAI,EAAA;AAAA,EAC5D,EAAA,EAAI,EAAA;AAAA,EAAI,EAAA,EAAI,EAAA;AAAA,EAAI,EAAA,EAAI,EAAA;AAAA,EAAI,EAAA,EAAI,EAAA;AAAA,EAAI,EAAA,EAAI,EAAA;AAAA,EAAI,EAAA,EAAI,EAAA;AAAA,EAAI,EAAA,EAAI,EAAA;AAAA,EAAI,EAAA,EAAI,EAAA;AAAA,EAC5D,EAAA,EAAI,EAAA;AAAA,EAAI,EAAA,EAAI,EAAA;AAAA,EAAI,EAAA,EAAI,EAAA;AAAA,EAAI,EAAA,EAAI,EAAA;AAAA,EAAI,EAAA,EAAI,EAAA;AAAA,EAAI,EAAA,EAAI,EAAA;AAAA,EAAI,EAAA,EAAI,EAAA;AAAA,EAAI,EAAA,EAAI,EAAA;AAAA,EAC5D,EAAA,EAAI,EAAA;AAAA,EAAI,EAAA,EAAI,EAAA;AAAA,EAAI,EAAA,EAAI,EAAA;AAAA,EAAI,EAAA,EAAI,EAAA;AAAA,EAAI,EAAA,EAAI,EAAA;AAAA,EAAI,EAAA,EAAI,EAAA;AAAA,EAAI,EAAA,EAAI,EAAA;AAAA,EAAI,EAAA,EAAI,EAAA;AAAA,EAC5D,EAAA,EAAI,EAAA;AAAA,EAAI,EAAA,EAAI,EAAA;AAAA,EAAI,EAAA,EAAI,EAAA;AAAA,EAAI,EAAA,EAAI,EAAA;AAAA,EAAI,EAAA,EAAI,EAAA;AAAA,EAAI,EAAA,EAAI,EAAA;AAAA,EAAI,EAAA,EAAI,EAAA;AAAA,EAAI,EAAA,EAAI,EAAA;AAAA,EAC5D,EAAA,EAAI,EAAA;AAAA,EAAI,EAAA,EAAI,EAAA;AAAA,EAAI,EAAA,EAAI,EAAA;AAAA,EAAI,EAAA,EAAI,EAAA;AAAA,EAAI,EAAA,EAAI,EAAA;AAAA,EAAI,EAAA,EAAI,EAAA;AAAA,EAAI,EAAA,EAAI,EA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","file":"index.mjs","sourcesContent":["import type { IBAN, ValidationResult } from './types'\n\n// Expected IBAN lengths per country (ISO 13616 registry)\nconst IBAN_LENGTHS: Record<string, number> = {\n AL: 28, AD: 24, AT: 20, AZ: 28, BH: 22, BE: 16, BA: 20, BR: 29,\n BG: 22, CR: 22, HR: 21, CY: 28, CZ: 24, DK: 18, DO: 28, EE: 20,\n FI: 18, FR: 27, GE: 22, DE: 22, GI: 23, GR: 27, GT: 28, HU: 28,\n IS: 26, IE: 22, IL: 23, IT: 27, JO: 30, KZ: 20, KW: 30, LV: 21,\n LB: 28, LI: 21, LT: 20, LU: 20, MK: 19, MT: 31, MR: 27, MU: 30,\n MC: 27, MD: 24, ME: 22, NL: 18, NO: 15, PK: 24, PS: 29, PL: 28,\n PT: 25, QA: 29, RO: 24, SM: 27, SA: 24, RS: 22, SK: 24, SI: 19,\n ES: 24, SE: 24, CH: 21, TN: 24, TR: 26, AE: 23, GB: 22, VG: 24,\n}\n\n// Process digit by digit to avoid JS integer overflow on large IBAN numbers\nfunction mod97(value: string): number {\n let remainder = 0\n for (const char of value) {\n remainder = (remainder * 10 + parseInt(char, 10)) % 97\n }\n return remainder\n}\n\n// Rearrange IBAN and convert letters to numbers per ISO 13616\nfunction ibanToDigits(iban: string): string {\n const rearranged = iban.slice(4) + iban.slice(0, 4)\n return rearranged\n .split('')\n .map((char) => {\n const code = char.charCodeAt(0)\n // A=10, B=11, ... Z=35\n return code >= 65 && code <= 90 ? (code - 55).toString() : char\n })\n .join('')\n}\n\nfunction formatIBANString(iban: string): string {\n return iban.replace(/(.{4})/g, '$1 ').trim()\n}\n\n/**\n * Validates an IBAN string.\n * Accepts IBANs with or without spaces.\n * Validates: country code, expected length, characters, and mod97 checksum.\n *\n * @example\n * validateIBAN('GB29NWBK60161331926819')\n * // { valid: true, value: 'GB29NWBK60161331926819', formatted: 'GB29 NWBK 6016 1331 9268 19' }\n *\n * validateIBAN('GB00NWBK60161331926819')\n * // { valid: false, error: 'IBAN checksum is invalid' }\n */\nexport function validateIBAN(input: string): ValidationResult<IBAN> {\n if (!input || typeof input !== 'string') {\n return { valid: false, error: 'Input must be a non-empty string' }\n }\n\n const cleaned = input.replace(/\\s/g, '').toUpperCase()\n\n if (cleaned.length < 4) {\n return { valid: false, error: 'IBAN is too short' }\n }\n\n const countryCode = cleaned.slice(0, 2)\n\n if (!/^[A-Z]{2}$/.test(countryCode)) {\n return { valid: false, error: 'IBAN must start with a 2-letter country code' }\n }\n\n const expectedLength = IBAN_LENGTHS[countryCode]\n\n if (!expectedLength) {\n return { valid: false, error: `Unsupported country code: ${countryCode}` }\n }\n\n if (cleaned.length !== expectedLength) {\n return {\n valid: false,\n error: `Invalid length for ${countryCode} IBAN. Expected ${expectedLength} characters, got ${cleaned.length}`,\n }\n }\n\n if (!/^[A-Z0-9]+$/.test(cleaned)) {\n return { valid: false, error: 'IBAN contains invalid characters' }\n }\n\n const digits = ibanToDigits(cleaned)\n\n if (mod97(digits) !== 1) {\n return { valid: false, error: 'IBAN checksum is invalid' }\n }\n\n return {\n valid: true,\n value: cleaned as IBAN,\n formatted: formatIBANString(cleaned),\n }\n}\n","import type { SortCode, AccountNumber, ValidationResult } from './types'\n\n/**\n * Validates a UK sort code.\n * Accepts formats: 60-16-13, 601613, 60 16 13\n *\n * @example\n * validateUKSortCode('60-16-13')\n * // { valid: true, value: '601613', formatted: '60-16-13' }\n *\n * validateUKSortCode('999')\n * // { valid: false, error: 'Sort code must be 6 digits...' }\n */\nexport function validateUKSortCode(input: string): ValidationResult<SortCode> {\n if (!input || typeof input !== 'string') {\n return { valid: false, error: 'Input must be a non-empty string' }\n }\n\n const cleaned = input.replace(/[-\\s]/g, '')\n\n if (!/^\\d{6}$/.test(cleaned)) {\n return {\n valid: false,\n error: 'Sort code must be exactly 6 digits. Accepted formats: 60-16-13, 601613, 60 16 13',\n }\n }\n\n const formatted = `${cleaned.slice(0, 2)}-${cleaned.slice(2, 4)}-${cleaned.slice(4, 6)}`\n\n return {\n valid: true,\n value: cleaned as SortCode,\n formatted,\n }\n}\n\n/**\n * Validates a UK bank account number.\n * Must be exactly 8 digits.\n *\n * @example\n * validateUKAccountNumber('31926819')\n * // { valid: true, value: '31926819', formatted: '3192 6819' }\n *\n * validateUKAccountNumber('1234')\n * // { valid: false, error: 'UK account number must be exactly 8 digits' }\n */\nexport function validateUKAccountNumber(input: string): ValidationResult<AccountNumber> {\n if (!input || typeof input !== 'string') {\n return { valid: false, error: 'Input must be a non-empty string' }\n }\n\n const cleaned = input.replace(/\\s/g, '')\n\n if (!/^\\d{8}$/.test(cleaned)) {\n return {\n valid: false,\n error: 'UK account number must be exactly 8 digits',\n }\n }\n\n const formatted = `${cleaned.slice(0, 4)} ${cleaned.slice(4, 8)}`\n\n return {\n valid: true,\n value: cleaned as AccountNumber,\n formatted,\n }\n}\n","import type { CurrencyCode, SupportedCurrency, MoneyResult, ValidationResult } from './types'\n\nexport const SUPPORTED_CURRENCIES: SupportedCurrency[] = [\n 'GBP', 'EUR', 'USD', 'JPY', 'CHF', 'CAD', 'AUD', 'NZD',\n]\n\nconst CURRENCY_LOCALES: Record<SupportedCurrency, string> = {\n GBP: 'en-GB',\n EUR: 'de-DE',\n USD: 'en-US',\n JPY: 'ja-JP',\n CHF: 'de-CH',\n CAD: 'en-CA',\n AUD: 'en-AU',\n NZD: 'en-NZ',\n}\n\nconst SYMBOL_MAP: Record<string, SupportedCurrency> = {\n '£': 'GBP',\n '€': 'EUR',\n '$': 'USD',\n '¥': 'JPY',\n 'CHF': 'CHF',\n}\n\n/**\n * Validates a currency code against supported ISO 4217 codes.\n *\n * @example\n * validateCurrencyCode('GBP')\n * // { valid: true, value: 'GBP', formatted: 'GBP' }\n *\n * validateCurrencyCode('XYZ')\n * // { valid: false, error: 'Unsupported currency code: XYZ' }\n */\nexport function validateCurrencyCode(input: string): ValidationResult<CurrencyCode> {\n if (!input || typeof input !== 'string') {\n return { valid: false, error: 'Input must be a non-empty string' }\n }\n\n const upper = input.toUpperCase() as SupportedCurrency\n\n if (!SUPPORTED_CURRENCIES.includes(upper)) {\n return {\n valid: false,\n error: `Unsupported currency code: ${input}. Supported: ${SUPPORTED_CURRENCIES.join(', ')}`,\n }\n }\n\n return {\n valid: true,\n value: upper as CurrencyCode,\n formatted: upper,\n }\n}\n\n/**\n * Formats a number as a locale-aware currency string.\n * Uses the built-in Intl.NumberFormat API — zero dependencies.\n *\n * @example\n * formatCurrency(1000.5, 'GBP') // '£1,000.50'\n * formatCurrency(1000.5, 'EUR', 'de-DE') // '1.000,50 €'\n * formatCurrency(1000.5, 'USD', 'en-US') // '$1,000.50'\n * formatCurrency(1000, 'JPY') // '¥1,000'\n */\nexport function formatCurrency(\n amount: number,\n currency: SupportedCurrency,\n locale?: string\n): string {\n const resolvedLocale = locale ?? CURRENCY_LOCALES[currency] ?? 'en-GB'\n\n return new Intl.NumberFormat(resolvedLocale, {\n style: 'currency',\n currency,\n minimumFractionDigits: currency === 'JPY' ? 0 : 2,\n maximumFractionDigits: currency === 'JPY' ? 0 : 2,\n }).format(amount)\n}\n\n/**\n * Parses a formatted currency string back into a structured money object.\n * Detects the currency from the symbol prefix.\n *\n * @example\n * parseMoney('£1,000.50')\n * // { valid: true, amount: 1000.5, currency: 'GBP', formatted: '£1,000.50' }\n *\n * parseMoney('not money')\n * // { valid: false, error: 'Could not detect currency from input' }\n */\nexport function parseMoney(input: string): MoneyResult {\n if (!input || typeof input !== 'string') {\n return { valid: false, error: 'Input must be a non-empty string' }\n }\n\n let currency: SupportedCurrency | undefined\n let cleaned = input.trim()\n\n for (const [symbol, code] of Object.entries(SYMBOL_MAP)) {\n if (cleaned.startsWith(symbol) || cleaned.endsWith(symbol)) {\n currency = code\n cleaned = cleaned.replace(symbol, '').trim()\n break\n }\n }\n\n if (!currency) {\n return { valid: false, error: 'Could not detect currency from input. Expected a symbol like £, €, $, ¥' }\n }\n\n // Remove thousands separators, normalise decimal separator\n const normalised = cleaned.replace(/,/g, '')\n const amount = parseFloat(normalised)\n\n if (isNaN(amount)) {\n return { valid: false, error: `Could not parse amount from: \"${cleaned}\"` }\n }\n\n return {\n valid: true,\n amount,\n currency,\n formatted: formatCurrency(amount, currency),\n }\n}\n","import type { BIC, ValidationResult } from './types'\n\n// BIC format: 4 letters (bank) + 2 letters (country) + 2 alphanumeric (location) + optional 3 alphanumeric (branch)\nconst BIC_REGEX = /^[A-Z]{4}[A-Z]{2}[A-Z0-9]{2}([A-Z0-9]{3})?$/\n\n/**\n * Validates a BIC (Bank Identifier Code) / SWIFT code.\n * Accepts both 8-character and 11-character BIC codes.\n *\n * Format: AAAABBCCXXX\n * - AAAA = Bank code (4 letters)\n * - BB = Country code (2 letters, ISO 3166-1)\n * - CC = Location code (2 alphanumeric)\n * - XXX = Branch code (3 alphanumeric, optional — 'XXX' means head office)\n *\n * @example\n * validateBIC('NWBKGB2L')\n * // { valid: true, value: 'NWBKGB2L', formatted: 'NWBKGB2L' }\n *\n * validateBIC('DEUTDEDB')\n * // { valid: true, value: 'DEUTDEDB', formatted: 'DEUTDEDB' }\n */\nexport function validateBIC(input: string): ValidationResult<BIC> {\n if (!input || typeof input !== 'string') {\n return { valid: false, error: 'Input must be a non-empty string' }\n }\n\n const cleaned = input.replace(/\\s/g, '').toUpperCase()\n\n if (cleaned.length !== 8 && cleaned.length !== 11) {\n return {\n valid: false,\n error: `BIC must be 8 or 11 characters. Got ${cleaned.length}`,\n }\n }\n\n if (!BIC_REGEX.test(cleaned)) {\n return {\n valid: false,\n error: 'Invalid BIC format. Expected: 4 letters + 2 letters + 2 alphanumeric + optional 3 alphanumeric',\n }\n }\n\n const bankCode = cleaned.slice(0, 4)\n const countryCode = cleaned.slice(4, 6)\n const location = cleaned.slice(6, 8)\n const branch = cleaned.length === 11 ? cleaned.slice(8, 11) : 'XXX'\n\n return {\n valid: true,\n value: cleaned as BIC,\n formatted: `${bankCode} ${countryCode} ${location} ${branch}`,\n }\n}\n","import type { CardNumber, ValidationResult } from './types'\n\nexport type CardNetwork = 'Visa' | 'Mastercard' | 'Amex' | 'Discover' | 'Unknown'\n\nexport type CardValidationResult =\n | { valid: true; value: CardNumber; formatted: string; network: CardNetwork; last4: string }\n | { valid: false; error: string }\n\n/**\n * Detects the card network from the card number prefix.\n */\nfunction detectNetwork(digits: string): CardNetwork {\n if (/^4/.test(digits)) return 'Visa'\n if (/^5[1-5]/.test(digits) || /^2[2-7]/.test(digits)) return 'Mastercard'\n if (/^3[47]/.test(digits)) return 'Amex'\n if (/^6(?:011|5)/.test(digits)) return 'Discover'\n return 'Unknown'\n}\n\n/**\n * Formats a card number into groups based on the network.\n * Amex: 4-6-5, all others: 4-4-4-4\n */\nfunction formatCardNumber(digits: string, network: CardNetwork): string {\n if (network === 'Amex') {\n return `${digits.slice(0, 4)} ${digits.slice(4, 10)} ${digits.slice(10, 15)}`\n }\n return digits.replace(/(.{4})/g, '$1 ').trim()\n}\n\n/**\n * Validates a card number using the Luhn algorithm.\n * Accepts digits with or without spaces and hyphens.\n *\n * The Luhn algorithm:\n * 1. Double every second digit from the right\n * 2. If doubling produces a number > 9, subtract 9\n * 3. Sum all digits — result must be divisible by 10\n *\n * @example\n * validateCardNumber('4532015112830366')\n * // { valid: true, value: '...', formatted: '4532 0151 1283 0366', network: 'Visa', last4: '0366' }\n *\n * validateCardNumber('1234567890123456')\n * // { valid: false, error: 'Card number failed Luhn check' }\n */\nexport function validateCardNumber(input: string): CardValidationResult {\n if (!input || typeof input !== 'string') {\n return { valid: false, error: 'Input must be a non-empty string' }\n }\n\n const digits = input.replace(/[\\s-]/g, '')\n\n if (!/^\\d+$/.test(digits)) {\n return { valid: false, error: 'Card number must contain only digits' }\n }\n\n if (digits.length < 13 || digits.length > 19) {\n return {\n valid: false,\n error: `Card number length invalid. Expected 13-19 digits, got ${digits.length}`,\n }\n }\n\n // Luhn algorithm\n let sum = 0\n let shouldDouble = false\n\n for (let i = digits.length - 1; i >= 0; i--) {\n let digit = parseInt(digits[i]!, 10)\n\n if (shouldDouble) {\n digit *= 2\n if (digit > 9) digit -= 9\n }\n\n sum += digit\n shouldDouble = !shouldDouble\n }\n\n if (sum % 10 !== 0) {\n return { valid: false, error: 'Card number failed Luhn check — this is not a valid card number' }\n }\n\n const network = detectNetwork(digits)\n\n return {\n valid: true,\n value: digits as CardNumber,\n formatted: formatCardNumber(digits, network),\n network,\n last4: digits.slice(-4),\n }\n}\n"]}
@@ -0,0 +1,63 @@
1
+ import { V as ValidationResult, A as AccountNumber, B as BIC, a as SupportedCurrency, I as IBAN, S as SortCode } from '../types-KG-eFvWt.mjs';
2
+
3
+ type HookResult<T> = {
4
+ value: string;
5
+ formatted: string;
6
+ valid: boolean | null;
7
+ error: string | null;
8
+ onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
9
+ result: ValidationResult<T> | null;
10
+ };
11
+ /**
12
+ * React hook for IBAN input fields.
13
+ * Validates on change and returns formatted value and error state.
14
+ *
15
+ * @example
16
+ * const { value, formatted, valid, error, onChange } = useIBANInput()
17
+ *
18
+ * return (
19
+ * <input
20
+ * value={formatted}
21
+ * onChange={onChange}
22
+ * aria-invalid={valid === false}
23
+ * />
24
+ * )
25
+ */
26
+ declare function useIBANInput(): HookResult<IBAN>;
27
+ /**
28
+ * React hook for UK sort code input fields.
29
+ *
30
+ * @example
31
+ * const { formatted, valid, error, onChange } = useSortCodeInput()
32
+ */
33
+ declare function useSortCodeInput(): HookResult<SortCode>;
34
+ /**
35
+ * React hook for UK account number input fields.
36
+ *
37
+ * @example
38
+ * const { formatted, valid, error, onChange } = useAccountNumberInput()
39
+ */
40
+ declare function useAccountNumberInput(): HookResult<AccountNumber>;
41
+ /**
42
+ * React hook for BIC / SWIFT code input fields.
43
+ *
44
+ * @example
45
+ * const { formatted, valid, error, onChange } = useBICInput()
46
+ */
47
+ declare function useBICInput(): HookResult<BIC>;
48
+ /**
49
+ * React hook for currency amount inputs with locale-aware formatting.
50
+ * Returns both the raw numeric value and the formatted display string.
51
+ *
52
+ * @example
53
+ * const { rawValue, formatted, onChange } = useCurrencyInput('GBP')
54
+ *
55
+ * return <input value={formatted} onChange={onChange} />
56
+ */
57
+ declare function useCurrencyInput(currency: SupportedCurrency, locale?: string): {
58
+ rawValue: number | null;
59
+ formatted: string;
60
+ onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
61
+ };
62
+
63
+ export { useAccountNumberInput, useBICInput, useCurrencyInput, useIBANInput, useSortCodeInput };
@@ -0,0 +1,63 @@
1
+ import { V as ValidationResult, A as AccountNumber, B as BIC, a as SupportedCurrency, I as IBAN, S as SortCode } from '../types-KG-eFvWt.js';
2
+
3
+ type HookResult<T> = {
4
+ value: string;
5
+ formatted: string;
6
+ valid: boolean | null;
7
+ error: string | null;
8
+ onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
9
+ result: ValidationResult<T> | null;
10
+ };
11
+ /**
12
+ * React hook for IBAN input fields.
13
+ * Validates on change and returns formatted value and error state.
14
+ *
15
+ * @example
16
+ * const { value, formatted, valid, error, onChange } = useIBANInput()
17
+ *
18
+ * return (
19
+ * <input
20
+ * value={formatted}
21
+ * onChange={onChange}
22
+ * aria-invalid={valid === false}
23
+ * />
24
+ * )
25
+ */
26
+ declare function useIBANInput(): HookResult<IBAN>;
27
+ /**
28
+ * React hook for UK sort code input fields.
29
+ *
30
+ * @example
31
+ * const { formatted, valid, error, onChange } = useSortCodeInput()
32
+ */
33
+ declare function useSortCodeInput(): HookResult<SortCode>;
34
+ /**
35
+ * React hook for UK account number input fields.
36
+ *
37
+ * @example
38
+ * const { formatted, valid, error, onChange } = useAccountNumberInput()
39
+ */
40
+ declare function useAccountNumberInput(): HookResult<AccountNumber>;
41
+ /**
42
+ * React hook for BIC / SWIFT code input fields.
43
+ *
44
+ * @example
45
+ * const { formatted, valid, error, onChange } = useBICInput()
46
+ */
47
+ declare function useBICInput(): HookResult<BIC>;
48
+ /**
49
+ * React hook for currency amount inputs with locale-aware formatting.
50
+ * Returns both the raw numeric value and the formatted display string.
51
+ *
52
+ * @example
53
+ * const { rawValue, formatted, onChange } = useCurrencyInput('GBP')
54
+ *
55
+ * return <input value={formatted} onChange={onChange} />
56
+ */
57
+ declare function useCurrencyInput(currency: SupportedCurrency, locale?: string): {
58
+ rawValue: number | null;
59
+ formatted: string;
60
+ onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
61
+ };
62
+
63
+ export { useAccountNumberInput, useBICInput, useCurrencyInput, useIBANInput, useSortCodeInput };