finprim 0.1.0 → 0.1.1

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.
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/iban.ts","../../src/sortcode.ts","../../src/currency.ts","../../src/bic.ts","../../src/react/index.ts"],"names":["useState","useCallback"],"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,E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","file":"index.js","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 { useState, useCallback } from 'react'\nimport { validateIBAN } from '../iban'\nimport { validateUKSortCode, validateUKAccountNumber } from '../sortcode'\nimport { formatCurrency } from '../currency'\nimport { validateBIC } from '../bic'\nimport type {\n SupportedCurrency,\n ValidationResult,\n IBAN,\n SortCode,\n AccountNumber,\n BIC,\n} from '../types'\n\ntype HookResult<T> = {\n value: string\n formatted: string\n valid: boolean | null\n error: string | null\n onChange: (e: React.ChangeEvent<HTMLInputElement>) => void\n result: ValidationResult<T> | null\n}\n\nfunction useValidatedInput<T>(\n validator: (val: string) => ValidationResult<T>,\n minLength = 1\n): HookResult<T> {\n const [value, setValue] = useState('')\n const [result, setResult] = useState<ValidationResult<T> | null>(null)\n\n const onChange = useCallback(\n (e: React.ChangeEvent<HTMLInputElement>) => {\n const input = e.target.value\n setValue(input)\n if (input.length >= minLength) {\n setResult(validator(input))\n } else {\n setResult(null)\n }\n },\n [validator, minLength]\n )\n\n return {\n value,\n formatted: result?.valid ? result.formatted : value,\n valid: result === null ? null : result.valid,\n error: result && !result.valid ? result.error : null,\n onChange,\n result,\n }\n}\n\n/**\n * React hook for IBAN input fields.\n * Validates on change and returns formatted value and error state.\n *\n * @example\n * const { value, formatted, valid, error, onChange } = useIBANInput()\n *\n * return (\n * <input\n * value={formatted}\n * onChange={onChange}\n * aria-invalid={valid === false}\n * />\n * )\n */\nexport function useIBANInput(): HookResult<IBAN> {\n return useValidatedInput(validateIBAN, 5)\n}\n\n/**\n * React hook for UK sort code input fields.\n *\n * @example\n * const { formatted, valid, error, onChange } = useSortCodeInput()\n */\nexport function useSortCodeInput(): HookResult<SortCode> {\n return useValidatedInput(validateUKSortCode, 6)\n}\n\n/**\n * React hook for UK account number input fields.\n *\n * @example\n * const { formatted, valid, error, onChange } = useAccountNumberInput()\n */\nexport function useAccountNumberInput(): HookResult<AccountNumber> {\n return useValidatedInput(validateUKAccountNumber, 8)\n}\n\n/**\n * React hook for BIC / SWIFT code input fields.\n *\n * @example\n * const { formatted, valid, error, onChange } = useBICInput()\n */\nexport function useBICInput(): HookResult<BIC> {\n return useValidatedInput(validateBIC, 8)\n}\n\n/**\n * React hook for currency amount inputs with locale-aware formatting.\n * Returns both the raw numeric value and the formatted display string.\n *\n * @example\n * const { rawValue, formatted, onChange } = useCurrencyInput('GBP')\n *\n * return <input value={formatted} onChange={onChange} />\n */\nexport function useCurrencyInput(currency: SupportedCurrency, locale?: string) {\n const [rawValue, setRawValue] = useState<number | null>(null)\n\n const onChange = useCallback(\n (e: React.ChangeEvent<HTMLInputElement>) => {\n const num = parseFloat(e.target.value.replace(/[^0-9.]/g, ''))\n setRawValue(isNaN(num) ? null : num)\n },\n []\n )\n\n const formatted = rawValue !== null ? formatCurrency(rawValue, currency, locale) : ''\n\n return { rawValue, formatted, onChange }\n}\n"]}
1
+ {"version":3,"sources":["../../src/iban.ts","../../src/sortcode.ts","../../src/currency.ts","../../src/bic.ts","../../src/card.ts","../../src/react/index.ts"],"names":["useState","useCallback"],"mappings":";;;;;;;AAEA,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,E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","file":"index.js","sourcesContent":["import type { IBAN, IBANValidationResult } from './types'\n\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\nconst LETTER_A = 'A'.codePointAt(0)!\nconst LETTER_Z = 'Z'.codePointAt(0)!\nconst LETTER_TO_DIGIT_OFFSET = 55\n\nfunction mod97(value: string): number {\n return [...value].reduce(\n (remainder, char) => (remainder * 10 + Number.parseInt(char, 10)) % 97,\n 0\n )\n}\n\nfunction ibanToDigits(iban: string): string {\n const rearranged = iban.slice(4) + iban.slice(0, 4)\n return [...rearranged]\n .map((char) => {\n const code = char.codePointAt(0) ?? 0\n return code >= LETTER_A && code <= LETTER_Z\n ? (code - LETTER_TO_DIGIT_OFFSET).toString()\n : char\n })\n .join('')\n}\n\nfunction formatIBANString(iban: string): string {\n return iban.replace(/(.{4})/g, '$1 ').trim()\n}\n\nexport function validateIBAN(input: string): IBANValidationResult {\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 countryCode,\n }\n}\n","import type { SortCode, AccountNumber, ValidationResult } from './types'\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\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\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\nexport function formatCurrency(\n amount: number,\n currency: SupportedCurrency,\n locale?: string\n): string {\n const resolvedLocale = locale ?? CURRENCY_LOCALES[currency] ?? 'en-GB'\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\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 const normalised = cleaned.replace(/,/g, '')\n const amount = Number.parseFloat(normalised)\n\n if (Number.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\nconst BIC_REGEX = /^[A-Z]{4}[A-Z]{2}[A-Z0-9]{2}([A-Z0-9]{3})?$/\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\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\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\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 let sum = 0\n let shouldDouble = false\n\n for (let i = digits.length - 1; i >= 0; i--) {\n let digit = Number.parseInt(digits[i]!, 10)\n if (shouldDouble) {\n digit *= 2\n if (digit > 9) digit -= 9\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","import { useState, useCallback } from 'react'\nimport { validateIBAN } from '../iban'\nimport { validateUKSortCode, validateUKAccountNumber } from '../sortcode'\nimport { formatCurrency } from '../currency'\nimport { validateBIC } from '../bic'\nimport { validateCardNumber } from '../card'\nimport type { CardValidationResult } from '../card'\nimport type {\n SupportedCurrency,\n ValidationResult,\n IBAN,\n SortCode,\n AccountNumber,\n BIC,\n CardNumber,\n} from '../types'\n\ntype HookResult<T, R = ValidationResult<T>> = {\n value: string\n formatted: string\n valid: boolean | null\n error: string | null\n onChange: (e: React.ChangeEvent<HTMLInputElement>) => void\n result: R | null\n}\n\nfunction useValidatedInput<T, R extends ValidationResult<T> = ValidationResult<T>>(\n validator: (val: string) => R,\n minLength = 1\n): HookResult<T, R> {\n const [value, setValue] = useState('')\n const [result, setResult] = useState<R | null>(null)\n\n const onChange = useCallback(\n (e: React.ChangeEvent<HTMLInputElement>) => {\n const input = e.target.value\n setValue(input)\n if (input.length >= minLength) {\n setResult(validator(input))\n } else {\n setResult(null)\n }\n },\n [validator, minLength]\n )\n\n const error = result?.valid === false ? result.error : null\n\n return {\n value,\n formatted: result?.valid ? result.formatted : value,\n valid: result === null ? null : result.valid,\n error,\n onChange,\n result,\n }\n}\n\nexport function useIBANInput(): HookResult<IBAN> {\n return useValidatedInput(validateIBAN, 5)\n}\n\nexport function useSortCodeInput(): HookResult<SortCode> {\n return useValidatedInput(validateUKSortCode, 6)\n}\n\nexport function useAccountNumberInput(): HookResult<AccountNumber> {\n return useValidatedInput(validateUKAccountNumber, 8)\n}\n\nexport function useBICInput(): HookResult<BIC> {\n return useValidatedInput(validateBIC, 8)\n}\n\nexport function useCardNumberInput(): HookResult<CardNumber, CardValidationResult> {\n return useValidatedInput(validateCardNumber, 8)\n}\n\nexport function useCurrencyInput(currency: SupportedCurrency, locale?: string) {\n const [rawValue, setRawValue] = useState<number | null>(null)\n\n const onChange = useCallback(\n (e: React.ChangeEvent<HTMLInputElement>) => {\n const digits = e.target.value.replace(/[^0-9.]/g, '')\n const num = Number.parseFloat(digits)\n setRawValue(Number.isNaN(num) ? null : num)\n },\n []\n )\n\n const formatted = rawValue !== null ? formatCurrency(rawValue, currency, locale) : ''\n\n return { rawValue, formatted, onChange }\n}\n"]}
@@ -69,18 +69,20 @@ var IBAN_LENGTHS = {
69
69
  GB: 22,
70
70
  VG: 24
71
71
  };
72
+ var LETTER_A = "A".codePointAt(0);
73
+ var LETTER_Z = "Z".codePointAt(0);
74
+ var LETTER_TO_DIGIT_OFFSET = 55;
72
75
  function mod97(value) {
73
- let remainder = 0;
74
- for (const char of value) {
75
- remainder = (remainder * 10 + parseInt(char, 10)) % 97;
76
- }
77
- return remainder;
76
+ return [...value].reduce(
77
+ (remainder, char) => (remainder * 10 + Number.parseInt(char, 10)) % 97,
78
+ 0
79
+ );
78
80
  }
79
81
  function ibanToDigits(iban) {
80
82
  const rearranged = iban.slice(4) + iban.slice(0, 4);
81
- return rearranged.split("").map((char) => {
82
- const code = char.charCodeAt(0);
83
- return code >= 65 && code <= 90 ? (code - 55).toString() : char;
83
+ return [...rearranged].map((char) => {
84
+ const code = char.codePointAt(0) ?? 0;
85
+ return code >= LETTER_A && code <= LETTER_Z ? (code - LETTER_TO_DIGIT_OFFSET).toString() : char;
84
86
  }).join("");
85
87
  }
86
88
  function formatIBANString(iban) {
@@ -118,7 +120,8 @@ function validateIBAN(input) {
118
120
  return {
119
121
  valid: true,
120
122
  value: cleaned,
121
- formatted: formatIBANString(cleaned)
123
+ formatted: formatIBANString(cleaned),
124
+ countryCode
122
125
  };
123
126
  }
124
127
 
@@ -211,6 +214,58 @@ function validateBIC(input) {
211
214
  };
212
215
  }
213
216
 
217
+ // src/card.ts
218
+ function detectNetwork(digits) {
219
+ if (/^4/.test(digits)) return "Visa";
220
+ if (/^5[1-5]/.test(digits) || /^2[2-7]/.test(digits)) return "Mastercard";
221
+ if (/^3[47]/.test(digits)) return "Amex";
222
+ if (/^6(?:011|5)/.test(digits)) return "Discover";
223
+ return "Unknown";
224
+ }
225
+ function formatCardNumber(digits, network) {
226
+ if (network === "Amex") {
227
+ return `${digits.slice(0, 4)} ${digits.slice(4, 10)} ${digits.slice(10, 15)}`;
228
+ }
229
+ return digits.replace(/(.{4})/g, "$1 ").trim();
230
+ }
231
+ function validateCardNumber(input) {
232
+ if (!input || typeof input !== "string") {
233
+ return { valid: false, error: "Input must be a non-empty string" };
234
+ }
235
+ const digits = input.replace(/[\s-]/g, "");
236
+ if (!/^\d+$/.test(digits)) {
237
+ return { valid: false, error: "Card number must contain only digits" };
238
+ }
239
+ if (digits.length < 13 || digits.length > 19) {
240
+ return {
241
+ valid: false,
242
+ error: `Card number length invalid. Expected 13-19 digits, got ${digits.length}`
243
+ };
244
+ }
245
+ let sum = 0;
246
+ let shouldDouble = false;
247
+ for (let i = digits.length - 1; i >= 0; i--) {
248
+ let digit = Number.parseInt(digits[i], 10);
249
+ if (shouldDouble) {
250
+ digit *= 2;
251
+ if (digit > 9) digit -= 9;
252
+ }
253
+ sum += digit;
254
+ shouldDouble = !shouldDouble;
255
+ }
256
+ if (sum % 10 !== 0) {
257
+ return { valid: false, error: "Card number failed Luhn check \u2014 this is not a valid card number" };
258
+ }
259
+ const network = detectNetwork(digits);
260
+ return {
261
+ valid: true,
262
+ value: digits,
263
+ formatted: formatCardNumber(digits, network),
264
+ network,
265
+ last4: digits.slice(-4)
266
+ };
267
+ }
268
+
214
269
  // src/react/index.ts
215
270
  function useValidatedInput(validator, minLength = 1) {
216
271
  const [value, setValue] = useState("");
@@ -227,11 +282,12 @@ function useValidatedInput(validator, minLength = 1) {
227
282
  },
228
283
  [validator, minLength]
229
284
  );
285
+ const error = result?.valid === false ? result.error : null;
230
286
  return {
231
287
  value,
232
288
  formatted: result?.valid ? result.formatted : value,
233
289
  valid: result === null ? null : result.valid,
234
- error: result && !result.valid ? result.error : null,
290
+ error,
235
291
  onChange,
236
292
  result
237
293
  };
@@ -248,12 +304,16 @@ function useAccountNumberInput() {
248
304
  function useBICInput() {
249
305
  return useValidatedInput(validateBIC, 8);
250
306
  }
307
+ function useCardNumberInput() {
308
+ return useValidatedInput(validateCardNumber, 8);
309
+ }
251
310
  function useCurrencyInput(currency, locale) {
252
311
  const [rawValue, setRawValue] = useState(null);
253
312
  const onChange = useCallback(
254
313
  (e) => {
255
- const num = parseFloat(e.target.value.replace(/[^0-9.]/g, ""));
256
- setRawValue(isNaN(num) ? null : num);
314
+ const digits = e.target.value.replace(/[^0-9.]/g, "");
315
+ const num = Number.parseFloat(digits);
316
+ setRawValue(Number.isNaN(num) ? null : num);
257
317
  },
258
318
  []
259
319
  );
@@ -261,6 +321,6 @@ function useCurrencyInput(currency, locale) {
261
321
  return { rawValue, formatted, onChange };
262
322
  }
263
323
 
264
- export { useAccountNumberInput, useBICInput, useCurrencyInput, useIBANInput, useSortCodeInput };
324
+ export { useAccountNumberInput, useBICInput, useCardNumberInput, useCurrencyInput, useIBANInput, useSortCodeInput };
265
325
  //# sourceMappingURL=index.mjs.map
266
326
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/iban.ts","../../src/sortcode.ts","../../src/currency.ts","../../src/bic.ts","../../src/react/index.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,EAA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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 { useState, useCallback } from 'react'\nimport { validateIBAN } from '../iban'\nimport { validateUKSortCode, validateUKAccountNumber } from '../sortcode'\nimport { formatCurrency } from '../currency'\nimport { validateBIC } from '../bic'\nimport type {\n SupportedCurrency,\n ValidationResult,\n IBAN,\n SortCode,\n AccountNumber,\n BIC,\n} from '../types'\n\ntype HookResult<T> = {\n value: string\n formatted: string\n valid: boolean | null\n error: string | null\n onChange: (e: React.ChangeEvent<HTMLInputElement>) => void\n result: ValidationResult<T> | null\n}\n\nfunction useValidatedInput<T>(\n validator: (val: string) => ValidationResult<T>,\n minLength = 1\n): HookResult<T> {\n const [value, setValue] = useState('')\n const [result, setResult] = useState<ValidationResult<T> | null>(null)\n\n const onChange = useCallback(\n (e: React.ChangeEvent<HTMLInputElement>) => {\n const input = e.target.value\n setValue(input)\n if (input.length >= minLength) {\n setResult(validator(input))\n } else {\n setResult(null)\n }\n },\n [validator, minLength]\n )\n\n return {\n value,\n formatted: result?.valid ? result.formatted : value,\n valid: result === null ? null : result.valid,\n error: result && !result.valid ? result.error : null,\n onChange,\n result,\n }\n}\n\n/**\n * React hook for IBAN input fields.\n * Validates on change and returns formatted value and error state.\n *\n * @example\n * const { value, formatted, valid, error, onChange } = useIBANInput()\n *\n * return (\n * <input\n * value={formatted}\n * onChange={onChange}\n * aria-invalid={valid === false}\n * />\n * )\n */\nexport function useIBANInput(): HookResult<IBAN> {\n return useValidatedInput(validateIBAN, 5)\n}\n\n/**\n * React hook for UK sort code input fields.\n *\n * @example\n * const { formatted, valid, error, onChange } = useSortCodeInput()\n */\nexport function useSortCodeInput(): HookResult<SortCode> {\n return useValidatedInput(validateUKSortCode, 6)\n}\n\n/**\n * React hook for UK account number input fields.\n *\n * @example\n * const { formatted, valid, error, onChange } = useAccountNumberInput()\n */\nexport function useAccountNumberInput(): HookResult<AccountNumber> {\n return useValidatedInput(validateUKAccountNumber, 8)\n}\n\n/**\n * React hook for BIC / SWIFT code input fields.\n *\n * @example\n * const { formatted, valid, error, onChange } = useBICInput()\n */\nexport function useBICInput(): HookResult<BIC> {\n return useValidatedInput(validateBIC, 8)\n}\n\n/**\n * React hook for currency amount inputs with locale-aware formatting.\n * Returns both the raw numeric value and the formatted display string.\n *\n * @example\n * const { rawValue, formatted, onChange } = useCurrencyInput('GBP')\n *\n * return <input value={formatted} onChange={onChange} />\n */\nexport function useCurrencyInput(currency: SupportedCurrency, locale?: string) {\n const [rawValue, setRawValue] = useState<number | null>(null)\n\n const onChange = useCallback(\n (e: React.ChangeEvent<HTMLInputElement>) => {\n const num = parseFloat(e.target.value.replace(/[^0-9.]/g, ''))\n setRawValue(isNaN(num) ? null : num)\n },\n []\n )\n\n const formatted = rawValue !== null ? formatCurrency(rawValue, currency, locale) : ''\n\n return { rawValue, formatted, onChange }\n}\n"]}
1
+ {"version":3,"sources":["../../src/iban.ts","../../src/sortcode.ts","../../src/currency.ts","../../src/bic.ts","../../src/card.ts","../../src/react/index.ts"],"names":[],"mappings":";;;;;AAEA,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,EAA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","file":"index.mjs","sourcesContent":["import type { IBAN, IBANValidationResult } from './types'\n\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\nconst LETTER_A = 'A'.codePointAt(0)!\nconst LETTER_Z = 'Z'.codePointAt(0)!\nconst LETTER_TO_DIGIT_OFFSET = 55\n\nfunction mod97(value: string): number {\n return [...value].reduce(\n (remainder, char) => (remainder * 10 + Number.parseInt(char, 10)) % 97,\n 0\n )\n}\n\nfunction ibanToDigits(iban: string): string {\n const rearranged = iban.slice(4) + iban.slice(0, 4)\n return [...rearranged]\n .map((char) => {\n const code = char.codePointAt(0) ?? 0\n return code >= LETTER_A && code <= LETTER_Z\n ? (code - LETTER_TO_DIGIT_OFFSET).toString()\n : char\n })\n .join('')\n}\n\nfunction formatIBANString(iban: string): string {\n return iban.replace(/(.{4})/g, '$1 ').trim()\n}\n\nexport function validateIBAN(input: string): IBANValidationResult {\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 countryCode,\n }\n}\n","import type { SortCode, AccountNumber, ValidationResult } from './types'\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\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\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\nexport function formatCurrency(\n amount: number,\n currency: SupportedCurrency,\n locale?: string\n): string {\n const resolvedLocale = locale ?? CURRENCY_LOCALES[currency] ?? 'en-GB'\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\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 const normalised = cleaned.replace(/,/g, '')\n const amount = Number.parseFloat(normalised)\n\n if (Number.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\nconst BIC_REGEX = /^[A-Z]{4}[A-Z]{2}[A-Z0-9]{2}([A-Z0-9]{3})?$/\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\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\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\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 let sum = 0\n let shouldDouble = false\n\n for (let i = digits.length - 1; i >= 0; i--) {\n let digit = Number.parseInt(digits[i]!, 10)\n if (shouldDouble) {\n digit *= 2\n if (digit > 9) digit -= 9\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","import { useState, useCallback } from 'react'\nimport { validateIBAN } from '../iban'\nimport { validateUKSortCode, validateUKAccountNumber } from '../sortcode'\nimport { formatCurrency } from '../currency'\nimport { validateBIC } from '../bic'\nimport { validateCardNumber } from '../card'\nimport type { CardValidationResult } from '../card'\nimport type {\n SupportedCurrency,\n ValidationResult,\n IBAN,\n SortCode,\n AccountNumber,\n BIC,\n CardNumber,\n} from '../types'\n\ntype HookResult<T, R = ValidationResult<T>> = {\n value: string\n formatted: string\n valid: boolean | null\n error: string | null\n onChange: (e: React.ChangeEvent<HTMLInputElement>) => void\n result: R | null\n}\n\nfunction useValidatedInput<T, R extends ValidationResult<T> = ValidationResult<T>>(\n validator: (val: string) => R,\n minLength = 1\n): HookResult<T, R> {\n const [value, setValue] = useState('')\n const [result, setResult] = useState<R | null>(null)\n\n const onChange = useCallback(\n (e: React.ChangeEvent<HTMLInputElement>) => {\n const input = e.target.value\n setValue(input)\n if (input.length >= minLength) {\n setResult(validator(input))\n } else {\n setResult(null)\n }\n },\n [validator, minLength]\n )\n\n const error = result?.valid === false ? result.error : null\n\n return {\n value,\n formatted: result?.valid ? result.formatted : value,\n valid: result === null ? null : result.valid,\n error,\n onChange,\n result,\n }\n}\n\nexport function useIBANInput(): HookResult<IBAN> {\n return useValidatedInput(validateIBAN, 5)\n}\n\nexport function useSortCodeInput(): HookResult<SortCode> {\n return useValidatedInput(validateUKSortCode, 6)\n}\n\nexport function useAccountNumberInput(): HookResult<AccountNumber> {\n return useValidatedInput(validateUKAccountNumber, 8)\n}\n\nexport function useBICInput(): HookResult<BIC> {\n return useValidatedInput(validateBIC, 8)\n}\n\nexport function useCardNumberInput(): HookResult<CardNumber, CardValidationResult> {\n return useValidatedInput(validateCardNumber, 8)\n}\n\nexport function useCurrencyInput(currency: SupportedCurrency, locale?: string) {\n const [rawValue, setRawValue] = useState<number | null>(null)\n\n const onChange = useCallback(\n (e: React.ChangeEvent<HTMLInputElement>) => {\n const digits = e.target.value.replace(/[^0-9.]/g, '')\n const num = Number.parseFloat(digits)\n setRawValue(Number.isNaN(num) ? null : num)\n },\n []\n )\n\n const formatted = rawValue !== null ? formatCurrency(rawValue, currency, locale) : ''\n\n return { rawValue, formatted, onChange }\n}\n"]}
@@ -1,43 +1,11 @@
1
1
  import { z } from 'zod';
2
2
 
3
- /**
4
- * Zod schema for IBAN validation.
5
- *
6
- * @example
7
- * const schema = z.object({ iban: ibanSchema })
8
- * schema.parse({ iban: 'GB29NWBK60161331926819' }) // passes
9
- * schema.parse({ iban: 'invalid' }) // throws ZodError
10
- */
11
3
  declare const ibanSchema: z.ZodEffects<z.ZodString, string, string>;
12
- /**
13
- * Zod schema for UK sort code validation.
14
- * Accepts: 60-16-13, 601613, 60 16 13
15
- */
16
4
  declare const sortCodeSchema: z.ZodEffects<z.ZodString, string, string>;
17
- /**
18
- * Zod schema for UK account number validation.
19
- */
20
5
  declare const accountNumberSchema: z.ZodEffects<z.ZodString, string, string>;
21
- /**
22
- * Zod schema for supported currency codes.
23
- */
24
6
  declare const currencySchema: z.ZodEffects<z.ZodString, string, string>;
25
- /**
26
- * Zod schema for BIC / SWIFT code validation.
27
- */
28
7
  declare const bicSchema: z.ZodEffects<z.ZodString, string, string>;
29
- /**
30
- * Zod schema for card number validation (Luhn check).
31
- */
32
8
  declare const cardNumberSchema: z.ZodEffects<z.ZodString, string, string>;
33
- /**
34
- * Complete Zod schema for a UK domestic payment.
35
- * Ready to use in any NestJS controller, tRPC router, or API handler.
36
- *
37
- * @example
38
- * const body = ukPaymentSchema.parse(req.body)
39
- * // body.sortCode is validated, body.accountNumber is validated, etc.
40
- */
41
9
  declare const ukPaymentSchema: z.ZodObject<{
42
10
  sortCode: z.ZodEffects<z.ZodString, string, string>;
43
11
  accountNumber: z.ZodEffects<z.ZodString, string, string>;
@@ -60,9 +28,6 @@ declare const ukPaymentSchema: z.ZodObject<{
60
28
  reference?: string | undefined;
61
29
  payeeName?: string | undefined;
62
30
  }>;
63
- /**
64
- * Complete Zod schema for an international IBAN-based payment.
65
- */
66
31
  declare const internationalPaymentSchema: z.ZodObject<{
67
32
  iban: z.ZodEffects<z.ZodString, string, string>;
68
33
  bic: z.ZodOptional<z.ZodEffects<z.ZodString, string, string>>;
@@ -1,43 +1,11 @@
1
1
  import { z } from 'zod';
2
2
 
3
- /**
4
- * Zod schema for IBAN validation.
5
- *
6
- * @example
7
- * const schema = z.object({ iban: ibanSchema })
8
- * schema.parse({ iban: 'GB29NWBK60161331926819' }) // passes
9
- * schema.parse({ iban: 'invalid' }) // throws ZodError
10
- */
11
3
  declare const ibanSchema: z.ZodEffects<z.ZodString, string, string>;
12
- /**
13
- * Zod schema for UK sort code validation.
14
- * Accepts: 60-16-13, 601613, 60 16 13
15
- */
16
4
  declare const sortCodeSchema: z.ZodEffects<z.ZodString, string, string>;
17
- /**
18
- * Zod schema for UK account number validation.
19
- */
20
5
  declare const accountNumberSchema: z.ZodEffects<z.ZodString, string, string>;
21
- /**
22
- * Zod schema for supported currency codes.
23
- */
24
6
  declare const currencySchema: z.ZodEffects<z.ZodString, string, string>;
25
- /**
26
- * Zod schema for BIC / SWIFT code validation.
27
- */
28
7
  declare const bicSchema: z.ZodEffects<z.ZodString, string, string>;
29
- /**
30
- * Zod schema for card number validation (Luhn check).
31
- */
32
8
  declare const cardNumberSchema: z.ZodEffects<z.ZodString, string, string>;
33
- /**
34
- * Complete Zod schema for a UK domestic payment.
35
- * Ready to use in any NestJS controller, tRPC router, or API handler.
36
- *
37
- * @example
38
- * const body = ukPaymentSchema.parse(req.body)
39
- * // body.sortCode is validated, body.accountNumber is validated, etc.
40
- */
41
9
  declare const ukPaymentSchema: z.ZodObject<{
42
10
  sortCode: z.ZodEffects<z.ZodString, string, string>;
43
11
  accountNumber: z.ZodEffects<z.ZodString, string, string>;
@@ -60,9 +28,6 @@ declare const ukPaymentSchema: z.ZodObject<{
60
28
  reference?: string | undefined;
61
29
  payeeName?: string | undefined;
62
30
  }>;
63
- /**
64
- * Complete Zod schema for an international IBAN-based payment.
65
- */
66
31
  declare const internationalPaymentSchema: z.ZodObject<{
67
32
  iban: z.ZodEffects<z.ZodString, string, string>;
68
33
  bic: z.ZodOptional<z.ZodEffects<z.ZodString, string, string>>;
package/dist/zod/index.js CHANGED
@@ -71,18 +71,20 @@ var IBAN_LENGTHS = {
71
71
  GB: 22,
72
72
  VG: 24
73
73
  };
74
+ var LETTER_A = "A".codePointAt(0);
75
+ var LETTER_Z = "Z".codePointAt(0);
76
+ var LETTER_TO_DIGIT_OFFSET = 55;
74
77
  function mod97(value) {
75
- let remainder = 0;
76
- for (const char of value) {
77
- remainder = (remainder * 10 + parseInt(char, 10)) % 97;
78
- }
79
- return remainder;
78
+ return [...value].reduce(
79
+ (remainder, char) => (remainder * 10 + Number.parseInt(char, 10)) % 97,
80
+ 0
81
+ );
80
82
  }
81
83
  function ibanToDigits(iban) {
82
84
  const rearranged = iban.slice(4) + iban.slice(0, 4);
83
- return rearranged.split("").map((char) => {
84
- const code = char.charCodeAt(0);
85
- return code >= 65 && code <= 90 ? (code - 55).toString() : char;
85
+ return [...rearranged].map((char) => {
86
+ const code = char.codePointAt(0) ?? 0;
87
+ return code >= LETTER_A && code <= LETTER_Z ? (code - LETTER_TO_DIGIT_OFFSET).toString() : char;
86
88
  }).join("");
87
89
  }
88
90
  function formatIBANString(iban) {
@@ -120,7 +122,8 @@ function validateIBAN(input) {
120
122
  return {
121
123
  valid: true,
122
124
  value: cleaned,
123
- formatted: formatIBANString(cleaned)
125
+ formatted: formatIBANString(cleaned),
126
+ countryCode
124
127
  };
125
128
  }
126
129
 
@@ -252,7 +255,7 @@ function validateCardNumber(input) {
252
255
  let sum = 0;
253
256
  let shouldDouble = false;
254
257
  for (let i = digits.length - 1; i >= 0; i--) {
255
- let digit = parseInt(digits[i], 10);
258
+ let digit = Number.parseInt(digits[i], 10);
256
259
  if (shouldDouble) {
257
260
  digit *= 2;
258
261
  if (digit > 9) digit -= 9;
@@ -279,16 +282,22 @@ function refineWith(validator) {
279
282
  validate: (val) => validator(val).valid,
280
283
  message: (val) => {
281
284
  const result = validator(val);
282
- return { message: result.valid ? "" : result.error };
285
+ return { message: result.valid ? "" : result.error ?? "Invalid" };
283
286
  }
284
287
  };
285
288
  }
286
- var ibanSchema = zod.z.string().refine(refineWith(validateIBAN).validate, refineWith(validateIBAN).message);
287
- var sortCodeSchema = zod.z.string().refine(refineWith(validateUKSortCode).validate, refineWith(validateUKSortCode).message);
288
- var accountNumberSchema = zod.z.string().refine(refineWith(validateUKAccountNumber).validate, refineWith(validateUKAccountNumber).message);
289
- var currencySchema = zod.z.string().refine(refineWith(validateCurrencyCode).validate, refineWith(validateCurrencyCode).message);
290
- var bicSchema = zod.z.string().refine(refineWith(validateBIC).validate, refineWith(validateBIC).message);
291
- var cardNumberSchema = zod.z.string().refine(refineWith(validateCardNumber).validate, refineWith(validateCardNumber).message);
289
+ var ibanRefiner = refineWith(validateIBAN);
290
+ var sortCodeRefiner = refineWith(validateUKSortCode);
291
+ var accountNumberRefiner = refineWith(validateUKAccountNumber);
292
+ var currencyRefiner = refineWith(validateCurrencyCode);
293
+ var bicRefiner = refineWith(validateBIC);
294
+ var cardNumberRefiner = refineWith(validateCardNumber);
295
+ var ibanSchema = zod.z.string().refine(ibanRefiner.validate, ibanRefiner.message);
296
+ var sortCodeSchema = zod.z.string().refine(sortCodeRefiner.validate, sortCodeRefiner.message);
297
+ var accountNumberSchema = zod.z.string().refine(accountNumberRefiner.validate, accountNumberRefiner.message);
298
+ var currencySchema = zod.z.string().refine(currencyRefiner.validate, currencyRefiner.message);
299
+ var bicSchema = zod.z.string().refine(bicRefiner.validate, bicRefiner.message);
300
+ var cardNumberSchema = zod.z.string().refine(cardNumberRefiner.validate, cardNumberRefiner.message);
292
301
  var ukPaymentSchema = zod.z.object({
293
302
  sortCode: sortCodeSchema,
294
303
  accountNumber: accountNumberSchema,