@verifnow/sdk 1.5.0 → 1.6.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/README.md +2 -1
- package/dist/index.cjs +13 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +24 -3
- package/dist/index.d.ts +24 -3
- package/dist/index.js +13 -2
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -89,7 +89,7 @@ await client.validatePhone('+33612345678');
|
|
|
89
89
|
await client.validateIban('FR7630006000011234567890189');
|
|
90
90
|
await client.validateVat('FR12345678901');
|
|
91
91
|
await client.validateNas('046454286'); // Canadian Social Insurance Number
|
|
92
|
-
await client.validateSsn(
|
|
92
|
+
await client.validateSsn(form.ssn); // US Social Security Number — never commit a real one
|
|
93
93
|
await client.validateNif('B12345674'); // Spanish NIF — DNI, NIE or company
|
|
94
94
|
|
|
95
95
|
// When the rule is only known at runtime
|
|
@@ -111,6 +111,7 @@ interface ValidationResult {
|
|
|
111
111
|
ibanDetails?: IbanDetails; // IBAN only — structure and checksum, separately
|
|
112
112
|
nasDetails?: NasDetails; // Canadian SIN only — temporary resident, series
|
|
113
113
|
nifDetails?: NifDetails; // Spanish NIF only — DNI, NIE or company, legal form
|
|
114
|
+
ssnDetails?: SsnDetails; // US SSN only — whether the number is an ITIN
|
|
114
115
|
quota?: QuotaInfo; // from the X-RateLimit-* headers
|
|
115
116
|
raw: Record<string, unknown>; // untouched response body
|
|
116
117
|
}
|
package/dist/index.cjs
CHANGED
|
@@ -76,7 +76,7 @@ var VerifNowResponseError = class extends VerifNowError {
|
|
|
76
76
|
};
|
|
77
77
|
|
|
78
78
|
// src/version.ts
|
|
79
|
-
var VERSION = "1.
|
|
79
|
+
var VERSION = "1.6.0";
|
|
80
80
|
|
|
81
81
|
// src/client.ts
|
|
82
82
|
var DEFAULT_BASE_URL = "https://api.verifnow.io";
|
|
@@ -147,7 +147,12 @@ var VerifNow = class {
|
|
|
147
147
|
validateNas(value, options) {
|
|
148
148
|
return this.validate("nas", value, options);
|
|
149
149
|
}
|
|
150
|
-
/**
|
|
150
|
+
/**
|
|
151
|
+
* Validate a US Social Security Number against the numbers the SSA never issues.
|
|
152
|
+
*
|
|
153
|
+
* An SSN has no check digit: a typo that lands on another possible number cannot be caught, and
|
|
154
|
+
* only the SSA can confirm a number was issued. `ssnDetails.itin` flags an IRS ITIN.
|
|
155
|
+
*/
|
|
151
156
|
validateSsn(value, options) {
|
|
152
157
|
return this.validate("ssn", value, options);
|
|
153
158
|
}
|
|
@@ -445,6 +450,11 @@ function mapNifDetails(raw) {
|
|
|
445
450
|
entityType: asString(d.entity_type)
|
|
446
451
|
};
|
|
447
452
|
}
|
|
453
|
+
function mapSsnDetails(raw) {
|
|
454
|
+
if (raw === null || typeof raw !== "object") return void 0;
|
|
455
|
+
const d = raw;
|
|
456
|
+
return { itin: asBoolean(d.itin) };
|
|
457
|
+
}
|
|
448
458
|
function mapResult(payload, quota) {
|
|
449
459
|
return {
|
|
450
460
|
valid: payload.valid === true,
|
|
@@ -458,6 +468,7 @@ function mapResult(payload, quota) {
|
|
|
458
468
|
ibanDetails: mapIbanDetails(payload.ibanDetails),
|
|
459
469
|
nasDetails: mapNasDetails(payload.nasDetails),
|
|
460
470
|
nifDetails: mapNifDetails(payload.nifDetails),
|
|
471
|
+
ssnDetails: mapSsnDetails(payload.ssnDetails),
|
|
461
472
|
quota,
|
|
462
473
|
raw: payload
|
|
463
474
|
};
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/errors.ts","../src/version.ts","../src/client.ts","../src/types.ts"],"sourcesContent":["export { VerifNow, type RequestOptions } from './client.js';\n\nexport {\n VerifNowError,\n VerifNowAuthError,\n VerifNowRequestError,\n VerifNowRateLimitError,\n VerifNowServerError,\n VerifNowConnectionError,\n VerifNowResponseError,\n} from './errors.js';\n\nexport {\n VALIDATION_RULES,\n type Deliverability,\n type EmailDetails,\n type EmailSignals,\n type IbanDetails,\n type NasDetails,\n type NifDetails,\n type NifType,\n type PhoneDetails,\n type PhoneLineType,\n type QuotaInfo,\n type RetryOptions,\n type RiskLevel,\n type ValidationLevel,\n type ValidationResult,\n type ValidationRule,\n type VatDetails,\n type VatSource,\n type VerifNowOptions,\n} from './types.js';\n\nexport { VERSION } from './version.js';\n","import type { QuotaInfo } from './types.js';\n\n/**\n * Base class for every error this SDK throws.\n *\n * The SDK fails loudly on purpose. A validation client that swallows a network failure and\n * reports `valid: true` turns an outage into silently accepted bad data, and the outage stays\n * invisible until someone audits the database. Catch these and decide explicitly — accepting the\n * input on failure is a reasonable choice, but it should be a choice.\n *\n * @example\n * ```ts\n * try {\n * const result = await client.validateEmail(input);\n * return result.valid;\n * } catch (error) {\n * if (error instanceof VerifNowRateLimitError) throw error; // back-pressure, do not swallow\n * logger.warn({ error }, 'VerifNow unavailable, accepting input unverified');\n * return true;\n * }\n * ```\n */\nexport class VerifNowError extends Error {\n /** HTTP status, when the failure came back from the API rather than the network. */\n readonly status?: number;\n /** Correlation id from the `X-Request-Id` response header, useful in support requests. */\n readonly requestId?: string;\n\n constructor(\n message: string,\n options: { status?: number; requestId?: string; cause?: unknown } = {},\n ) {\n super(message, { cause: options.cause });\n this.name = new.target.name;\n this.status = options.status;\n this.requestId = options.requestId;\n Error.captureStackTrace?.(this, new.target);\n }\n}\n\n/** The API key is missing, malformed, revoked, or not authorised for this endpoint (401/403). */\nexport class VerifNowAuthError extends VerifNowError {}\n\n/**\n * The request was rejected as malformed (400).\n *\n * Retrying is pointless — the payload itself needs to change.\n */\nexport class VerifNowRequestError extends VerifNowError {}\n\n/** The monthly quota or the concurrency limit was exceeded (429). */\nexport class VerifNowRateLimitError extends VerifNowError {\n /** Quota counters from the response headers, when present. */\n readonly quota?: QuotaInfo;\n /** Seconds to wait before retrying, derived from `Retry-After` or `X-RateLimit-Reset`. */\n readonly retryAfterSeconds?: number;\n\n constructor(\n message: string,\n options: {\n status?: number;\n requestId?: string;\n cause?: unknown;\n quota?: QuotaInfo;\n retryAfterSeconds?: number;\n } = {},\n ) {\n super(message, options);\n this.quota = options.quota;\n this.retryAfterSeconds = options.retryAfterSeconds;\n }\n}\n\n/** The API failed to process the request (5xx). Retried automatically before surfacing. */\nexport class VerifNowServerError extends VerifNowError {}\n\n/**\n * The API could not be reached at all: DNS failure, refused connection, TLS error, or the\n * request exceeded `timeoutMs`.\n *\n * A wrong `baseUrl` surfaces here, which is why it names the URL it tried.\n */\nexport class VerifNowConnectionError extends VerifNowError {\n /** True when the failure was the client-side timeout rather than a transport error. */\n readonly timedOut: boolean;\n\n constructor(\n message: string,\n options: { cause?: unknown; timedOut?: boolean } = {},\n ) {\n super(message, { cause: options.cause });\n this.timedOut = options.timedOut ?? false;\n }\n}\n\n/** The API returned a success status with a body this SDK could not parse. */\nexport class VerifNowResponseError extends VerifNowError {}\n","/**\n * SDK version, sent to the API as `X-VerifNow-SDK: node/<version>` so calls made through an\n * official SDK can be told apart from hand-rolled integrations.\n *\n * Kept in sync with `package.json` by a test — bump both together.\n */\nexport const VERSION = '1.5.0';\n","import {\n VerifNowAuthError,\n VerifNowConnectionError,\n VerifNowError,\n VerifNowRateLimitError,\n VerifNowRequestError,\n VerifNowResponseError,\n VerifNowServerError,\n} from './errors.js';\nimport type {\n EmailDetails,\n EmailSignals,\n IbanDetails,\n NasDetails,\n NifDetails,\n PhoneDetails,\n QuotaInfo,\n RetryOptions,\n ValidationResult,\n ValidationRule,\n VatDetails,\n VerifNowOptions,\n} from './types.js';\nimport { VERSION } from './version.js';\n\nconst DEFAULT_BASE_URL = 'https://api.verifnow.io';\nconst DEFAULT_TIMEOUT_MS = 5_000;\nconst DEFAULT_RETRY: Required<RetryOptions> = {\n attempts: 2,\n backoffMs: 200,\n maxBackoffMs: 2_000,\n};\n\n/** Per-call overrides. */\nexport interface RequestOptions {\n /** Override the client timeout for this call. */\n timeoutMs?: number;\n /** Cancel the call from your own controller. Combined with the timeout. */\n signal?: AbortSignal;\n}\n\n/**\n * Client for the VerifNow validation API.\n *\n * @example\n * ```ts\n * import { VerifNow } from '@verifnow/sdk';\n *\n * const client = new VerifNow({ apiKey: process.env.VERIFNOW_API_KEY! });\n * const result = await client.validateEmail('user@example.com');\n *\n * if (!result.valid) console.log(result.message);\n * if (result.emailDetails?.signals?.typoDetected) {\n * console.log('Did you mean', result.emailDetails.signals.suggestedDomain);\n * }\n * ```\n */\nexport class VerifNow {\n readonly #apiKey: string;\n readonly #baseUrl: string;\n readonly #timeoutMs: number;\n readonly #retry: Required<RetryOptions> | null;\n readonly #headers: Record<string, string>;\n readonly #fetch: typeof globalThis.fetch;\n\n constructor(options: VerifNowOptions) {\n if (!options?.apiKey || options.apiKey.trim() === '') {\n throw new VerifNowError(\n 'A VerifNow API key is required. Create one in the dashboard and pass it as `apiKey`.',\n );\n }\n\n const fetchImpl = options.fetch ?? globalThis.fetch;\n if (typeof fetchImpl !== 'function') {\n throw new VerifNowError(\n 'No global fetch available. Use Node 18 or later, or pass a `fetch` implementation.',\n );\n }\n\n this.#apiKey = options.apiKey.trim();\n // Trailing slashes would produce `//api/v1/...`, which some proxies reject.\n this.#baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\\/+$/, '');\n this.#timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n this.#retry =\n options.retry === false ? null : { ...DEFAULT_RETRY, ...(options.retry ?? {}) };\n this.#headers = options.headers ?? {};\n this.#fetch = fetchImpl.bind(globalThis);\n }\n\n /** Validate an email address: syntax, DNS/MX, typo, disposable, role-based, quality score. */\n validateEmail(value: string, options?: RequestOptions): Promise<ValidationResult> {\n return this.validate('email', value, options);\n }\n\n /**\n * Validate a phone number against its country's numbering plan.\n *\n * The number must include its country code (`+33…` or `0033…`). Valid numbers come back in\n * E.164 as `normalizedValue`, with country and line type in `phoneDetails`.\n */\n validatePhone(value: string, options?: RequestOptions): Promise<ValidationResult> {\n return this.validate('phone', value, options);\n }\n\n /**\n * Validate an IBAN against the SWIFT registry entry for its country, then its check digits.\n *\n * `ibanDetails` reports the two separately: check digits catch a typo, the registry catches an\n * account number that could never exist in that country.\n */\n validateIban(value: string, options?: RequestOptions): Promise<ValidationResult> {\n return this.validate('iban', value, options);\n }\n\n /** Validate a VAT number. */\n validateVat(value: string, options?: RequestOptions): Promise<ValidationResult> {\n return this.validate('vat', value, options);\n }\n\n /**\n * Validate a Canadian Social Insurance Number: format and Luhn check digit.\n *\n * `nasDetails` flags a temporary resident's number (it expires with their permit) and numbers\n * from series not issued to individuals. Only collect a SIN where the law requires it.\n */\n validateNas(value: string, options?: RequestOptions): Promise<ValidationResult> {\n return this.validate('nas', value, options);\n }\n\n /** Validate a US Social Security Number. */\n validateSsn(value: string, options?: RequestOptions): Promise<ValidationResult> {\n return this.validate('ssn', value, options);\n }\n\n /**\n * Validate a Spanish NIF: a DNI, a NIE (foreign nationals), the K/L/M series, or a company NIF.\n *\n * `nifDetails` says which, whether it belongs to a person, and for a company its legal form.\n * Spanish only — a Portuguese NIF is a different scheme and is not accepted here.\n */\n validateNif(value: string, options?: RequestOptions): Promise<ValidationResult> {\n return this.validate('nif', value, options);\n }\n\n /**\n * Validate a value against any rule.\n *\n * The typed helpers above call this. Use it directly when the rule is chosen at runtime.\n */\n async validate(\n rule: ValidationRule,\n value: string,\n options: RequestOptions = {},\n ): Promise<ValidationResult> {\n if (typeof value !== 'string' || value.trim() === '') {\n // Caught here rather than server-side: an empty value consumes quota and can only fail.\n throw new VerifNowRequestError(\n `Cannot validate an empty value for rule \"${rule}\".`,\n );\n }\n\n const url = `${this.#baseUrl}/api/v1/validate/${rule}`;\n const body = JSON.stringify({ value });\n const maxAttempts = this.#retry ? this.#retry.attempts + 1 : 1;\n\n let lastError: VerifNowError | undefined;\n\n for (let attempt = 0; attempt < maxAttempts; attempt++) {\n try {\n return await this.#requestOnce(url, body, options);\n } catch (error) {\n if (!(error instanceof VerifNowError)) throw error;\n lastError = error;\n\n const isLastAttempt = attempt === maxAttempts - 1;\n if (isLastAttempt || !this.#retry) throw error;\n\n const delay = this.#retryDelay(error, attempt);\n if (delay === null) throw error;\n\n await sleep(delay);\n }\n }\n\n /* c8 ignore next -- the loop either returns or throws */\n throw lastError ?? new VerifNowError('Request failed');\n }\n\n /**\n * How long to wait before retrying, or `null` when the error should surface immediately.\n *\n * A 429 is retried only when the reset is close: the concurrency limit clears in\n * milliseconds, but a spent monthly quota does not, and sleeping on it helps nobody.\n */\n #retryDelay(error: VerifNowError, attempt: number): number | null {\n const retry = this.#retry!;\n const backoff = Math.min(retry.backoffMs * 2 ** attempt, retry.maxBackoffMs);\n\n if (error instanceof VerifNowRateLimitError) {\n const waitMs = (error.retryAfterSeconds ?? 0) * 1000;\n if (waitMs > retry.maxBackoffMs) return null;\n return Math.max(waitMs, backoff);\n }\n\n if (error instanceof VerifNowServerError) return backoff;\n // A timeout is retried: the deadline is ours, and the next attempt gets a fresh one.\n if (error instanceof VerifNowConnectionError) return backoff;\n\n // 400, 401 and unparseable bodies will fail identically on a second attempt.\n return null;\n }\n\n async #requestOnce(\n url: string,\n body: string,\n options: RequestOptions,\n ): Promise<ValidationResult> {\n const timeoutMs = options.timeoutMs ?? this.#timeoutMs;\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), timeoutMs);\n const abortFromCaller = () => controller.abort();\n options.signal?.addEventListener('abort', abortFromCaller, { once: true });\n\n let response: Response;\n try {\n response = await this.#fetch(url, {\n method: 'POST',\n headers: {\n ...this.#headers,\n 'Content-Type': 'application/json',\n Accept: 'application/json',\n 'X-API-KEY': this.#apiKey,\n 'X-VerifNow-SDK': `node/${VERSION}`,\n },\n body,\n signal: controller.signal,\n });\n } catch (cause) {\n // The caller's own cancellation is theirs to handle, not a transport failure.\n if (options.signal?.aborted) throw cause;\n\n const timedOut = controller.signal.aborted;\n throw new VerifNowConnectionError(\n timedOut\n ? `VerifNow request to ${url} timed out after ${timeoutMs}ms.`\n : `Could not reach the VerifNow API at ${url}. Check \\`baseUrl\\` and network access.`,\n { cause, timedOut },\n );\n } finally {\n clearTimeout(timer);\n options.signal?.removeEventListener('abort', abortFromCaller);\n }\n\n return this.#handleResponse(response);\n }\n\n async #handleResponse(response: Response): Promise<ValidationResult> {\n const requestId = response.headers.get('X-Request-Id') ?? undefined;\n const quota = parseQuota(response.headers);\n\n if (response.ok) {\n let payload: unknown;\n try {\n payload = await response.json();\n } catch (cause) {\n throw new VerifNowResponseError(\n 'VerifNow returned a success status with a body that is not valid JSON.',\n { status: response.status, requestId, cause },\n );\n }\n\n if (payload === null || typeof payload !== 'object') {\n throw new VerifNowResponseError(\n 'VerifNow returned an unexpected response shape.',\n { status: response.status, requestId },\n );\n }\n\n return mapResult(payload as Record<string, unknown>, quota);\n }\n\n const message = await readErrorMessage(response);\n const context = { status: response.status, requestId };\n\n if (response.status === 401 || response.status === 403) {\n throw new VerifNowAuthError(\n `VerifNow rejected the API key (${response.status}): ${message}`,\n context,\n );\n }\n\n if (response.status === 429) {\n throw new VerifNowRateLimitError(`VerifNow rate limit reached: ${message}`, {\n ...context,\n quota,\n retryAfterSeconds: parseRetryAfter(response.headers, quota),\n });\n }\n\n if (response.status >= 500) {\n throw new VerifNowServerError(\n `VerifNow returned ${response.status}: ${message}`,\n context,\n );\n }\n\n throw new VerifNowRequestError(\n `VerifNow rejected the request (${response.status}): ${message}`,\n context,\n );\n }\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\nfunction toNumber(value: string | null): number | undefined {\n if (value === null) return undefined;\n const parsed = Number(value);\n return Number.isFinite(parsed) ? parsed : undefined;\n}\n\nfunction parseQuota(headers: Headers): QuotaInfo | undefined {\n const limit = toNumber(headers.get('X-RateLimit-Limit'));\n const remaining = toNumber(headers.get('X-RateLimit-Remaining'));\n const resetSeconds = toNumber(headers.get('X-RateLimit-Reset'));\n const overage = headers.get('X-Quota-Overage') === 'true';\n\n if (\n limit === undefined &&\n remaining === undefined &&\n resetSeconds === undefined &&\n !overage\n ) {\n return undefined;\n }\n\n return {\n limit,\n remaining,\n resetAt: resetSeconds === undefined ? undefined : new Date(resetSeconds * 1000),\n overage,\n };\n}\n\nfunction parseRetryAfter(headers: Headers, quota?: QuotaInfo): number | undefined {\n const retryAfter = headers.get('Retry-After');\n if (retryAfter !== null) {\n const seconds = Number(retryAfter);\n if (Number.isFinite(seconds)) return seconds;\n\n // RFC 7231 also allows an HTTP-date.\n const asDate = Date.parse(retryAfter);\n if (!Number.isNaN(asDate)) {\n return Math.max(0, Math.ceil((asDate - Date.now()) / 1000));\n }\n }\n\n if (quota?.resetAt) {\n return Math.max(0, Math.ceil((quota.resetAt.getTime() - Date.now()) / 1000));\n }\n\n return undefined;\n}\n\nasync function readErrorMessage(response: Response): Promise<string> {\n try {\n const text = await response.text();\n if (!text) return response.statusText || 'no details';\n\n try {\n const parsed = JSON.parse(text) as Record<string, unknown>;\n const message = parsed.message ?? parsed.error;\n if (typeof message === 'string' && message !== '') return message;\n } catch {\n // Not JSON — a proxy or the servlet container's default error page.\n }\n\n return text.slice(0, 500);\n } catch {\n return response.statusText || 'no details';\n }\n}\n\nfunction asString(value: unknown): string | undefined {\n return typeof value === 'string' ? value : undefined;\n}\n\nfunction asBoolean(value: unknown): boolean | undefined {\n return typeof value === 'boolean' ? value : undefined;\n}\n\nfunction asNumber(value: unknown): number | undefined {\n return typeof value === 'number' && Number.isFinite(value) ? value : undefined;\n}\n\nfunction mapSignals(raw: unknown): EmailSignals | undefined {\n if (raw === null || typeof raw !== 'object') return undefined;\n const s = raw as Record<string, unknown>;\n\n return {\n syntaxValid: asBoolean(s.syntax_valid),\n mxValid: asBoolean(s.mx_valid),\n typoDetected: asBoolean(s.typo_detected),\n suggestedDomain: asString(s.suggested_domain),\n disposable: asBoolean(s.disposable),\n roleBased: asBoolean(s.role_based),\n freeProvider: asBoolean(s.free_provider),\n domainAgeDays: asNumber(s.domain_age_days),\n mxProvider: asString(s.mx_provider),\n mxQualityScore: asNumber(s.mx_quality_score),\n };\n}\n\nfunction mapEmailDetails(raw: unknown): EmailDetails | undefined {\n if (raw === null || typeof raw !== 'object') return undefined;\n const d = raw as Record<string, unknown>;\n\n return {\n signals: mapSignals(d.signals),\n riskScore: asNumber(d.risk_score),\n riskLevel: asString(d.risk_level) as EmailDetails['riskLevel'],\n deliverability: asString(d.deliverability) as EmailDetails['deliverability'],\n appliedLevel: asString(d.applied_level) as EmailDetails['appliedLevel'],\n };\n}\n\n/**\n * Reads `registered`, preserving the difference between `false` and `null`.\n *\n * `asBoolean` cannot be used here: it maps `null` to `undefined`, which would erase the one\n * distinction the whole VAT design exists to carry. `false` means the registry answered and the\n * number is not there; `null` means nobody could ask. A caller that cannot tell them apart will\n * reject real businesses whenever VIES is down.\n *\n * A missing key is read as `null` for the same reason — unknown, not absent.\n */\nfunction asRegistered(value: unknown): boolean | null {\n return typeof value === 'boolean' ? value : null;\n}\n\nfunction asDate(value: unknown): Date | undefined {\n if (typeof value !== 'string') return undefined;\n const parsed = new Date(value);\n return Number.isNaN(parsed.getTime()) ? undefined : parsed;\n}\n\nfunction mapVatDetails(raw: unknown): VatDetails | undefined {\n if (raw === null || typeof raw !== 'object') return undefined;\n const d = raw as Record<string, unknown>;\n\n return {\n formatValid: asBoolean(d.format_valid),\n registered: asRegistered(d.registered),\n countryCode: asString(d.country_code),\n source: asString(d.source) as VatDetails['source'],\n checkedAt: asDate(d.checked_at),\n traderName: asString(d.trader_name),\n traderAddress: asString(d.trader_address),\n viesAvailable: asBoolean(d.vies_available),\n consultationNumber: asString(d.consultation_number),\n };\n}\n\nfunction mapPhoneDetails(raw: unknown): PhoneDetails | undefined {\n if (raw === null || typeof raw !== 'object') return undefined;\n const d = raw as Record<string, unknown>;\n\n return {\n countryCode: asString(d.country_code),\n callingCode: asNumber(d.calling_code),\n lineType: asString(d.line_type) as PhoneDetails['lineType'],\n internationalFormat: asString(d.international_format),\n nationalFormat: asString(d.national_format),\n };\n}\n\nfunction mapIbanDetails(raw: unknown): IbanDetails | undefined {\n if (raw === null || typeof raw !== 'object') return undefined;\n const d = raw as Record<string, unknown>;\n\n return {\n countryCode: asString(d.country_code),\n structureValid: asBoolean(d.structure_valid),\n checksumValid: asBoolean(d.checksum_valid),\n length: asNumber(d.length),\n expectedLength: asNumber(d.expected_length),\n formatted: asString(d.formatted),\n };\n}\n\nfunction mapNasDetails(raw: unknown): NasDetails | undefined {\n if (raw === null || typeof raw !== 'object') return undefined;\n const d = raw as Record<string, unknown>;\n\n return {\n checksumValid: asBoolean(d.checksum_valid),\n temporaryResident: asBoolean(d.temporary_resident),\n individualSeries: asBoolean(d.individual_series),\n formatted: asString(d.formatted),\n };\n}\n\nfunction mapNifDetails(raw: unknown): NifDetails | undefined {\n if (raw === null || typeof raw !== 'object') return undefined;\n const d = raw as Record<string, unknown>;\n\n return {\n type: asString(d.type) as NifDetails['type'],\n naturalPerson: asBoolean(d.natural_person),\n checksumValid: asBoolean(d.checksum_valid),\n entityLetter: asString(d.entity_letter),\n entityType: asString(d.entity_type),\n };\n}\n\n/**\n * Maps the API's snake_case diagnostics onto camelCase, so a TypeScript caller is not switching\n * naming conventions mid-expression. The untouched body stays available on `raw`.\n */\nfunction mapResult(\n payload: Record<string, unknown>,\n quota: QuotaInfo | undefined,\n): ValidationResult {\n return {\n valid: payload.valid === true,\n message: asString(payload.message),\n normalizedValue: asString(payload.normalizedValue) ?? null,\n originalValue: asString(payload.originalValue),\n validationLevel: asString(payload.validationLevel) as ValidationResult['validationLevel'],\n emailDetails: mapEmailDetails(payload.emailDetails),\n vatDetails: mapVatDetails(payload.vatDetails),\n phoneDetails: mapPhoneDetails(payload.phoneDetails),\n ibanDetails: mapIbanDetails(payload.ibanDetails),\n nasDetails: mapNasDetails(payload.nasDetails),\n nifDetails: mapNifDetails(payload.nifDetails),\n quota,\n raw: payload,\n };\n}\n","/**\n * Validation rules exposed by the VerifNow API.\n *\n * Each maps to `POST /api/v1/validate/{rule}`.\n */\nexport type ValidationRule =\n | 'email'\n | 'phone'\n | 'iban'\n | 'vat'\n | 'nas'\n | 'ssn'\n | 'nif';\n\nexport const VALIDATION_RULES: readonly ValidationRule[] = [\n 'email',\n 'phone',\n 'iban',\n 'vat',\n 'nas',\n 'ssn',\n 'nif',\n] as const;\n\n/**\n * Depth of checks applied to a request, decided by the plan attached to the API key.\n *\n * `STANDARD` runs on the FREE and STARTER plans, `ADVANCED` on GROWTH, `PREMIUM` on PRO.\n * Branch on this rather than on the plan name: it is the only value that tells you which\n * signals are actually present in the response.\n */\nexport type ValidationLevel = 'BASIC' | 'STANDARD' | 'ADVANCED' | 'PREMIUM';\n\n/** Categorical risk assessment. Returned from `ADVANCED` depth upward. */\nexport type RiskLevel = 'LOW' | 'MEDIUM' | 'HIGH';\n\nexport type Deliverability =\n | 'DELIVERABLE'\n | 'RISKY'\n | 'UNDELIVERABLE'\n | 'UNKNOWN';\n\n/**\n * Per-signal breakdown behind an email verdict.\n *\n * Fields are `undefined` when the applied level does not compute them — the last four require\n * `ADVANCED` depth or higher. Check `ValidationResult.appliedLevel` before relying on one.\n */\nexport interface EmailSignals {\n /** The address matches the syntax pattern for the applied level. */\n syntaxValid?: boolean;\n /** The domain resolves and publishes MX (or fallback A) records. */\n mxValid?: boolean;\n /** A likely typo was found in the domain, e.g. `gmail.con`. */\n typoDetected?: boolean;\n /** The correction proposed when `typoDetected` is true. */\n suggestedDomain?: string;\n /** The domain belongs to a throwaway mailbox provider. */\n disposable?: boolean;\n /** The local part is a shared mailbox: `info@`, `admin@`, `noreply@`. */\n roleBased?: boolean;\n /** The domain is a consumer mailbox provider. Requires ADVANCED. */\n freeProvider?: boolean;\n /** Estimated age of the domain in days. Requires ADVANCED. */\n domainAgeDays?: number;\n /** Identified mail provider, e.g. `google`. Requires ADVANCED. */\n mxProvider?: string;\n /** Mail server quality between 0 and 1. Requires ADVANCED. */\n mxQualityScore?: number;\n}\n\n/** Email-specific diagnostics. Absent when the applied level is `BASIC`. */\nexport interface EmailDetails {\n signals?: EmailSignals;\n /** Aggregated risk on a 0–100 scale, where 0 is the lowest risk. */\n riskScore?: number;\n /** Categorical risk. Requires ADVANCED depth or higher. */\n riskLevel?: RiskLevel;\n deliverability?: Deliverability;\n /** The depth actually applied, echoed back by the API. */\n appliedLevel?: ValidationLevel;\n}\n\n/**\n * Where a VAT registration verdict came from.\n *\n * VIES publishes no SLA and drops member states several times a month, so a VAT answer is not\n * always a live one. Branch on this rather than on `ValidationResult.valid` whenever the\n * difference matters for your own compliance.\n */\nexport type VatSource =\n /** Confirmed against VIES during this request. */\n | 'LIVE'\n /** Served from a VIES answer less than 24 hours old. */\n | 'CACHE'\n /** VIES was unreachable, so an older cached answer was used. */\n | 'STALE'\n /** VIES was unreachable and nothing was cached. Registration is unknown. */\n | 'UNVERIFIED'\n /** The country is outside VIES, so no registry lookup is possible. */\n | 'NOT_APPLICABLE';\n\n/** VAT-specific diagnostics. Present on `vat` validations. */\nexport interface VatDetails {\n /** The number matches its member state's structure. Decided locally, never depends on VIES. */\n formatValid?: boolean;\n /**\n * Present in the member state's registry.\n *\n * **`null` means unknown, never \"not registered.\"** It is returned when VIES could not be\n * consulted. Treating `null` as `false` rejects legitimate customers during someone else's\n * outage — the single most expensive mistake available in VAT validation.\n */\n registered: boolean | null;\n /** Member state the number belongs to, e.g. `IE`. Greece is `EL`, Northern Ireland `XI`. */\n countryCode?: string;\n source?: VatSource;\n /** When the registration was last confirmed against VIES. */\n checkedAt?: Date;\n /** Registered trading name, when the member state discloses it. Germany does not. */\n traderName?: string;\n /** Registered address, when the member state discloses it. */\n traderAddress?: string;\n /** Whether VIES could answer for this country during the request. */\n viesAvailable?: boolean;\n /**\n * The consultation number VIES issued for this lookup — the receipt a tax authority accepts as\n * evidence that you checked. Present only when your account has its own VAT number configured,\n * because VIES issues one only to an identified requester.\n */\n consultationNumber?: string;\n}\n\n/**\n * Kind of line, according to the country's numbering plan.\n *\n * A `PREMIUM_RATE` or `VOIP` number is still `valid` — it exists. This is how you decide to\n * exclude one, rather than the API deciding for you.\n */\nexport type PhoneLineType =\n | 'MOBILE'\n | 'FIXED_LINE'\n /** The plan does not distinguish the two — the case for the US and Canada. */\n | 'FIXED_LINE_OR_MOBILE'\n | 'TOLL_FREE'\n | 'PREMIUM_RATE'\n | 'SHARED_COST'\n | 'VOIP'\n | 'PERSONAL_NUMBER'\n | 'PAGER'\n | 'UAN'\n | 'VOICEMAIL'\n | 'UNKNOWN';\n\n/**\n * Phone-specific diagnostics. Present whenever the input parsed as an international number —\n * including when it is invalid for its country, so you can tell the user which country it was\n * read as.\n */\nexport interface PhoneDetails {\n /** ISO 3166-1 alpha-2 country, e.g. `FR`. Absent when the calling code is shared by several. */\n countryCode?: string;\n /** International calling code without the plus sign, e.g. `33`. */\n callingCode?: number;\n /** Absent when the number is invalid. */\n lineType?: PhoneLineType;\n /** e.g. `+33 6 12 34 56 78`. Absent when the number is invalid. */\n internationalFormat?: string;\n /** e.g. `06 12 34 56 78`. Absent when the number is invalid. */\n nationalFormat?: string;\n}\n\n/**\n * IBAN-specific diagnostics. Present on `iban` validations.\n *\n * Structure and checksum are reported separately because they fail for different reasons:\n * `structureValid` answers \"could this be an account number in that country\" (the SWIFT\n * registry's length and layout), `checksumValid` answers \"was it typed correctly\" (mod-97).\n * There is no bank name or BIC — that needs a registry the API does not hold.\n */\nexport interface IbanDetails {\n /** The IBAN's country, from its first two characters. */\n countryCode?: string;\n /** Length and character layout match the registry entry for that country. */\n structureValid?: boolean;\n /** The mod-97 check digits are correct. */\n checksumValid?: boolean;\n /** Length of the value as submitted, spaces removed. */\n length?: number;\n /** Length the registry requires for that country; absent for an unknown country. */\n expectedLength?: number;\n /** Print format, in groups of four. Present only for a valid IBAN. */\n formatted?: string;\n}\n\n/**\n * Canadian Social Insurance Number diagnostics. Present on `nas` validations.\n *\n * There is no province and no expiry date: the first digit no longer reliably identifies a\n * province, and a temporary resident's SIN expires with their permit, which only the document\n * shows.\n */\nexport interface NasDetails {\n /** The Luhn check digit is correct. */\n checksumValid?: boolean;\n /**\n * A 9-series number, issued to temporary residents. It expires with the holder's permit, and the\n * number itself does not say when — check the document.\n */\n temporaryResident?: boolean;\n /**\n * The first digit belongs to a series issued to individuals. `false` for numbers starting with\n * 0 or 8 — including 046 454 286, the government's sample number, which is why it is safe to\n * use in tests.\n */\n individualSeries?: boolean;\n /** Printed form, e.g. `046 454 286`. */\n formatted?: string;\n}\n\n/** The kinds of Spanish tax identification number. */\nexport type NifType =\n /** Spanish national with a DNI: 8 digits and a letter. */\n | 'DNI'\n /** Foreign national: X, Y or Z, 7 digits and a letter. */\n | 'NIE'\n /** Spanish national under 14 without a DNI. */\n | 'NIF_K'\n /** Spanish national resident abroad, staying under six months. */\n | 'NIF_L'\n /** Foreign national without a NIE. */\n | 'NIF_M'\n /** A company or other entity: a letter for the legal form, 7 digits, a control character. */\n | 'ENTITY';\n\n/** Spanish NIF diagnostics. Present on `nif` validations. */\nexport interface NifDetails {\n type?: NifType;\n /** The number belongs to a person rather than a company or other entity. */\n naturalPerson?: boolean;\n /** The control character is correct. */\n checksumValid?: boolean;\n /** For an entity, the letter that encodes its legal form, e.g. `B`. */\n entityLetter?: string;\n /** For an entity, its legal form, e.g. `Private limited company (Sociedad de responsabilidad limitada)`. */\n entityType?: string;\n}\n\n/** Outcome of a single validation call. */\nexport interface ValidationResult {\n /** Whether the value passed every check the applied level ran. */\n valid: boolean;\n /** Human-readable explanation of the verdict. */\n message?: string;\n /** Canonical form of the input — `null` when the value is invalid. */\n normalizedValue: string | null;\n /** The value exactly as submitted. */\n originalValue?: string;\n /** Depth applied to this request. */\n validationLevel?: ValidationLevel;\n /** Present for email validations from `STANDARD` depth upward. */\n emailDetails?: EmailDetails;\n /** Present for VAT validations. */\n vatDetails?: VatDetails;\n /** Present for phone validations. The E.164 form is `normalizedValue`. */\n phoneDetails?: PhoneDetails;\n /** Present for IBAN validations. */\n ibanDetails?: IbanDetails;\n /** Present for Canadian SIN (`nas`) validations. */\n nasDetails?: NasDetails;\n /** Present for Spanish NIF validations. */\n nifDetails?: NifDetails;\n /** Quota state reported by the response headers. */\n quota?: QuotaInfo;\n /** The unmodified JSON body, for fields this SDK version does not model yet. */\n raw: Record<string, unknown>;\n}\n\n/** Quota counters read from the `X-RateLimit-*` response headers. */\nexport interface QuotaInfo {\n /** Validations included in the current billing period. */\n limit?: number;\n /** Validations left before overage or blocking. */\n remaining?: number;\n /** When the current period resets. */\n resetAt?: Date;\n /** True once you are past the included quota and into per-unit billing. */\n overage?: boolean;\n}\n\nexport interface RetryOptions {\n /**\n * Retry attempts after the first failure. Defaults to 2, so up to 3 requests in total.\n * Only connection failures, 429 and 5xx are retried — never a 400 or 401, which will not\n * succeed on a second try.\n */\n attempts?: number;\n /** Delay before the first retry, in ms. Doubles each attempt. Defaults to 200. */\n backoffMs?: number;\n /** Upper bound on a single backoff delay, in ms. Defaults to 2000. */\n maxBackoffMs?: number;\n}\n\nexport interface VerifNowOptions {\n /** API key created in the VerifNow dashboard. Sent as the `X-API-KEY` header. */\n apiKey: string;\n /** Override the API origin. Defaults to `https://api.verifnow.io`. */\n baseUrl?: string;\n /** Abort a single request after this many ms. Defaults to 5000. */\n timeoutMs?: number;\n /** Retry policy, or `false` to disable retries entirely. */\n retry?: RetryOptions | false;\n /** Extra headers merged into every request. */\n headers?: Record<string, string>;\n /**\n * Replacement for the global `fetch`, for tests or a custom agent.\n * Defaults to `globalThis.fetch`.\n */\n fetch?: typeof globalThis.fetch;\n}\n"],"mappings":"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","names":["asDate"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/errors.ts","../src/version.ts","../src/client.ts","../src/types.ts"],"sourcesContent":["export { VerifNow, type RequestOptions } from './client.js';\n\nexport {\n VerifNowError,\n VerifNowAuthError,\n VerifNowRequestError,\n VerifNowRateLimitError,\n VerifNowServerError,\n VerifNowConnectionError,\n VerifNowResponseError,\n} from './errors.js';\n\nexport {\n VALIDATION_RULES,\n type Deliverability,\n type EmailDetails,\n type EmailSignals,\n type IbanDetails,\n type NasDetails,\n type NifDetails,\n type NifType,\n type SsnDetails,\n type PhoneDetails,\n type PhoneLineType,\n type QuotaInfo,\n type RetryOptions,\n type RiskLevel,\n type ValidationLevel,\n type ValidationResult,\n type ValidationRule,\n type VatDetails,\n type VatSource,\n type VerifNowOptions,\n} from './types.js';\n\nexport { VERSION } from './version.js';\n","import type { QuotaInfo } from './types.js';\n\n/**\n * Base class for every error this SDK throws.\n *\n * The SDK fails loudly on purpose. A validation client that swallows a network failure and\n * reports `valid: true` turns an outage into silently accepted bad data, and the outage stays\n * invisible until someone audits the database. Catch these and decide explicitly — accepting the\n * input on failure is a reasonable choice, but it should be a choice.\n *\n * @example\n * ```ts\n * try {\n * const result = await client.validateEmail(input);\n * return result.valid;\n * } catch (error) {\n * if (error instanceof VerifNowRateLimitError) throw error; // back-pressure, do not swallow\n * logger.warn({ error }, 'VerifNow unavailable, accepting input unverified');\n * return true;\n * }\n * ```\n */\nexport class VerifNowError extends Error {\n /** HTTP status, when the failure came back from the API rather than the network. */\n readonly status?: number;\n /** Correlation id from the `X-Request-Id` response header, useful in support requests. */\n readonly requestId?: string;\n\n constructor(\n message: string,\n options: { status?: number; requestId?: string; cause?: unknown } = {},\n ) {\n super(message, { cause: options.cause });\n this.name = new.target.name;\n this.status = options.status;\n this.requestId = options.requestId;\n Error.captureStackTrace?.(this, new.target);\n }\n}\n\n/** The API key is missing, malformed, revoked, or not authorised for this endpoint (401/403). */\nexport class VerifNowAuthError extends VerifNowError {}\n\n/**\n * The request was rejected as malformed (400).\n *\n * Retrying is pointless — the payload itself needs to change.\n */\nexport class VerifNowRequestError extends VerifNowError {}\n\n/** The monthly quota or the concurrency limit was exceeded (429). */\nexport class VerifNowRateLimitError extends VerifNowError {\n /** Quota counters from the response headers, when present. */\n readonly quota?: QuotaInfo;\n /** Seconds to wait before retrying, derived from `Retry-After` or `X-RateLimit-Reset`. */\n readonly retryAfterSeconds?: number;\n\n constructor(\n message: string,\n options: {\n status?: number;\n requestId?: string;\n cause?: unknown;\n quota?: QuotaInfo;\n retryAfterSeconds?: number;\n } = {},\n ) {\n super(message, options);\n this.quota = options.quota;\n this.retryAfterSeconds = options.retryAfterSeconds;\n }\n}\n\n/** The API failed to process the request (5xx). Retried automatically before surfacing. */\nexport class VerifNowServerError extends VerifNowError {}\n\n/**\n * The API could not be reached at all: DNS failure, refused connection, TLS error, or the\n * request exceeded `timeoutMs`.\n *\n * A wrong `baseUrl` surfaces here, which is why it names the URL it tried.\n */\nexport class VerifNowConnectionError extends VerifNowError {\n /** True when the failure was the client-side timeout rather than a transport error. */\n readonly timedOut: boolean;\n\n constructor(\n message: string,\n options: { cause?: unknown; timedOut?: boolean } = {},\n ) {\n super(message, { cause: options.cause });\n this.timedOut = options.timedOut ?? false;\n }\n}\n\n/** The API returned a success status with a body this SDK could not parse. */\nexport class VerifNowResponseError extends VerifNowError {}\n","/**\n * SDK version, sent to the API as `X-VerifNow-SDK: node/<version>` so calls made through an\n * official SDK can be told apart from hand-rolled integrations.\n *\n * Kept in sync with `package.json` by a test — bump both together.\n */\nexport const VERSION = '1.6.0';\n","import {\n VerifNowAuthError,\n VerifNowConnectionError,\n VerifNowError,\n VerifNowRateLimitError,\n VerifNowRequestError,\n VerifNowResponseError,\n VerifNowServerError,\n} from './errors.js';\nimport type {\n EmailDetails,\n EmailSignals,\n IbanDetails,\n NasDetails,\n NifDetails,\n SsnDetails,\n PhoneDetails,\n QuotaInfo,\n RetryOptions,\n ValidationResult,\n ValidationRule,\n VatDetails,\n VerifNowOptions,\n} from './types.js';\nimport { VERSION } from './version.js';\n\nconst DEFAULT_BASE_URL = 'https://api.verifnow.io';\nconst DEFAULT_TIMEOUT_MS = 5_000;\nconst DEFAULT_RETRY: Required<RetryOptions> = {\n attempts: 2,\n backoffMs: 200,\n maxBackoffMs: 2_000,\n};\n\n/** Per-call overrides. */\nexport interface RequestOptions {\n /** Override the client timeout for this call. */\n timeoutMs?: number;\n /** Cancel the call from your own controller. Combined with the timeout. */\n signal?: AbortSignal;\n}\n\n/**\n * Client for the VerifNow validation API.\n *\n * @example\n * ```ts\n * import { VerifNow } from '@verifnow/sdk';\n *\n * const client = new VerifNow({ apiKey: process.env.VERIFNOW_API_KEY! });\n * const result = await client.validateEmail('user@example.com');\n *\n * if (!result.valid) console.log(result.message);\n * if (result.emailDetails?.signals?.typoDetected) {\n * console.log('Did you mean', result.emailDetails.signals.suggestedDomain);\n * }\n * ```\n */\nexport class VerifNow {\n readonly #apiKey: string;\n readonly #baseUrl: string;\n readonly #timeoutMs: number;\n readonly #retry: Required<RetryOptions> | null;\n readonly #headers: Record<string, string>;\n readonly #fetch: typeof globalThis.fetch;\n\n constructor(options: VerifNowOptions) {\n if (!options?.apiKey || options.apiKey.trim() === '') {\n throw new VerifNowError(\n 'A VerifNow API key is required. Create one in the dashboard and pass it as `apiKey`.',\n );\n }\n\n const fetchImpl = options.fetch ?? globalThis.fetch;\n if (typeof fetchImpl !== 'function') {\n throw new VerifNowError(\n 'No global fetch available. Use Node 18 or later, or pass a `fetch` implementation.',\n );\n }\n\n this.#apiKey = options.apiKey.trim();\n // Trailing slashes would produce `//api/v1/...`, which some proxies reject.\n this.#baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\\/+$/, '');\n this.#timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n this.#retry =\n options.retry === false ? null : { ...DEFAULT_RETRY, ...(options.retry ?? {}) };\n this.#headers = options.headers ?? {};\n this.#fetch = fetchImpl.bind(globalThis);\n }\n\n /** Validate an email address: syntax, DNS/MX, typo, disposable, role-based, quality score. */\n validateEmail(value: string, options?: RequestOptions): Promise<ValidationResult> {\n return this.validate('email', value, options);\n }\n\n /**\n * Validate a phone number against its country's numbering plan.\n *\n * The number must include its country code (`+33…` or `0033…`). Valid numbers come back in\n * E.164 as `normalizedValue`, with country and line type in `phoneDetails`.\n */\n validatePhone(value: string, options?: RequestOptions): Promise<ValidationResult> {\n return this.validate('phone', value, options);\n }\n\n /**\n * Validate an IBAN against the SWIFT registry entry for its country, then its check digits.\n *\n * `ibanDetails` reports the two separately: check digits catch a typo, the registry catches an\n * account number that could never exist in that country.\n */\n validateIban(value: string, options?: RequestOptions): Promise<ValidationResult> {\n return this.validate('iban', value, options);\n }\n\n /** Validate a VAT number. */\n validateVat(value: string, options?: RequestOptions): Promise<ValidationResult> {\n return this.validate('vat', value, options);\n }\n\n /**\n * Validate a Canadian Social Insurance Number: format and Luhn check digit.\n *\n * `nasDetails` flags a temporary resident's number (it expires with their permit) and numbers\n * from series not issued to individuals. Only collect a SIN where the law requires it.\n */\n validateNas(value: string, options?: RequestOptions): Promise<ValidationResult> {\n return this.validate('nas', value, options);\n }\n\n /**\n * Validate a US Social Security Number against the numbers the SSA never issues.\n *\n * An SSN has no check digit: a typo that lands on another possible number cannot be caught, and\n * only the SSA can confirm a number was issued. `ssnDetails.itin` flags an IRS ITIN.\n */\n validateSsn(value: string, options?: RequestOptions): Promise<ValidationResult> {\n return this.validate('ssn', value, options);\n }\n\n /**\n * Validate a Spanish NIF: a DNI, a NIE (foreign nationals), the K/L/M series, or a company NIF.\n *\n * `nifDetails` says which, whether it belongs to a person, and for a company its legal form.\n * Spanish only — a Portuguese NIF is a different scheme and is not accepted here.\n */\n validateNif(value: string, options?: RequestOptions): Promise<ValidationResult> {\n return this.validate('nif', value, options);\n }\n\n /**\n * Validate a value against any rule.\n *\n * The typed helpers above call this. Use it directly when the rule is chosen at runtime.\n */\n async validate(\n rule: ValidationRule,\n value: string,\n options: RequestOptions = {},\n ): Promise<ValidationResult> {\n if (typeof value !== 'string' || value.trim() === '') {\n // Caught here rather than server-side: an empty value consumes quota and can only fail.\n throw new VerifNowRequestError(\n `Cannot validate an empty value for rule \"${rule}\".`,\n );\n }\n\n const url = `${this.#baseUrl}/api/v1/validate/${rule}`;\n const body = JSON.stringify({ value });\n const maxAttempts = this.#retry ? this.#retry.attempts + 1 : 1;\n\n let lastError: VerifNowError | undefined;\n\n for (let attempt = 0; attempt < maxAttempts; attempt++) {\n try {\n return await this.#requestOnce(url, body, options);\n } catch (error) {\n if (!(error instanceof VerifNowError)) throw error;\n lastError = error;\n\n const isLastAttempt = attempt === maxAttempts - 1;\n if (isLastAttempt || !this.#retry) throw error;\n\n const delay = this.#retryDelay(error, attempt);\n if (delay === null) throw error;\n\n await sleep(delay);\n }\n }\n\n /* c8 ignore next -- the loop either returns or throws */\n throw lastError ?? new VerifNowError('Request failed');\n }\n\n /**\n * How long to wait before retrying, or `null` when the error should surface immediately.\n *\n * A 429 is retried only when the reset is close: the concurrency limit clears in\n * milliseconds, but a spent monthly quota does not, and sleeping on it helps nobody.\n */\n #retryDelay(error: VerifNowError, attempt: number): number | null {\n const retry = this.#retry!;\n const backoff = Math.min(retry.backoffMs * 2 ** attempt, retry.maxBackoffMs);\n\n if (error instanceof VerifNowRateLimitError) {\n const waitMs = (error.retryAfterSeconds ?? 0) * 1000;\n if (waitMs > retry.maxBackoffMs) return null;\n return Math.max(waitMs, backoff);\n }\n\n if (error instanceof VerifNowServerError) return backoff;\n // A timeout is retried: the deadline is ours, and the next attempt gets a fresh one.\n if (error instanceof VerifNowConnectionError) return backoff;\n\n // 400, 401 and unparseable bodies will fail identically on a second attempt.\n return null;\n }\n\n async #requestOnce(\n url: string,\n body: string,\n options: RequestOptions,\n ): Promise<ValidationResult> {\n const timeoutMs = options.timeoutMs ?? this.#timeoutMs;\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), timeoutMs);\n const abortFromCaller = () => controller.abort();\n options.signal?.addEventListener('abort', abortFromCaller, { once: true });\n\n let response: Response;\n try {\n response = await this.#fetch(url, {\n method: 'POST',\n headers: {\n ...this.#headers,\n 'Content-Type': 'application/json',\n Accept: 'application/json',\n 'X-API-KEY': this.#apiKey,\n 'X-VerifNow-SDK': `node/${VERSION}`,\n },\n body,\n signal: controller.signal,\n });\n } catch (cause) {\n // The caller's own cancellation is theirs to handle, not a transport failure.\n if (options.signal?.aborted) throw cause;\n\n const timedOut = controller.signal.aborted;\n throw new VerifNowConnectionError(\n timedOut\n ? `VerifNow request to ${url} timed out after ${timeoutMs}ms.`\n : `Could not reach the VerifNow API at ${url}. Check \\`baseUrl\\` and network access.`,\n { cause, timedOut },\n );\n } finally {\n clearTimeout(timer);\n options.signal?.removeEventListener('abort', abortFromCaller);\n }\n\n return this.#handleResponse(response);\n }\n\n async #handleResponse(response: Response): Promise<ValidationResult> {\n const requestId = response.headers.get('X-Request-Id') ?? undefined;\n const quota = parseQuota(response.headers);\n\n if (response.ok) {\n let payload: unknown;\n try {\n payload = await response.json();\n } catch (cause) {\n throw new VerifNowResponseError(\n 'VerifNow returned a success status with a body that is not valid JSON.',\n { status: response.status, requestId, cause },\n );\n }\n\n if (payload === null || typeof payload !== 'object') {\n throw new VerifNowResponseError(\n 'VerifNow returned an unexpected response shape.',\n { status: response.status, requestId },\n );\n }\n\n return mapResult(payload as Record<string, unknown>, quota);\n }\n\n const message = await readErrorMessage(response);\n const context = { status: response.status, requestId };\n\n if (response.status === 401 || response.status === 403) {\n throw new VerifNowAuthError(\n `VerifNow rejected the API key (${response.status}): ${message}`,\n context,\n );\n }\n\n if (response.status === 429) {\n throw new VerifNowRateLimitError(`VerifNow rate limit reached: ${message}`, {\n ...context,\n quota,\n retryAfterSeconds: parseRetryAfter(response.headers, quota),\n });\n }\n\n if (response.status >= 500) {\n throw new VerifNowServerError(\n `VerifNow returned ${response.status}: ${message}`,\n context,\n );\n }\n\n throw new VerifNowRequestError(\n `VerifNow rejected the request (${response.status}): ${message}`,\n context,\n );\n }\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\nfunction toNumber(value: string | null): number | undefined {\n if (value === null) return undefined;\n const parsed = Number(value);\n return Number.isFinite(parsed) ? parsed : undefined;\n}\n\nfunction parseQuota(headers: Headers): QuotaInfo | undefined {\n const limit = toNumber(headers.get('X-RateLimit-Limit'));\n const remaining = toNumber(headers.get('X-RateLimit-Remaining'));\n const resetSeconds = toNumber(headers.get('X-RateLimit-Reset'));\n const overage = headers.get('X-Quota-Overage') === 'true';\n\n if (\n limit === undefined &&\n remaining === undefined &&\n resetSeconds === undefined &&\n !overage\n ) {\n return undefined;\n }\n\n return {\n limit,\n remaining,\n resetAt: resetSeconds === undefined ? undefined : new Date(resetSeconds * 1000),\n overage,\n };\n}\n\nfunction parseRetryAfter(headers: Headers, quota?: QuotaInfo): number | undefined {\n const retryAfter = headers.get('Retry-After');\n if (retryAfter !== null) {\n const seconds = Number(retryAfter);\n if (Number.isFinite(seconds)) return seconds;\n\n // RFC 7231 also allows an HTTP-date.\n const asDate = Date.parse(retryAfter);\n if (!Number.isNaN(asDate)) {\n return Math.max(0, Math.ceil((asDate - Date.now()) / 1000));\n }\n }\n\n if (quota?.resetAt) {\n return Math.max(0, Math.ceil((quota.resetAt.getTime() - Date.now()) / 1000));\n }\n\n return undefined;\n}\n\nasync function readErrorMessage(response: Response): Promise<string> {\n try {\n const text = await response.text();\n if (!text) return response.statusText || 'no details';\n\n try {\n const parsed = JSON.parse(text) as Record<string, unknown>;\n const message = parsed.message ?? parsed.error;\n if (typeof message === 'string' && message !== '') return message;\n } catch {\n // Not JSON — a proxy or the servlet container's default error page.\n }\n\n return text.slice(0, 500);\n } catch {\n return response.statusText || 'no details';\n }\n}\n\nfunction asString(value: unknown): string | undefined {\n return typeof value === 'string' ? value : undefined;\n}\n\nfunction asBoolean(value: unknown): boolean | undefined {\n return typeof value === 'boolean' ? value : undefined;\n}\n\nfunction asNumber(value: unknown): number | undefined {\n return typeof value === 'number' && Number.isFinite(value) ? value : undefined;\n}\n\nfunction mapSignals(raw: unknown): EmailSignals | undefined {\n if (raw === null || typeof raw !== 'object') return undefined;\n const s = raw as Record<string, unknown>;\n\n return {\n syntaxValid: asBoolean(s.syntax_valid),\n mxValid: asBoolean(s.mx_valid),\n typoDetected: asBoolean(s.typo_detected),\n suggestedDomain: asString(s.suggested_domain),\n disposable: asBoolean(s.disposable),\n roleBased: asBoolean(s.role_based),\n freeProvider: asBoolean(s.free_provider),\n domainAgeDays: asNumber(s.domain_age_days),\n mxProvider: asString(s.mx_provider),\n mxQualityScore: asNumber(s.mx_quality_score),\n };\n}\n\nfunction mapEmailDetails(raw: unknown): EmailDetails | undefined {\n if (raw === null || typeof raw !== 'object') return undefined;\n const d = raw as Record<string, unknown>;\n\n return {\n signals: mapSignals(d.signals),\n riskScore: asNumber(d.risk_score),\n riskLevel: asString(d.risk_level) as EmailDetails['riskLevel'],\n deliverability: asString(d.deliverability) as EmailDetails['deliverability'],\n appliedLevel: asString(d.applied_level) as EmailDetails['appliedLevel'],\n };\n}\n\n/**\n * Reads `registered`, preserving the difference between `false` and `null`.\n *\n * `asBoolean` cannot be used here: it maps `null` to `undefined`, which would erase the one\n * distinction the whole VAT design exists to carry. `false` means the registry answered and the\n * number is not there; `null` means nobody could ask. A caller that cannot tell them apart will\n * reject real businesses whenever VIES is down.\n *\n * A missing key is read as `null` for the same reason — unknown, not absent.\n */\nfunction asRegistered(value: unknown): boolean | null {\n return typeof value === 'boolean' ? value : null;\n}\n\nfunction asDate(value: unknown): Date | undefined {\n if (typeof value !== 'string') return undefined;\n const parsed = new Date(value);\n return Number.isNaN(parsed.getTime()) ? undefined : parsed;\n}\n\nfunction mapVatDetails(raw: unknown): VatDetails | undefined {\n if (raw === null || typeof raw !== 'object') return undefined;\n const d = raw as Record<string, unknown>;\n\n return {\n formatValid: asBoolean(d.format_valid),\n registered: asRegistered(d.registered),\n countryCode: asString(d.country_code),\n source: asString(d.source) as VatDetails['source'],\n checkedAt: asDate(d.checked_at),\n traderName: asString(d.trader_name),\n traderAddress: asString(d.trader_address),\n viesAvailable: asBoolean(d.vies_available),\n consultationNumber: asString(d.consultation_number),\n };\n}\n\nfunction mapPhoneDetails(raw: unknown): PhoneDetails | undefined {\n if (raw === null || typeof raw !== 'object') return undefined;\n const d = raw as Record<string, unknown>;\n\n return {\n countryCode: asString(d.country_code),\n callingCode: asNumber(d.calling_code),\n lineType: asString(d.line_type) as PhoneDetails['lineType'],\n internationalFormat: asString(d.international_format),\n nationalFormat: asString(d.national_format),\n };\n}\n\nfunction mapIbanDetails(raw: unknown): IbanDetails | undefined {\n if (raw === null || typeof raw !== 'object') return undefined;\n const d = raw as Record<string, unknown>;\n\n return {\n countryCode: asString(d.country_code),\n structureValid: asBoolean(d.structure_valid),\n checksumValid: asBoolean(d.checksum_valid),\n length: asNumber(d.length),\n expectedLength: asNumber(d.expected_length),\n formatted: asString(d.formatted),\n };\n}\n\nfunction mapNasDetails(raw: unknown): NasDetails | undefined {\n if (raw === null || typeof raw !== 'object') return undefined;\n const d = raw as Record<string, unknown>;\n\n return {\n checksumValid: asBoolean(d.checksum_valid),\n temporaryResident: asBoolean(d.temporary_resident),\n individualSeries: asBoolean(d.individual_series),\n formatted: asString(d.formatted),\n };\n}\n\nfunction mapNifDetails(raw: unknown): NifDetails | undefined {\n if (raw === null || typeof raw !== 'object') return undefined;\n const d = raw as Record<string, unknown>;\n\n return {\n type: asString(d.type) as NifDetails['type'],\n naturalPerson: asBoolean(d.natural_person),\n checksumValid: asBoolean(d.checksum_valid),\n entityLetter: asString(d.entity_letter),\n entityType: asString(d.entity_type),\n };\n}\n\nfunction mapSsnDetails(raw: unknown): SsnDetails | undefined {\n if (raw === null || typeof raw !== 'object') return undefined;\n const d = raw as Record<string, unknown>;\n\n return { itin: asBoolean(d.itin) };\n}\n\n/**\n * Maps the API's snake_case diagnostics onto camelCase, so a TypeScript caller is not switching\n * naming conventions mid-expression. The untouched body stays available on `raw`.\n */\nfunction mapResult(\n payload: Record<string, unknown>,\n quota: QuotaInfo | undefined,\n): ValidationResult {\n return {\n valid: payload.valid === true,\n message: asString(payload.message),\n normalizedValue: asString(payload.normalizedValue) ?? null,\n originalValue: asString(payload.originalValue),\n validationLevel: asString(payload.validationLevel) as ValidationResult['validationLevel'],\n emailDetails: mapEmailDetails(payload.emailDetails),\n vatDetails: mapVatDetails(payload.vatDetails),\n phoneDetails: mapPhoneDetails(payload.phoneDetails),\n ibanDetails: mapIbanDetails(payload.ibanDetails),\n nasDetails: mapNasDetails(payload.nasDetails),\n nifDetails: mapNifDetails(payload.nifDetails),\n ssnDetails: mapSsnDetails(payload.ssnDetails),\n quota,\n raw: payload,\n };\n}\n","/**\n * Validation rules exposed by the VerifNow API.\n *\n * Each maps to `POST /api/v1/validate/{rule}`.\n */\nexport type ValidationRule =\n | 'email'\n | 'phone'\n | 'iban'\n | 'vat'\n | 'nas'\n | 'ssn'\n | 'nif';\n\nexport const VALIDATION_RULES: readonly ValidationRule[] = [\n 'email',\n 'phone',\n 'iban',\n 'vat',\n 'nas',\n 'ssn',\n 'nif',\n] as const;\n\n/**\n * Depth of checks applied to a request, decided by the plan attached to the API key.\n *\n * `STANDARD` runs on the FREE and STARTER plans, `ADVANCED` on GROWTH, `PREMIUM` on PRO.\n * Branch on this rather than on the plan name: it is the only value that tells you which\n * signals are actually present in the response.\n */\nexport type ValidationLevel = 'BASIC' | 'STANDARD' | 'ADVANCED' | 'PREMIUM';\n\n/** Categorical risk assessment. Returned from `ADVANCED` depth upward. */\nexport type RiskLevel = 'LOW' | 'MEDIUM' | 'HIGH';\n\nexport type Deliverability =\n | 'DELIVERABLE'\n | 'RISKY'\n | 'UNDELIVERABLE'\n | 'UNKNOWN';\n\n/**\n * Per-signal breakdown behind an email verdict.\n *\n * Fields are `undefined` when the applied level does not compute them — the last four require\n * `ADVANCED` depth or higher. Check `ValidationResult.appliedLevel` before relying on one.\n */\nexport interface EmailSignals {\n /** The address matches the syntax pattern for the applied level. */\n syntaxValid?: boolean;\n /** The domain resolves and publishes MX (or fallback A) records. */\n mxValid?: boolean;\n /** A likely typo was found in the domain, e.g. `gmail.con`. */\n typoDetected?: boolean;\n /** The correction proposed when `typoDetected` is true. */\n suggestedDomain?: string;\n /** The domain belongs to a throwaway mailbox provider. */\n disposable?: boolean;\n /** The local part is a shared mailbox: `info@`, `admin@`, `noreply@`. */\n roleBased?: boolean;\n /** The domain is a consumer mailbox provider. Requires ADVANCED. */\n freeProvider?: boolean;\n /** Estimated age of the domain in days. Requires ADVANCED. */\n domainAgeDays?: number;\n /** Identified mail provider, e.g. `google`. Requires ADVANCED. */\n mxProvider?: string;\n /** Mail server quality between 0 and 1. Requires ADVANCED. */\n mxQualityScore?: number;\n}\n\n/** Email-specific diagnostics. Absent when the applied level is `BASIC`. */\nexport interface EmailDetails {\n signals?: EmailSignals;\n /** Aggregated risk on a 0–100 scale, where 0 is the lowest risk. */\n riskScore?: number;\n /** Categorical risk. Requires ADVANCED depth or higher. */\n riskLevel?: RiskLevel;\n deliverability?: Deliverability;\n /** The depth actually applied, echoed back by the API. */\n appliedLevel?: ValidationLevel;\n}\n\n/**\n * Where a VAT registration verdict came from.\n *\n * VIES publishes no SLA and drops member states several times a month, so a VAT answer is not\n * always a live one. Branch on this rather than on `ValidationResult.valid` whenever the\n * difference matters for your own compliance.\n */\nexport type VatSource =\n /** Confirmed against VIES during this request. */\n | 'LIVE'\n /** Served from a VIES answer less than 24 hours old. */\n | 'CACHE'\n /** VIES was unreachable, so an older cached answer was used. */\n | 'STALE'\n /** VIES was unreachable and nothing was cached. Registration is unknown. */\n | 'UNVERIFIED'\n /** The country is outside VIES, so no registry lookup is possible. */\n | 'NOT_APPLICABLE';\n\n/** VAT-specific diagnostics. Present on `vat` validations. */\nexport interface VatDetails {\n /** The number matches its member state's structure. Decided locally, never depends on VIES. */\n formatValid?: boolean;\n /**\n * Present in the member state's registry.\n *\n * **`null` means unknown, never \"not registered.\"** It is returned when VIES could not be\n * consulted. Treating `null` as `false` rejects legitimate customers during someone else's\n * outage — the single most expensive mistake available in VAT validation.\n */\n registered: boolean | null;\n /** Member state the number belongs to, e.g. `IE`. Greece is `EL`, Northern Ireland `XI`. */\n countryCode?: string;\n source?: VatSource;\n /** When the registration was last confirmed against VIES. */\n checkedAt?: Date;\n /** Registered trading name, when the member state discloses it. Germany does not. */\n traderName?: string;\n /** Registered address, when the member state discloses it. */\n traderAddress?: string;\n /** Whether VIES could answer for this country during the request. */\n viesAvailable?: boolean;\n /**\n * The consultation number VIES issued for this lookup — the receipt a tax authority accepts as\n * evidence that you checked. Present only when your account has its own VAT number configured,\n * because VIES issues one only to an identified requester.\n */\n consultationNumber?: string;\n}\n\n/**\n * Kind of line, according to the country's numbering plan.\n *\n * A `PREMIUM_RATE` or `VOIP` number is still `valid` — it exists. This is how you decide to\n * exclude one, rather than the API deciding for you.\n */\nexport type PhoneLineType =\n | 'MOBILE'\n | 'FIXED_LINE'\n /** The plan does not distinguish the two — the case for the US and Canada. */\n | 'FIXED_LINE_OR_MOBILE'\n | 'TOLL_FREE'\n | 'PREMIUM_RATE'\n | 'SHARED_COST'\n | 'VOIP'\n | 'PERSONAL_NUMBER'\n | 'PAGER'\n | 'UAN'\n | 'VOICEMAIL'\n | 'UNKNOWN';\n\n/**\n * Phone-specific diagnostics. Present whenever the input parsed as an international number —\n * including when it is invalid for its country, so you can tell the user which country it was\n * read as.\n */\nexport interface PhoneDetails {\n /** ISO 3166-1 alpha-2 country, e.g. `FR`. Absent when the calling code is shared by several. */\n countryCode?: string;\n /** International calling code without the plus sign, e.g. `33`. */\n callingCode?: number;\n /** Absent when the number is invalid. */\n lineType?: PhoneLineType;\n /** e.g. `+33 6 12 34 56 78`. Absent when the number is invalid. */\n internationalFormat?: string;\n /** e.g. `06 12 34 56 78`. Absent when the number is invalid. */\n nationalFormat?: string;\n}\n\n/**\n * IBAN-specific diagnostics. Present on `iban` validations.\n *\n * Structure and checksum are reported separately because they fail for different reasons:\n * `structureValid` answers \"could this be an account number in that country\" (the SWIFT\n * registry's length and layout), `checksumValid` answers \"was it typed correctly\" (mod-97).\n * There is no bank name or BIC — that needs a registry the API does not hold.\n */\nexport interface IbanDetails {\n /** The IBAN's country, from its first two characters. */\n countryCode?: string;\n /** Length and character layout match the registry entry for that country. */\n structureValid?: boolean;\n /** The mod-97 check digits are correct. */\n checksumValid?: boolean;\n /** Length of the value as submitted, spaces removed. */\n length?: number;\n /** Length the registry requires for that country; absent for an unknown country. */\n expectedLength?: number;\n /** Print format, in groups of four. Present only for a valid IBAN. */\n formatted?: string;\n}\n\n/**\n * Canadian Social Insurance Number diagnostics. Present on `nas` validations.\n *\n * There is no province and no expiry date: the first digit no longer reliably identifies a\n * province, and a temporary resident's SIN expires with their permit, which only the document\n * shows.\n */\nexport interface NasDetails {\n /** The Luhn check digit is correct. */\n checksumValid?: boolean;\n /**\n * A 9-series number, issued to temporary residents. It expires with the holder's permit, and the\n * number itself does not say when — check the document.\n */\n temporaryResident?: boolean;\n /**\n * The first digit belongs to a series issued to individuals. `false` for numbers starting with\n * 0 or 8 — including 046 454 286, the government's sample number, which is why it is safe to\n * use in tests.\n */\n individualSeries?: boolean;\n /** Printed form, e.g. `046 454 286`. */\n formatted?: string;\n}\n\n/** The kinds of Spanish tax identification number. */\nexport type NifType =\n /** Spanish national with a DNI: 8 digits and a letter. */\n | 'DNI'\n /** Foreign national: X, Y or Z, 7 digits and a letter. */\n | 'NIE'\n /** Spanish national under 14 without a DNI. */\n | 'NIF_K'\n /** Spanish national resident abroad, staying under six months. */\n | 'NIF_L'\n /** Foreign national without a NIE. */\n | 'NIF_M'\n /** A company or other entity: a letter for the legal form, 7 digits, a control character. */\n | 'ENTITY';\n\n/** Spanish NIF diagnostics. Present on `nif` validations. */\nexport interface NifDetails {\n type?: NifType;\n /** The number belongs to a person rather than a company or other entity. */\n naturalPerson?: boolean;\n /** The control character is correct. */\n checksumValid?: boolean;\n /** For an entity, the letter that encodes its legal form, e.g. `B`. */\n entityLetter?: string;\n /** For an entity, its legal form, e.g. `Private limited company (Sociedad de responsabilidad limitada)`. */\n entityType?: string;\n}\n\n/**\n * US SSN diagnostics. Present on `ssn` validations.\n *\n * An SSN has no check digit, and since 2011 its first digits say nothing about a state, so there is\n * little a number can reveal about itself. The one thing worth knowing is whether it is an ITIN.\n */\nexport interface SsnDetails {\n /**\n * The number is an IRS ITIN, not an SSN: it starts with 9 and its fourth and fifth digits are in\n * 50-65, 70-88, 90-92 or 94-99. `valid` is `false` for the SSN, but an ITIN is an acceptable\n * taxpayer number where one is accepted (a W-9, for instance).\n */\n itin?: boolean;\n}\n\n/** Outcome of a single validation call. */\nexport interface ValidationResult {\n /** Whether the value passed every check the applied level ran. */\n valid: boolean;\n /** Human-readable explanation of the verdict. */\n message?: string;\n /** Canonical form of the input — `null` when the value is invalid. */\n normalizedValue: string | null;\n /** The value exactly as submitted. */\n originalValue?: string;\n /** Depth applied to this request. */\n validationLevel?: ValidationLevel;\n /** Present for email validations from `STANDARD` depth upward. */\n emailDetails?: EmailDetails;\n /** Present for VAT validations. */\n vatDetails?: VatDetails;\n /** Present for phone validations. The E.164 form is `normalizedValue`. */\n phoneDetails?: PhoneDetails;\n /** Present for IBAN validations. */\n ibanDetails?: IbanDetails;\n /** Present for Canadian SIN (`nas`) validations. */\n nasDetails?: NasDetails;\n /** Present for Spanish NIF validations. */\n nifDetails?: NifDetails;\n /** Present for US SSN validations. */\n ssnDetails?: SsnDetails;\n /** Quota state reported by the response headers. */\n quota?: QuotaInfo;\n /** The unmodified JSON body, for fields this SDK version does not model yet. */\n raw: Record<string, unknown>;\n}\n\n/** Quota counters read from the `X-RateLimit-*` response headers. */\nexport interface QuotaInfo {\n /** Validations included in the current billing period. */\n limit?: number;\n /** Validations left before overage or blocking. */\n remaining?: number;\n /** When the current period resets. */\n resetAt?: Date;\n /** True once you are past the included quota and into per-unit billing. */\n overage?: boolean;\n}\n\nexport interface RetryOptions {\n /**\n * Retry attempts after the first failure. Defaults to 2, so up to 3 requests in total.\n * Only connection failures, 429 and 5xx are retried — never a 400 or 401, which will not\n * succeed on a second try.\n */\n attempts?: number;\n /** Delay before the first retry, in ms. Doubles each attempt. Defaults to 200. */\n backoffMs?: number;\n /** Upper bound on a single backoff delay, in ms. Defaults to 2000. */\n maxBackoffMs?: number;\n}\n\nexport interface VerifNowOptions {\n /** API key created in the VerifNow dashboard. Sent as the `X-API-KEY` header. */\n apiKey: string;\n /** Override the API origin. Defaults to `https://api.verifnow.io`. */\n baseUrl?: string;\n /** Abort a single request after this many ms. Defaults to 5000. */\n timeoutMs?: number;\n /** Retry policy, or `false` to disable retries entirely. */\n retry?: RetryOptions | false;\n /** Extra headers merged into every request. */\n headers?: Record<string, string>;\n /**\n * Replacement for the global `fetch`, for tests or a custom agent.\n * Defaults to `globalThis.fetch`.\n */\n fetch?: typeof globalThis.fetch;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACsBO,IAAM,gBAAN,cAA4B,MAAM;AAAA;AAAA,EAE9B;AAAA;AAAA,EAEA;AAAA,EAET,YACE,SACA,UAAoE,CAAC,GACrE;AACA,UAAM,SAAS,EAAE,OAAO,QAAQ,MAAM,CAAC;AACvC,SAAK,OAAO,WAAW;AACvB,SAAK,SAAS,QAAQ;AACtB,SAAK,YAAY,QAAQ;AACzB,UAAM,oBAAoB,MAAM,UAAU;AAAA,EAC5C;AACF;AAGO,IAAM,oBAAN,cAAgC,cAAc;AAAC;AAO/C,IAAM,uBAAN,cAAmC,cAAc;AAAC;AAGlD,IAAM,yBAAN,cAAqC,cAAc;AAAA;AAAA,EAE/C;AAAA;AAAA,EAEA;AAAA,EAET,YACE,SACA,UAMI,CAAC,GACL;AACA,UAAM,SAAS,OAAO;AACtB,SAAK,QAAQ,QAAQ;AACrB,SAAK,oBAAoB,QAAQ;AAAA,EACnC;AACF;AAGO,IAAM,sBAAN,cAAkC,cAAc;AAAC;AAQjD,IAAM,0BAAN,cAAsC,cAAc;AAAA;AAAA,EAEhD;AAAA,EAET,YACE,SACA,UAAmD,CAAC,GACpD;AACA,UAAM,SAAS,EAAE,OAAO,QAAQ,MAAM,CAAC;AACvC,SAAK,WAAW,QAAQ,YAAY;AAAA,EACtC;AACF;AAGO,IAAM,wBAAN,cAAoC,cAAc;AAAC;;;AC1FnD,IAAM,UAAU;;;ACoBvB,IAAM,mBAAmB;AACzB,IAAM,qBAAqB;AAC3B,IAAM,gBAAwC;AAAA,EAC5C,UAAU;AAAA,EACV,WAAW;AAAA,EACX,cAAc;AAChB;AA0BO,IAAM,WAAN,MAAe;AAAA,EACX;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAET,YAAY,SAA0B;AACpC,QAAI,CAAC,SAAS,UAAU,QAAQ,OAAO,KAAK,MAAM,IAAI;AACpD,YAAM,IAAI;AAAA,QACR;AAAA,MACF;AAAA,IACF;AAEA,UAAM,YAAY,QAAQ,SAAS,WAAW;AAC9C,QAAI,OAAO,cAAc,YAAY;AACnC,YAAM,IAAI;AAAA,QACR;AAAA,MACF;AAAA,IACF;AAEA,SAAK,UAAU,QAAQ,OAAO,KAAK;AAEnC,SAAK,YAAY,QAAQ,WAAW,kBAAkB,QAAQ,QAAQ,EAAE;AACxE,SAAK,aAAa,QAAQ,aAAa;AACvC,SAAK,SACH,QAAQ,UAAU,QAAQ,OAAO,EAAE,GAAG,eAAe,GAAI,QAAQ,SAAS,CAAC,EAAG;AAChF,SAAK,WAAW,QAAQ,WAAW,CAAC;AACpC,SAAK,SAAS,UAAU,KAAK,UAAU;AAAA,EACzC;AAAA;AAAA,EAGA,cAAc,OAAe,SAAqD;AAChF,WAAO,KAAK,SAAS,SAAS,OAAO,OAAO;AAAA,EAC9C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,cAAc,OAAe,SAAqD;AAChF,WAAO,KAAK,SAAS,SAAS,OAAO,OAAO;AAAA,EAC9C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,aAAa,OAAe,SAAqD;AAC/E,WAAO,KAAK,SAAS,QAAQ,OAAO,OAAO;AAAA,EAC7C;AAAA;AAAA,EAGA,YAAY,OAAe,SAAqD;AAC9E,WAAO,KAAK,SAAS,OAAO,OAAO,OAAO;AAAA,EAC5C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,YAAY,OAAe,SAAqD;AAC9E,WAAO,KAAK,SAAS,OAAO,OAAO,OAAO;AAAA,EAC5C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,YAAY,OAAe,SAAqD;AAC9E,WAAO,KAAK,SAAS,OAAO,OAAO,OAAO;AAAA,EAC5C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,YAAY,OAAe,SAAqD;AAC9E,WAAO,KAAK,SAAS,OAAO,OAAO,OAAO;AAAA,EAC5C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,SACJ,MACA,OACA,UAA0B,CAAC,GACA;AAC3B,QAAI,OAAO,UAAU,YAAY,MAAM,KAAK,MAAM,IAAI;AAEpD,YAAM,IAAI;AAAA,QACR,4CAA4C,IAAI;AAAA,MAClD;AAAA,IACF;AAEA,UAAM,MAAM,GAAG,KAAK,QAAQ,oBAAoB,IAAI;AACpD,UAAM,OAAO,KAAK,UAAU,EAAE,MAAM,CAAC;AACrC,UAAM,cAAc,KAAK,SAAS,KAAK,OAAO,WAAW,IAAI;AAE7D,QAAI;AAEJ,aAAS,UAAU,GAAG,UAAU,aAAa,WAAW;AACtD,UAAI;AACF,eAAO,MAAM,KAAK,aAAa,KAAK,MAAM,OAAO;AAAA,MACnD,SAAS,OAAO;AACd,YAAI,EAAE,iBAAiB,eAAgB,OAAM;AAC7C,oBAAY;AAEZ,cAAM,gBAAgB,YAAY,cAAc;AAChD,YAAI,iBAAiB,CAAC,KAAK,OAAQ,OAAM;AAEzC,cAAM,QAAQ,KAAK,YAAY,OAAO,OAAO;AAC7C,YAAI,UAAU,KAAM,OAAM;AAE1B,cAAM,MAAM,KAAK;AAAA,MACnB;AAAA,IACF;AAGA,UAAM,aAAa,IAAI,cAAc,gBAAgB;AAAA,EACvD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,YAAY,OAAsB,SAAgC;AAChE,UAAM,QAAQ,KAAK;AACnB,UAAM,UAAU,KAAK,IAAI,MAAM,YAAY,KAAK,SAAS,MAAM,YAAY;AAE3E,QAAI,iBAAiB,wBAAwB;AAC3C,YAAM,UAAU,MAAM,qBAAqB,KAAK;AAChD,UAAI,SAAS,MAAM,aAAc,QAAO;AACxC,aAAO,KAAK,IAAI,QAAQ,OAAO;AAAA,IACjC;AAEA,QAAI,iBAAiB,oBAAqB,QAAO;AAEjD,QAAI,iBAAiB,wBAAyB,QAAO;AAGrD,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,aACJ,KACA,MACA,SAC2B;AAC3B,UAAM,YAAY,QAAQ,aAAa,KAAK;AAC5C,UAAM,aAAa,IAAI,gBAAgB;AACvC,UAAM,QAAQ,WAAW,MAAM,WAAW,MAAM,GAAG,SAAS;AAC5D,UAAM,kBAAkB,MAAM,WAAW,MAAM;AAC/C,YAAQ,QAAQ,iBAAiB,SAAS,iBAAiB,EAAE,MAAM,KAAK,CAAC;AAEzE,QAAI;AACJ,QAAI;AACF,iBAAW,MAAM,KAAK,OAAO,KAAK;AAAA,QAChC,QAAQ;AAAA,QACR,SAAS;AAAA,UACP,GAAG,KAAK;AAAA,UACR,gBAAgB;AAAA,UAChB,QAAQ;AAAA,UACR,aAAa,KAAK;AAAA,UAClB,kBAAkB,QAAQ,OAAO;AAAA,QACnC;AAAA,QACA;AAAA,QACA,QAAQ,WAAW;AAAA,MACrB,CAAC;AAAA,IACH,SAAS,OAAO;AAEd,UAAI,QAAQ,QAAQ,QAAS,OAAM;AAEnC,YAAM,WAAW,WAAW,OAAO;AACnC,YAAM,IAAI;AAAA,QACR,WACI,uBAAuB,GAAG,oBAAoB,SAAS,QACvD,uCAAuC,GAAG;AAAA,QAC9C,EAAE,OAAO,SAAS;AAAA,MACpB;AAAA,IACF,UAAE;AACA,mBAAa,KAAK;AAClB,cAAQ,QAAQ,oBAAoB,SAAS,eAAe;AAAA,IAC9D;AAEA,WAAO,KAAK,gBAAgB,QAAQ;AAAA,EACtC;AAAA,EAEA,MAAM,gBAAgB,UAA+C;AACnE,UAAM,YAAY,SAAS,QAAQ,IAAI,cAAc,KAAK;AAC1D,UAAM,QAAQ,WAAW,SAAS,OAAO;AAEzC,QAAI,SAAS,IAAI;AACf,UAAI;AACJ,UAAI;AACF,kBAAU,MAAM,SAAS,KAAK;AAAA,MAChC,SAAS,OAAO;AACd,cAAM,IAAI;AAAA,UACR;AAAA,UACA,EAAE,QAAQ,SAAS,QAAQ,WAAW,MAAM;AAAA,QAC9C;AAAA,MACF;AAEA,UAAI,YAAY,QAAQ,OAAO,YAAY,UAAU;AACnD,cAAM,IAAI;AAAA,UACR;AAAA,UACA,EAAE,QAAQ,SAAS,QAAQ,UAAU;AAAA,QACvC;AAAA,MACF;AAEA,aAAO,UAAU,SAAoC,KAAK;AAAA,IAC5D;AAEA,UAAM,UAAU,MAAM,iBAAiB,QAAQ;AAC/C,UAAM,UAAU,EAAE,QAAQ,SAAS,QAAQ,UAAU;AAErD,QAAI,SAAS,WAAW,OAAO,SAAS,WAAW,KAAK;AACtD,YAAM,IAAI;AAAA,QACR,kCAAkC,SAAS,MAAM,MAAM,OAAO;AAAA,QAC9D;AAAA,MACF;AAAA,IACF;AAEA,QAAI,SAAS,WAAW,KAAK;AAC3B,YAAM,IAAI,uBAAuB,gCAAgC,OAAO,IAAI;AAAA,QAC1E,GAAG;AAAA,QACH;AAAA,QACA,mBAAmB,gBAAgB,SAAS,SAAS,KAAK;AAAA,MAC5D,CAAC;AAAA,IACH;AAEA,QAAI,SAAS,UAAU,KAAK;AAC1B,YAAM,IAAI;AAAA,QACR,qBAAqB,SAAS,MAAM,KAAK,OAAO;AAAA,QAChD;AAAA,MACF;AAAA,IACF;AAEA,UAAM,IAAI;AAAA,MACR,kCAAkC,SAAS,MAAM,MAAM,OAAO;AAAA,MAC9D;AAAA,IACF;AAAA,EACF;AACF;AAEA,SAAS,MAAM,IAA2B;AACxC,SAAO,IAAI,QAAQ,CAAC,YAAY,WAAW,SAAS,EAAE,CAAC;AACzD;AAEA,SAAS,SAAS,OAA0C;AAC1D,MAAI,UAAU,KAAM,QAAO;AAC3B,QAAM,SAAS,OAAO,KAAK;AAC3B,SAAO,OAAO,SAAS,MAAM,IAAI,SAAS;AAC5C;AAEA,SAAS,WAAW,SAAyC;AAC3D,QAAM,QAAQ,SAAS,QAAQ,IAAI,mBAAmB,CAAC;AACvD,QAAM,YAAY,SAAS,QAAQ,IAAI,uBAAuB,CAAC;AAC/D,QAAM,eAAe,SAAS,QAAQ,IAAI,mBAAmB,CAAC;AAC9D,QAAM,UAAU,QAAQ,IAAI,iBAAiB,MAAM;AAEnD,MACE,UAAU,UACV,cAAc,UACd,iBAAiB,UACjB,CAAC,SACD;AACA,WAAO;AAAA,EACT;AAEA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,SAAS,iBAAiB,SAAY,SAAY,IAAI,KAAK,eAAe,GAAI;AAAA,IAC9E;AAAA,EACF;AACF;AAEA,SAAS,gBAAgB,SAAkB,OAAuC;AAChF,QAAM,aAAa,QAAQ,IAAI,aAAa;AAC5C,MAAI,eAAe,MAAM;AACvB,UAAM,UAAU,OAAO,UAAU;AACjC,QAAI,OAAO,SAAS,OAAO,EAAG,QAAO;AAGrC,UAAMA,UAAS,KAAK,MAAM,UAAU;AACpC,QAAI,CAAC,OAAO,MAAMA,OAAM,GAAG;AACzB,aAAO,KAAK,IAAI,GAAG,KAAK,MAAMA,UAAS,KAAK,IAAI,KAAK,GAAI,CAAC;AAAA,IAC5D;AAAA,EACF;AAEA,MAAI,OAAO,SAAS;AAClB,WAAO,KAAK,IAAI,GAAG,KAAK,MAAM,MAAM,QAAQ,QAAQ,IAAI,KAAK,IAAI,KAAK,GAAI,CAAC;AAAA,EAC7E;AAEA,SAAO;AACT;AAEA,eAAe,iBAAiB,UAAqC;AACnE,MAAI;AACF,UAAM,OAAO,MAAM,SAAS,KAAK;AACjC,QAAI,CAAC,KAAM,QAAO,SAAS,cAAc;AAEzC,QAAI;AACF,YAAM,SAAS,KAAK,MAAM,IAAI;AAC9B,YAAM,UAAU,OAAO,WAAW,OAAO;AACzC,UAAI,OAAO,YAAY,YAAY,YAAY,GAAI,QAAO;AAAA,IAC5D,QAAQ;AAAA,IAER;AAEA,WAAO,KAAK,MAAM,GAAG,GAAG;AAAA,EAC1B,QAAQ;AACN,WAAO,SAAS,cAAc;AAAA,EAChC;AACF;AAEA,SAAS,SAAS,OAAoC;AACpD,SAAO,OAAO,UAAU,WAAW,QAAQ;AAC7C;AAEA,SAAS,UAAU,OAAqC;AACtD,SAAO,OAAO,UAAU,YAAY,QAAQ;AAC9C;AAEA,SAAS,SAAS,OAAoC;AACpD,SAAO,OAAO,UAAU,YAAY,OAAO,SAAS,KAAK,IAAI,QAAQ;AACvE;AAEA,SAAS,WAAW,KAAwC;AAC1D,MAAI,QAAQ,QAAQ,OAAO,QAAQ,SAAU,QAAO;AACpD,QAAM,IAAI;AAEV,SAAO;AAAA,IACL,aAAa,UAAU,EAAE,YAAY;AAAA,IACrC,SAAS,UAAU,EAAE,QAAQ;AAAA,IAC7B,cAAc,UAAU,EAAE,aAAa;AAAA,IACvC,iBAAiB,SAAS,EAAE,gBAAgB;AAAA,IAC5C,YAAY,UAAU,EAAE,UAAU;AAAA,IAClC,WAAW,UAAU,EAAE,UAAU;AAAA,IACjC,cAAc,UAAU,EAAE,aAAa;AAAA,IACvC,eAAe,SAAS,EAAE,eAAe;AAAA,IACzC,YAAY,SAAS,EAAE,WAAW;AAAA,IAClC,gBAAgB,SAAS,EAAE,gBAAgB;AAAA,EAC7C;AACF;AAEA,SAAS,gBAAgB,KAAwC;AAC/D,MAAI,QAAQ,QAAQ,OAAO,QAAQ,SAAU,QAAO;AACpD,QAAM,IAAI;AAEV,SAAO;AAAA,IACL,SAAS,WAAW,EAAE,OAAO;AAAA,IAC7B,WAAW,SAAS,EAAE,UAAU;AAAA,IAChC,WAAW,SAAS,EAAE,UAAU;AAAA,IAChC,gBAAgB,SAAS,EAAE,cAAc;AAAA,IACzC,cAAc,SAAS,EAAE,aAAa;AAAA,EACxC;AACF;AAYA,SAAS,aAAa,OAAgC;AACpD,SAAO,OAAO,UAAU,YAAY,QAAQ;AAC9C;AAEA,SAAS,OAAO,OAAkC;AAChD,MAAI,OAAO,UAAU,SAAU,QAAO;AACtC,QAAM,SAAS,IAAI,KAAK,KAAK;AAC7B,SAAO,OAAO,MAAM,OAAO,QAAQ,CAAC,IAAI,SAAY;AACtD;AAEA,SAAS,cAAc,KAAsC;AAC3D,MAAI,QAAQ,QAAQ,OAAO,QAAQ,SAAU,QAAO;AACpD,QAAM,IAAI;AAEV,SAAO;AAAA,IACL,aAAa,UAAU,EAAE,YAAY;AAAA,IACrC,YAAY,aAAa,EAAE,UAAU;AAAA,IACrC,aAAa,SAAS,EAAE,YAAY;AAAA,IACpC,QAAQ,SAAS,EAAE,MAAM;AAAA,IACzB,WAAW,OAAO,EAAE,UAAU;AAAA,IAC9B,YAAY,SAAS,EAAE,WAAW;AAAA,IAClC,eAAe,SAAS,EAAE,cAAc;AAAA,IACxC,eAAe,UAAU,EAAE,cAAc;AAAA,IACzC,oBAAoB,SAAS,EAAE,mBAAmB;AAAA,EACpD;AACF;AAEA,SAAS,gBAAgB,KAAwC;AAC/D,MAAI,QAAQ,QAAQ,OAAO,QAAQ,SAAU,QAAO;AACpD,QAAM,IAAI;AAEV,SAAO;AAAA,IACL,aAAa,SAAS,EAAE,YAAY;AAAA,IACpC,aAAa,SAAS,EAAE,YAAY;AAAA,IACpC,UAAU,SAAS,EAAE,SAAS;AAAA,IAC9B,qBAAqB,SAAS,EAAE,oBAAoB;AAAA,IACpD,gBAAgB,SAAS,EAAE,eAAe;AAAA,EAC5C;AACF;AAEA,SAAS,eAAe,KAAuC;AAC7D,MAAI,QAAQ,QAAQ,OAAO,QAAQ,SAAU,QAAO;AACpD,QAAM,IAAI;AAEV,SAAO;AAAA,IACL,aAAa,SAAS,EAAE,YAAY;AAAA,IACpC,gBAAgB,UAAU,EAAE,eAAe;AAAA,IAC3C,eAAe,UAAU,EAAE,cAAc;AAAA,IACzC,QAAQ,SAAS,EAAE,MAAM;AAAA,IACzB,gBAAgB,SAAS,EAAE,eAAe;AAAA,IAC1C,WAAW,SAAS,EAAE,SAAS;AAAA,EACjC;AACF;AAEA,SAAS,cAAc,KAAsC;AAC3D,MAAI,QAAQ,QAAQ,OAAO,QAAQ,SAAU,QAAO;AACpD,QAAM,IAAI;AAEV,SAAO;AAAA,IACL,eAAe,UAAU,EAAE,cAAc;AAAA,IACzC,mBAAmB,UAAU,EAAE,kBAAkB;AAAA,IACjD,kBAAkB,UAAU,EAAE,iBAAiB;AAAA,IAC/C,WAAW,SAAS,EAAE,SAAS;AAAA,EACjC;AACF;AAEA,SAAS,cAAc,KAAsC;AAC3D,MAAI,QAAQ,QAAQ,OAAO,QAAQ,SAAU,QAAO;AACpD,QAAM,IAAI;AAEV,SAAO;AAAA,IACL,MAAM,SAAS,EAAE,IAAI;AAAA,IACrB,eAAe,UAAU,EAAE,cAAc;AAAA,IACzC,eAAe,UAAU,EAAE,cAAc;AAAA,IACzC,cAAc,SAAS,EAAE,aAAa;AAAA,IACtC,YAAY,SAAS,EAAE,WAAW;AAAA,EACpC;AACF;AAEA,SAAS,cAAc,KAAsC;AAC3D,MAAI,QAAQ,QAAQ,OAAO,QAAQ,SAAU,QAAO;AACpD,QAAM,IAAI;AAEV,SAAO,EAAE,MAAM,UAAU,EAAE,IAAI,EAAE;AACnC;AAMA,SAAS,UACP,SACA,OACkB;AAClB,SAAO;AAAA,IACL,OAAO,QAAQ,UAAU;AAAA,IACzB,SAAS,SAAS,QAAQ,OAAO;AAAA,IACjC,iBAAiB,SAAS,QAAQ,eAAe,KAAK;AAAA,IACtD,eAAe,SAAS,QAAQ,aAAa;AAAA,IAC7C,iBAAiB,SAAS,QAAQ,eAAe;AAAA,IACjD,cAAc,gBAAgB,QAAQ,YAAY;AAAA,IAClD,YAAY,cAAc,QAAQ,UAAU;AAAA,IAC5C,cAAc,gBAAgB,QAAQ,YAAY;AAAA,IAClD,aAAa,eAAe,QAAQ,WAAW;AAAA,IAC/C,YAAY,cAAc,QAAQ,UAAU;AAAA,IAC5C,YAAY,cAAc,QAAQ,UAAU;AAAA,IAC5C,YAAY,cAAc,QAAQ,UAAU;AAAA,IAC5C;AAAA,IACA,KAAK;AAAA,EACP;AACF;;;AC5hBO,IAAM,mBAA8C;AAAA,EACzD;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;","names":["asDate"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -201,6 +201,20 @@ interface NifDetails {
|
|
|
201
201
|
/** For an entity, its legal form, e.g. `Private limited company (Sociedad de responsabilidad limitada)`. */
|
|
202
202
|
entityType?: string;
|
|
203
203
|
}
|
|
204
|
+
/**
|
|
205
|
+
* US SSN diagnostics. Present on `ssn` validations.
|
|
206
|
+
*
|
|
207
|
+
* An SSN has no check digit, and since 2011 its first digits say nothing about a state, so there is
|
|
208
|
+
* little a number can reveal about itself. The one thing worth knowing is whether it is an ITIN.
|
|
209
|
+
*/
|
|
210
|
+
interface SsnDetails {
|
|
211
|
+
/**
|
|
212
|
+
* The number is an IRS ITIN, not an SSN: it starts with 9 and its fourth and fifth digits are in
|
|
213
|
+
* 50-65, 70-88, 90-92 or 94-99. `valid` is `false` for the SSN, but an ITIN is an acceptable
|
|
214
|
+
* taxpayer number where one is accepted (a W-9, for instance).
|
|
215
|
+
*/
|
|
216
|
+
itin?: boolean;
|
|
217
|
+
}
|
|
204
218
|
/** Outcome of a single validation call. */
|
|
205
219
|
interface ValidationResult {
|
|
206
220
|
/** Whether the value passed every check the applied level ran. */
|
|
@@ -225,6 +239,8 @@ interface ValidationResult {
|
|
|
225
239
|
nasDetails?: NasDetails;
|
|
226
240
|
/** Present for Spanish NIF validations. */
|
|
227
241
|
nifDetails?: NifDetails;
|
|
242
|
+
/** Present for US SSN validations. */
|
|
243
|
+
ssnDetails?: SsnDetails;
|
|
228
244
|
/** Quota state reported by the response headers. */
|
|
229
245
|
quota?: QuotaInfo;
|
|
230
246
|
/** The unmodified JSON body, for fields this SDK version does not model yet. */
|
|
@@ -322,7 +338,12 @@ declare class VerifNow {
|
|
|
322
338
|
* from series not issued to individuals. Only collect a SIN where the law requires it.
|
|
323
339
|
*/
|
|
324
340
|
validateNas(value: string, options?: RequestOptions): Promise<ValidationResult>;
|
|
325
|
-
/**
|
|
341
|
+
/**
|
|
342
|
+
* Validate a US Social Security Number against the numbers the SSA never issues.
|
|
343
|
+
*
|
|
344
|
+
* An SSN has no check digit: a typo that lands on another possible number cannot be caught, and
|
|
345
|
+
* only the SSA can confirm a number was issued. `ssnDetails.itin` flags an IRS ITIN.
|
|
346
|
+
*/
|
|
326
347
|
validateSsn(value: string, options?: RequestOptions): Promise<ValidationResult>;
|
|
327
348
|
/**
|
|
328
349
|
* Validate a Spanish NIF: a DNI, a NIE (foreign nationals), the K/L/M series, or a company NIF.
|
|
@@ -421,6 +442,6 @@ declare class VerifNowResponseError extends VerifNowError {
|
|
|
421
442
|
*
|
|
422
443
|
* Kept in sync with `package.json` by a test — bump both together.
|
|
423
444
|
*/
|
|
424
|
-
declare const VERSION = "1.
|
|
445
|
+
declare const VERSION = "1.6.0";
|
|
425
446
|
|
|
426
|
-
export { type Deliverability, type EmailDetails, type EmailSignals, type IbanDetails, type NasDetails, type NifDetails, type NifType, type PhoneDetails, type PhoneLineType, type QuotaInfo, type RequestOptions, type RetryOptions, type RiskLevel, VALIDATION_RULES, VERSION, type ValidationLevel, type ValidationResult, type ValidationRule, type VatDetails, type VatSource, VerifNow, VerifNowAuthError, VerifNowConnectionError, VerifNowError, type VerifNowOptions, VerifNowRateLimitError, VerifNowRequestError, VerifNowResponseError, VerifNowServerError };
|
|
447
|
+
export { type Deliverability, type EmailDetails, type EmailSignals, type IbanDetails, type NasDetails, type NifDetails, type NifType, type PhoneDetails, type PhoneLineType, type QuotaInfo, type RequestOptions, type RetryOptions, type RiskLevel, type SsnDetails, VALIDATION_RULES, VERSION, type ValidationLevel, type ValidationResult, type ValidationRule, type VatDetails, type VatSource, VerifNow, VerifNowAuthError, VerifNowConnectionError, VerifNowError, type VerifNowOptions, VerifNowRateLimitError, VerifNowRequestError, VerifNowResponseError, VerifNowServerError };
|
package/dist/index.d.ts
CHANGED
|
@@ -201,6 +201,20 @@ interface NifDetails {
|
|
|
201
201
|
/** For an entity, its legal form, e.g. `Private limited company (Sociedad de responsabilidad limitada)`. */
|
|
202
202
|
entityType?: string;
|
|
203
203
|
}
|
|
204
|
+
/**
|
|
205
|
+
* US SSN diagnostics. Present on `ssn` validations.
|
|
206
|
+
*
|
|
207
|
+
* An SSN has no check digit, and since 2011 its first digits say nothing about a state, so there is
|
|
208
|
+
* little a number can reveal about itself. The one thing worth knowing is whether it is an ITIN.
|
|
209
|
+
*/
|
|
210
|
+
interface SsnDetails {
|
|
211
|
+
/**
|
|
212
|
+
* The number is an IRS ITIN, not an SSN: it starts with 9 and its fourth and fifth digits are in
|
|
213
|
+
* 50-65, 70-88, 90-92 or 94-99. `valid` is `false` for the SSN, but an ITIN is an acceptable
|
|
214
|
+
* taxpayer number where one is accepted (a W-9, for instance).
|
|
215
|
+
*/
|
|
216
|
+
itin?: boolean;
|
|
217
|
+
}
|
|
204
218
|
/** Outcome of a single validation call. */
|
|
205
219
|
interface ValidationResult {
|
|
206
220
|
/** Whether the value passed every check the applied level ran. */
|
|
@@ -225,6 +239,8 @@ interface ValidationResult {
|
|
|
225
239
|
nasDetails?: NasDetails;
|
|
226
240
|
/** Present for Spanish NIF validations. */
|
|
227
241
|
nifDetails?: NifDetails;
|
|
242
|
+
/** Present for US SSN validations. */
|
|
243
|
+
ssnDetails?: SsnDetails;
|
|
228
244
|
/** Quota state reported by the response headers. */
|
|
229
245
|
quota?: QuotaInfo;
|
|
230
246
|
/** The unmodified JSON body, for fields this SDK version does not model yet. */
|
|
@@ -322,7 +338,12 @@ declare class VerifNow {
|
|
|
322
338
|
* from series not issued to individuals. Only collect a SIN where the law requires it.
|
|
323
339
|
*/
|
|
324
340
|
validateNas(value: string, options?: RequestOptions): Promise<ValidationResult>;
|
|
325
|
-
/**
|
|
341
|
+
/**
|
|
342
|
+
* Validate a US Social Security Number against the numbers the SSA never issues.
|
|
343
|
+
*
|
|
344
|
+
* An SSN has no check digit: a typo that lands on another possible number cannot be caught, and
|
|
345
|
+
* only the SSA can confirm a number was issued. `ssnDetails.itin` flags an IRS ITIN.
|
|
346
|
+
*/
|
|
326
347
|
validateSsn(value: string, options?: RequestOptions): Promise<ValidationResult>;
|
|
327
348
|
/**
|
|
328
349
|
* Validate a Spanish NIF: a DNI, a NIE (foreign nationals), the K/L/M series, or a company NIF.
|
|
@@ -421,6 +442,6 @@ declare class VerifNowResponseError extends VerifNowError {
|
|
|
421
442
|
*
|
|
422
443
|
* Kept in sync with `package.json` by a test — bump both together.
|
|
423
444
|
*/
|
|
424
|
-
declare const VERSION = "1.
|
|
445
|
+
declare const VERSION = "1.6.0";
|
|
425
446
|
|
|
426
|
-
export { type Deliverability, type EmailDetails, type EmailSignals, type IbanDetails, type NasDetails, type NifDetails, type NifType, type PhoneDetails, type PhoneLineType, type QuotaInfo, type RequestOptions, type RetryOptions, type RiskLevel, VALIDATION_RULES, VERSION, type ValidationLevel, type ValidationResult, type ValidationRule, type VatDetails, type VatSource, VerifNow, VerifNowAuthError, VerifNowConnectionError, VerifNowError, type VerifNowOptions, VerifNowRateLimitError, VerifNowRequestError, VerifNowResponseError, VerifNowServerError };
|
|
447
|
+
export { type Deliverability, type EmailDetails, type EmailSignals, type IbanDetails, type NasDetails, type NifDetails, type NifType, type PhoneDetails, type PhoneLineType, type QuotaInfo, type RequestOptions, type RetryOptions, type RiskLevel, type SsnDetails, VALIDATION_RULES, VERSION, type ValidationLevel, type ValidationResult, type ValidationRule, type VatDetails, type VatSource, VerifNow, VerifNowAuthError, VerifNowConnectionError, VerifNowError, type VerifNowOptions, VerifNowRateLimitError, VerifNowRequestError, VerifNowResponseError, VerifNowServerError };
|
package/dist/index.js
CHANGED
|
@@ -41,7 +41,7 @@ var VerifNowResponseError = class extends VerifNowError {
|
|
|
41
41
|
};
|
|
42
42
|
|
|
43
43
|
// src/version.ts
|
|
44
|
-
var VERSION = "1.
|
|
44
|
+
var VERSION = "1.6.0";
|
|
45
45
|
|
|
46
46
|
// src/client.ts
|
|
47
47
|
var DEFAULT_BASE_URL = "https://api.verifnow.io";
|
|
@@ -112,7 +112,12 @@ var VerifNow = class {
|
|
|
112
112
|
validateNas(value, options) {
|
|
113
113
|
return this.validate("nas", value, options);
|
|
114
114
|
}
|
|
115
|
-
/**
|
|
115
|
+
/**
|
|
116
|
+
* Validate a US Social Security Number against the numbers the SSA never issues.
|
|
117
|
+
*
|
|
118
|
+
* An SSN has no check digit: a typo that lands on another possible number cannot be caught, and
|
|
119
|
+
* only the SSA can confirm a number was issued. `ssnDetails.itin` flags an IRS ITIN.
|
|
120
|
+
*/
|
|
116
121
|
validateSsn(value, options) {
|
|
117
122
|
return this.validate("ssn", value, options);
|
|
118
123
|
}
|
|
@@ -410,6 +415,11 @@ function mapNifDetails(raw) {
|
|
|
410
415
|
entityType: asString(d.entity_type)
|
|
411
416
|
};
|
|
412
417
|
}
|
|
418
|
+
function mapSsnDetails(raw) {
|
|
419
|
+
if (raw === null || typeof raw !== "object") return void 0;
|
|
420
|
+
const d = raw;
|
|
421
|
+
return { itin: asBoolean(d.itin) };
|
|
422
|
+
}
|
|
413
423
|
function mapResult(payload, quota) {
|
|
414
424
|
return {
|
|
415
425
|
valid: payload.valid === true,
|
|
@@ -423,6 +433,7 @@ function mapResult(payload, quota) {
|
|
|
423
433
|
ibanDetails: mapIbanDetails(payload.ibanDetails),
|
|
424
434
|
nasDetails: mapNasDetails(payload.nasDetails),
|
|
425
435
|
nifDetails: mapNifDetails(payload.nifDetails),
|
|
436
|
+
ssnDetails: mapSsnDetails(payload.ssnDetails),
|
|
426
437
|
quota,
|
|
427
438
|
raw: payload
|
|
428
439
|
};
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/errors.ts","../src/version.ts","../src/client.ts","../src/types.ts"],"sourcesContent":["import type { QuotaInfo } from './types.js';\n\n/**\n * Base class for every error this SDK throws.\n *\n * The SDK fails loudly on purpose. A validation client that swallows a network failure and\n * reports `valid: true` turns an outage into silently accepted bad data, and the outage stays\n * invisible until someone audits the database. Catch these and decide explicitly — accepting the\n * input on failure is a reasonable choice, but it should be a choice.\n *\n * @example\n * ```ts\n * try {\n * const result = await client.validateEmail(input);\n * return result.valid;\n * } catch (error) {\n * if (error instanceof VerifNowRateLimitError) throw error; // back-pressure, do not swallow\n * logger.warn({ error }, 'VerifNow unavailable, accepting input unverified');\n * return true;\n * }\n * ```\n */\nexport class VerifNowError extends Error {\n /** HTTP status, when the failure came back from the API rather than the network. */\n readonly status?: number;\n /** Correlation id from the `X-Request-Id` response header, useful in support requests. */\n readonly requestId?: string;\n\n constructor(\n message: string,\n options: { status?: number; requestId?: string; cause?: unknown } = {},\n ) {\n super(message, { cause: options.cause });\n this.name = new.target.name;\n this.status = options.status;\n this.requestId = options.requestId;\n Error.captureStackTrace?.(this, new.target);\n }\n}\n\n/** The API key is missing, malformed, revoked, or not authorised for this endpoint (401/403). */\nexport class VerifNowAuthError extends VerifNowError {}\n\n/**\n * The request was rejected as malformed (400).\n *\n * Retrying is pointless — the payload itself needs to change.\n */\nexport class VerifNowRequestError extends VerifNowError {}\n\n/** The monthly quota or the concurrency limit was exceeded (429). */\nexport class VerifNowRateLimitError extends VerifNowError {\n /** Quota counters from the response headers, when present. */\n readonly quota?: QuotaInfo;\n /** Seconds to wait before retrying, derived from `Retry-After` or `X-RateLimit-Reset`. */\n readonly retryAfterSeconds?: number;\n\n constructor(\n message: string,\n options: {\n status?: number;\n requestId?: string;\n cause?: unknown;\n quota?: QuotaInfo;\n retryAfterSeconds?: number;\n } = {},\n ) {\n super(message, options);\n this.quota = options.quota;\n this.retryAfterSeconds = options.retryAfterSeconds;\n }\n}\n\n/** The API failed to process the request (5xx). Retried automatically before surfacing. */\nexport class VerifNowServerError extends VerifNowError {}\n\n/**\n * The API could not be reached at all: DNS failure, refused connection, TLS error, or the\n * request exceeded `timeoutMs`.\n *\n * A wrong `baseUrl` surfaces here, which is why it names the URL it tried.\n */\nexport class VerifNowConnectionError extends VerifNowError {\n /** True when the failure was the client-side timeout rather than a transport error. */\n readonly timedOut: boolean;\n\n constructor(\n message: string,\n options: { cause?: unknown; timedOut?: boolean } = {},\n ) {\n super(message, { cause: options.cause });\n this.timedOut = options.timedOut ?? false;\n }\n}\n\n/** The API returned a success status with a body this SDK could not parse. */\nexport class VerifNowResponseError extends VerifNowError {}\n","/**\n * SDK version, sent to the API as `X-VerifNow-SDK: node/<version>` so calls made through an\n * official SDK can be told apart from hand-rolled integrations.\n *\n * Kept in sync with `package.json` by a test — bump both together.\n */\nexport const VERSION = '1.5.0';\n","import {\n VerifNowAuthError,\n VerifNowConnectionError,\n VerifNowError,\n VerifNowRateLimitError,\n VerifNowRequestError,\n VerifNowResponseError,\n VerifNowServerError,\n} from './errors.js';\nimport type {\n EmailDetails,\n EmailSignals,\n IbanDetails,\n NasDetails,\n NifDetails,\n PhoneDetails,\n QuotaInfo,\n RetryOptions,\n ValidationResult,\n ValidationRule,\n VatDetails,\n VerifNowOptions,\n} from './types.js';\nimport { VERSION } from './version.js';\n\nconst DEFAULT_BASE_URL = 'https://api.verifnow.io';\nconst DEFAULT_TIMEOUT_MS = 5_000;\nconst DEFAULT_RETRY: Required<RetryOptions> = {\n attempts: 2,\n backoffMs: 200,\n maxBackoffMs: 2_000,\n};\n\n/** Per-call overrides. */\nexport interface RequestOptions {\n /** Override the client timeout for this call. */\n timeoutMs?: number;\n /** Cancel the call from your own controller. Combined with the timeout. */\n signal?: AbortSignal;\n}\n\n/**\n * Client for the VerifNow validation API.\n *\n * @example\n * ```ts\n * import { VerifNow } from '@verifnow/sdk';\n *\n * const client = new VerifNow({ apiKey: process.env.VERIFNOW_API_KEY! });\n * const result = await client.validateEmail('user@example.com');\n *\n * if (!result.valid) console.log(result.message);\n * if (result.emailDetails?.signals?.typoDetected) {\n * console.log('Did you mean', result.emailDetails.signals.suggestedDomain);\n * }\n * ```\n */\nexport class VerifNow {\n readonly #apiKey: string;\n readonly #baseUrl: string;\n readonly #timeoutMs: number;\n readonly #retry: Required<RetryOptions> | null;\n readonly #headers: Record<string, string>;\n readonly #fetch: typeof globalThis.fetch;\n\n constructor(options: VerifNowOptions) {\n if (!options?.apiKey || options.apiKey.trim() === '') {\n throw new VerifNowError(\n 'A VerifNow API key is required. Create one in the dashboard and pass it as `apiKey`.',\n );\n }\n\n const fetchImpl = options.fetch ?? globalThis.fetch;\n if (typeof fetchImpl !== 'function') {\n throw new VerifNowError(\n 'No global fetch available. Use Node 18 or later, or pass a `fetch` implementation.',\n );\n }\n\n this.#apiKey = options.apiKey.trim();\n // Trailing slashes would produce `//api/v1/...`, which some proxies reject.\n this.#baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\\/+$/, '');\n this.#timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n this.#retry =\n options.retry === false ? null : { ...DEFAULT_RETRY, ...(options.retry ?? {}) };\n this.#headers = options.headers ?? {};\n this.#fetch = fetchImpl.bind(globalThis);\n }\n\n /** Validate an email address: syntax, DNS/MX, typo, disposable, role-based, quality score. */\n validateEmail(value: string, options?: RequestOptions): Promise<ValidationResult> {\n return this.validate('email', value, options);\n }\n\n /**\n * Validate a phone number against its country's numbering plan.\n *\n * The number must include its country code (`+33…` or `0033…`). Valid numbers come back in\n * E.164 as `normalizedValue`, with country and line type in `phoneDetails`.\n */\n validatePhone(value: string, options?: RequestOptions): Promise<ValidationResult> {\n return this.validate('phone', value, options);\n }\n\n /**\n * Validate an IBAN against the SWIFT registry entry for its country, then its check digits.\n *\n * `ibanDetails` reports the two separately: check digits catch a typo, the registry catches an\n * account number that could never exist in that country.\n */\n validateIban(value: string, options?: RequestOptions): Promise<ValidationResult> {\n return this.validate('iban', value, options);\n }\n\n /** Validate a VAT number. */\n validateVat(value: string, options?: RequestOptions): Promise<ValidationResult> {\n return this.validate('vat', value, options);\n }\n\n /**\n * Validate a Canadian Social Insurance Number: format and Luhn check digit.\n *\n * `nasDetails` flags a temporary resident's number (it expires with their permit) and numbers\n * from series not issued to individuals. Only collect a SIN where the law requires it.\n */\n validateNas(value: string, options?: RequestOptions): Promise<ValidationResult> {\n return this.validate('nas', value, options);\n }\n\n /** Validate a US Social Security Number. */\n validateSsn(value: string, options?: RequestOptions): Promise<ValidationResult> {\n return this.validate('ssn', value, options);\n }\n\n /**\n * Validate a Spanish NIF: a DNI, a NIE (foreign nationals), the K/L/M series, or a company NIF.\n *\n * `nifDetails` says which, whether it belongs to a person, and for a company its legal form.\n * Spanish only — a Portuguese NIF is a different scheme and is not accepted here.\n */\n validateNif(value: string, options?: RequestOptions): Promise<ValidationResult> {\n return this.validate('nif', value, options);\n }\n\n /**\n * Validate a value against any rule.\n *\n * The typed helpers above call this. Use it directly when the rule is chosen at runtime.\n */\n async validate(\n rule: ValidationRule,\n value: string,\n options: RequestOptions = {},\n ): Promise<ValidationResult> {\n if (typeof value !== 'string' || value.trim() === '') {\n // Caught here rather than server-side: an empty value consumes quota and can only fail.\n throw new VerifNowRequestError(\n `Cannot validate an empty value for rule \"${rule}\".`,\n );\n }\n\n const url = `${this.#baseUrl}/api/v1/validate/${rule}`;\n const body = JSON.stringify({ value });\n const maxAttempts = this.#retry ? this.#retry.attempts + 1 : 1;\n\n let lastError: VerifNowError | undefined;\n\n for (let attempt = 0; attempt < maxAttempts; attempt++) {\n try {\n return await this.#requestOnce(url, body, options);\n } catch (error) {\n if (!(error instanceof VerifNowError)) throw error;\n lastError = error;\n\n const isLastAttempt = attempt === maxAttempts - 1;\n if (isLastAttempt || !this.#retry) throw error;\n\n const delay = this.#retryDelay(error, attempt);\n if (delay === null) throw error;\n\n await sleep(delay);\n }\n }\n\n /* c8 ignore next -- the loop either returns or throws */\n throw lastError ?? new VerifNowError('Request failed');\n }\n\n /**\n * How long to wait before retrying, or `null` when the error should surface immediately.\n *\n * A 429 is retried only when the reset is close: the concurrency limit clears in\n * milliseconds, but a spent monthly quota does not, and sleeping on it helps nobody.\n */\n #retryDelay(error: VerifNowError, attempt: number): number | null {\n const retry = this.#retry!;\n const backoff = Math.min(retry.backoffMs * 2 ** attempt, retry.maxBackoffMs);\n\n if (error instanceof VerifNowRateLimitError) {\n const waitMs = (error.retryAfterSeconds ?? 0) * 1000;\n if (waitMs > retry.maxBackoffMs) return null;\n return Math.max(waitMs, backoff);\n }\n\n if (error instanceof VerifNowServerError) return backoff;\n // A timeout is retried: the deadline is ours, and the next attempt gets a fresh one.\n if (error instanceof VerifNowConnectionError) return backoff;\n\n // 400, 401 and unparseable bodies will fail identically on a second attempt.\n return null;\n }\n\n async #requestOnce(\n url: string,\n body: string,\n options: RequestOptions,\n ): Promise<ValidationResult> {\n const timeoutMs = options.timeoutMs ?? this.#timeoutMs;\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), timeoutMs);\n const abortFromCaller = () => controller.abort();\n options.signal?.addEventListener('abort', abortFromCaller, { once: true });\n\n let response: Response;\n try {\n response = await this.#fetch(url, {\n method: 'POST',\n headers: {\n ...this.#headers,\n 'Content-Type': 'application/json',\n Accept: 'application/json',\n 'X-API-KEY': this.#apiKey,\n 'X-VerifNow-SDK': `node/${VERSION}`,\n },\n body,\n signal: controller.signal,\n });\n } catch (cause) {\n // The caller's own cancellation is theirs to handle, not a transport failure.\n if (options.signal?.aborted) throw cause;\n\n const timedOut = controller.signal.aborted;\n throw new VerifNowConnectionError(\n timedOut\n ? `VerifNow request to ${url} timed out after ${timeoutMs}ms.`\n : `Could not reach the VerifNow API at ${url}. Check \\`baseUrl\\` and network access.`,\n { cause, timedOut },\n );\n } finally {\n clearTimeout(timer);\n options.signal?.removeEventListener('abort', abortFromCaller);\n }\n\n return this.#handleResponse(response);\n }\n\n async #handleResponse(response: Response): Promise<ValidationResult> {\n const requestId = response.headers.get('X-Request-Id') ?? undefined;\n const quota = parseQuota(response.headers);\n\n if (response.ok) {\n let payload: unknown;\n try {\n payload = await response.json();\n } catch (cause) {\n throw new VerifNowResponseError(\n 'VerifNow returned a success status with a body that is not valid JSON.',\n { status: response.status, requestId, cause },\n );\n }\n\n if (payload === null || typeof payload !== 'object') {\n throw new VerifNowResponseError(\n 'VerifNow returned an unexpected response shape.',\n { status: response.status, requestId },\n );\n }\n\n return mapResult(payload as Record<string, unknown>, quota);\n }\n\n const message = await readErrorMessage(response);\n const context = { status: response.status, requestId };\n\n if (response.status === 401 || response.status === 403) {\n throw new VerifNowAuthError(\n `VerifNow rejected the API key (${response.status}): ${message}`,\n context,\n );\n }\n\n if (response.status === 429) {\n throw new VerifNowRateLimitError(`VerifNow rate limit reached: ${message}`, {\n ...context,\n quota,\n retryAfterSeconds: parseRetryAfter(response.headers, quota),\n });\n }\n\n if (response.status >= 500) {\n throw new VerifNowServerError(\n `VerifNow returned ${response.status}: ${message}`,\n context,\n );\n }\n\n throw new VerifNowRequestError(\n `VerifNow rejected the request (${response.status}): ${message}`,\n context,\n );\n }\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\nfunction toNumber(value: string | null): number | undefined {\n if (value === null) return undefined;\n const parsed = Number(value);\n return Number.isFinite(parsed) ? parsed : undefined;\n}\n\nfunction parseQuota(headers: Headers): QuotaInfo | undefined {\n const limit = toNumber(headers.get('X-RateLimit-Limit'));\n const remaining = toNumber(headers.get('X-RateLimit-Remaining'));\n const resetSeconds = toNumber(headers.get('X-RateLimit-Reset'));\n const overage = headers.get('X-Quota-Overage') === 'true';\n\n if (\n limit === undefined &&\n remaining === undefined &&\n resetSeconds === undefined &&\n !overage\n ) {\n return undefined;\n }\n\n return {\n limit,\n remaining,\n resetAt: resetSeconds === undefined ? undefined : new Date(resetSeconds * 1000),\n overage,\n };\n}\n\nfunction parseRetryAfter(headers: Headers, quota?: QuotaInfo): number | undefined {\n const retryAfter = headers.get('Retry-After');\n if (retryAfter !== null) {\n const seconds = Number(retryAfter);\n if (Number.isFinite(seconds)) return seconds;\n\n // RFC 7231 also allows an HTTP-date.\n const asDate = Date.parse(retryAfter);\n if (!Number.isNaN(asDate)) {\n return Math.max(0, Math.ceil((asDate - Date.now()) / 1000));\n }\n }\n\n if (quota?.resetAt) {\n return Math.max(0, Math.ceil((quota.resetAt.getTime() - Date.now()) / 1000));\n }\n\n return undefined;\n}\n\nasync function readErrorMessage(response: Response): Promise<string> {\n try {\n const text = await response.text();\n if (!text) return response.statusText || 'no details';\n\n try {\n const parsed = JSON.parse(text) as Record<string, unknown>;\n const message = parsed.message ?? parsed.error;\n if (typeof message === 'string' && message !== '') return message;\n } catch {\n // Not JSON — a proxy or the servlet container's default error page.\n }\n\n return text.slice(0, 500);\n } catch {\n return response.statusText || 'no details';\n }\n}\n\nfunction asString(value: unknown): string | undefined {\n return typeof value === 'string' ? value : undefined;\n}\n\nfunction asBoolean(value: unknown): boolean | undefined {\n return typeof value === 'boolean' ? value : undefined;\n}\n\nfunction asNumber(value: unknown): number | undefined {\n return typeof value === 'number' && Number.isFinite(value) ? value : undefined;\n}\n\nfunction mapSignals(raw: unknown): EmailSignals | undefined {\n if (raw === null || typeof raw !== 'object') return undefined;\n const s = raw as Record<string, unknown>;\n\n return {\n syntaxValid: asBoolean(s.syntax_valid),\n mxValid: asBoolean(s.mx_valid),\n typoDetected: asBoolean(s.typo_detected),\n suggestedDomain: asString(s.suggested_domain),\n disposable: asBoolean(s.disposable),\n roleBased: asBoolean(s.role_based),\n freeProvider: asBoolean(s.free_provider),\n domainAgeDays: asNumber(s.domain_age_days),\n mxProvider: asString(s.mx_provider),\n mxQualityScore: asNumber(s.mx_quality_score),\n };\n}\n\nfunction mapEmailDetails(raw: unknown): EmailDetails | undefined {\n if (raw === null || typeof raw !== 'object') return undefined;\n const d = raw as Record<string, unknown>;\n\n return {\n signals: mapSignals(d.signals),\n riskScore: asNumber(d.risk_score),\n riskLevel: asString(d.risk_level) as EmailDetails['riskLevel'],\n deliverability: asString(d.deliverability) as EmailDetails['deliverability'],\n appliedLevel: asString(d.applied_level) as EmailDetails['appliedLevel'],\n };\n}\n\n/**\n * Reads `registered`, preserving the difference between `false` and `null`.\n *\n * `asBoolean` cannot be used here: it maps `null` to `undefined`, which would erase the one\n * distinction the whole VAT design exists to carry. `false` means the registry answered and the\n * number is not there; `null` means nobody could ask. A caller that cannot tell them apart will\n * reject real businesses whenever VIES is down.\n *\n * A missing key is read as `null` for the same reason — unknown, not absent.\n */\nfunction asRegistered(value: unknown): boolean | null {\n return typeof value === 'boolean' ? value : null;\n}\n\nfunction asDate(value: unknown): Date | undefined {\n if (typeof value !== 'string') return undefined;\n const parsed = new Date(value);\n return Number.isNaN(parsed.getTime()) ? undefined : parsed;\n}\n\nfunction mapVatDetails(raw: unknown): VatDetails | undefined {\n if (raw === null || typeof raw !== 'object') return undefined;\n const d = raw as Record<string, unknown>;\n\n return {\n formatValid: asBoolean(d.format_valid),\n registered: asRegistered(d.registered),\n countryCode: asString(d.country_code),\n source: asString(d.source) as VatDetails['source'],\n checkedAt: asDate(d.checked_at),\n traderName: asString(d.trader_name),\n traderAddress: asString(d.trader_address),\n viesAvailable: asBoolean(d.vies_available),\n consultationNumber: asString(d.consultation_number),\n };\n}\n\nfunction mapPhoneDetails(raw: unknown): PhoneDetails | undefined {\n if (raw === null || typeof raw !== 'object') return undefined;\n const d = raw as Record<string, unknown>;\n\n return {\n countryCode: asString(d.country_code),\n callingCode: asNumber(d.calling_code),\n lineType: asString(d.line_type) as PhoneDetails['lineType'],\n internationalFormat: asString(d.international_format),\n nationalFormat: asString(d.national_format),\n };\n}\n\nfunction mapIbanDetails(raw: unknown): IbanDetails | undefined {\n if (raw === null || typeof raw !== 'object') return undefined;\n const d = raw as Record<string, unknown>;\n\n return {\n countryCode: asString(d.country_code),\n structureValid: asBoolean(d.structure_valid),\n checksumValid: asBoolean(d.checksum_valid),\n length: asNumber(d.length),\n expectedLength: asNumber(d.expected_length),\n formatted: asString(d.formatted),\n };\n}\n\nfunction mapNasDetails(raw: unknown): NasDetails | undefined {\n if (raw === null || typeof raw !== 'object') return undefined;\n const d = raw as Record<string, unknown>;\n\n return {\n checksumValid: asBoolean(d.checksum_valid),\n temporaryResident: asBoolean(d.temporary_resident),\n individualSeries: asBoolean(d.individual_series),\n formatted: asString(d.formatted),\n };\n}\n\nfunction mapNifDetails(raw: unknown): NifDetails | undefined {\n if (raw === null || typeof raw !== 'object') return undefined;\n const d = raw as Record<string, unknown>;\n\n return {\n type: asString(d.type) as NifDetails['type'],\n naturalPerson: asBoolean(d.natural_person),\n checksumValid: asBoolean(d.checksum_valid),\n entityLetter: asString(d.entity_letter),\n entityType: asString(d.entity_type),\n };\n}\n\n/**\n * Maps the API's snake_case diagnostics onto camelCase, so a TypeScript caller is not switching\n * naming conventions mid-expression. The untouched body stays available on `raw`.\n */\nfunction mapResult(\n payload: Record<string, unknown>,\n quota: QuotaInfo | undefined,\n): ValidationResult {\n return {\n valid: payload.valid === true,\n message: asString(payload.message),\n normalizedValue: asString(payload.normalizedValue) ?? null,\n originalValue: asString(payload.originalValue),\n validationLevel: asString(payload.validationLevel) as ValidationResult['validationLevel'],\n emailDetails: mapEmailDetails(payload.emailDetails),\n vatDetails: mapVatDetails(payload.vatDetails),\n phoneDetails: mapPhoneDetails(payload.phoneDetails),\n ibanDetails: mapIbanDetails(payload.ibanDetails),\n nasDetails: mapNasDetails(payload.nasDetails),\n nifDetails: mapNifDetails(payload.nifDetails),\n quota,\n raw: payload,\n };\n}\n","/**\n * Validation rules exposed by the VerifNow API.\n *\n * Each maps to `POST /api/v1/validate/{rule}`.\n */\nexport type ValidationRule =\n | 'email'\n | 'phone'\n | 'iban'\n | 'vat'\n | 'nas'\n | 'ssn'\n | 'nif';\n\nexport const VALIDATION_RULES: readonly ValidationRule[] = [\n 'email',\n 'phone',\n 'iban',\n 'vat',\n 'nas',\n 'ssn',\n 'nif',\n] as const;\n\n/**\n * Depth of checks applied to a request, decided by the plan attached to the API key.\n *\n * `STANDARD` runs on the FREE and STARTER plans, `ADVANCED` on GROWTH, `PREMIUM` on PRO.\n * Branch on this rather than on the plan name: it is the only value that tells you which\n * signals are actually present in the response.\n */\nexport type ValidationLevel = 'BASIC' | 'STANDARD' | 'ADVANCED' | 'PREMIUM';\n\n/** Categorical risk assessment. Returned from `ADVANCED` depth upward. */\nexport type RiskLevel = 'LOW' | 'MEDIUM' | 'HIGH';\n\nexport type Deliverability =\n | 'DELIVERABLE'\n | 'RISKY'\n | 'UNDELIVERABLE'\n | 'UNKNOWN';\n\n/**\n * Per-signal breakdown behind an email verdict.\n *\n * Fields are `undefined` when the applied level does not compute them — the last four require\n * `ADVANCED` depth or higher. Check `ValidationResult.appliedLevel` before relying on one.\n */\nexport interface EmailSignals {\n /** The address matches the syntax pattern for the applied level. */\n syntaxValid?: boolean;\n /** The domain resolves and publishes MX (or fallback A) records. */\n mxValid?: boolean;\n /** A likely typo was found in the domain, e.g. `gmail.con`. */\n typoDetected?: boolean;\n /** The correction proposed when `typoDetected` is true. */\n suggestedDomain?: string;\n /** The domain belongs to a throwaway mailbox provider. */\n disposable?: boolean;\n /** The local part is a shared mailbox: `info@`, `admin@`, `noreply@`. */\n roleBased?: boolean;\n /** The domain is a consumer mailbox provider. Requires ADVANCED. */\n freeProvider?: boolean;\n /** Estimated age of the domain in days. Requires ADVANCED. */\n domainAgeDays?: number;\n /** Identified mail provider, e.g. `google`. Requires ADVANCED. */\n mxProvider?: string;\n /** Mail server quality between 0 and 1. Requires ADVANCED. */\n mxQualityScore?: number;\n}\n\n/** Email-specific diagnostics. Absent when the applied level is `BASIC`. */\nexport interface EmailDetails {\n signals?: EmailSignals;\n /** Aggregated risk on a 0–100 scale, where 0 is the lowest risk. */\n riskScore?: number;\n /** Categorical risk. Requires ADVANCED depth or higher. */\n riskLevel?: RiskLevel;\n deliverability?: Deliverability;\n /** The depth actually applied, echoed back by the API. */\n appliedLevel?: ValidationLevel;\n}\n\n/**\n * Where a VAT registration verdict came from.\n *\n * VIES publishes no SLA and drops member states several times a month, so a VAT answer is not\n * always a live one. Branch on this rather than on `ValidationResult.valid` whenever the\n * difference matters for your own compliance.\n */\nexport type VatSource =\n /** Confirmed against VIES during this request. */\n | 'LIVE'\n /** Served from a VIES answer less than 24 hours old. */\n | 'CACHE'\n /** VIES was unreachable, so an older cached answer was used. */\n | 'STALE'\n /** VIES was unreachable and nothing was cached. Registration is unknown. */\n | 'UNVERIFIED'\n /** The country is outside VIES, so no registry lookup is possible. */\n | 'NOT_APPLICABLE';\n\n/** VAT-specific diagnostics. Present on `vat` validations. */\nexport interface VatDetails {\n /** The number matches its member state's structure. Decided locally, never depends on VIES. */\n formatValid?: boolean;\n /**\n * Present in the member state's registry.\n *\n * **`null` means unknown, never \"not registered.\"** It is returned when VIES could not be\n * consulted. Treating `null` as `false` rejects legitimate customers during someone else's\n * outage — the single most expensive mistake available in VAT validation.\n */\n registered: boolean | null;\n /** Member state the number belongs to, e.g. `IE`. Greece is `EL`, Northern Ireland `XI`. */\n countryCode?: string;\n source?: VatSource;\n /** When the registration was last confirmed against VIES. */\n checkedAt?: Date;\n /** Registered trading name, when the member state discloses it. Germany does not. */\n traderName?: string;\n /** Registered address, when the member state discloses it. */\n traderAddress?: string;\n /** Whether VIES could answer for this country during the request. */\n viesAvailable?: boolean;\n /**\n * The consultation number VIES issued for this lookup — the receipt a tax authority accepts as\n * evidence that you checked. Present only when your account has its own VAT number configured,\n * because VIES issues one only to an identified requester.\n */\n consultationNumber?: string;\n}\n\n/**\n * Kind of line, according to the country's numbering plan.\n *\n * A `PREMIUM_RATE` or `VOIP` number is still `valid` — it exists. This is how you decide to\n * exclude one, rather than the API deciding for you.\n */\nexport type PhoneLineType =\n | 'MOBILE'\n | 'FIXED_LINE'\n /** The plan does not distinguish the two — the case for the US and Canada. */\n | 'FIXED_LINE_OR_MOBILE'\n | 'TOLL_FREE'\n | 'PREMIUM_RATE'\n | 'SHARED_COST'\n | 'VOIP'\n | 'PERSONAL_NUMBER'\n | 'PAGER'\n | 'UAN'\n | 'VOICEMAIL'\n | 'UNKNOWN';\n\n/**\n * Phone-specific diagnostics. Present whenever the input parsed as an international number —\n * including when it is invalid for its country, so you can tell the user which country it was\n * read as.\n */\nexport interface PhoneDetails {\n /** ISO 3166-1 alpha-2 country, e.g. `FR`. Absent when the calling code is shared by several. */\n countryCode?: string;\n /** International calling code without the plus sign, e.g. `33`. */\n callingCode?: number;\n /** Absent when the number is invalid. */\n lineType?: PhoneLineType;\n /** e.g. `+33 6 12 34 56 78`. Absent when the number is invalid. */\n internationalFormat?: string;\n /** e.g. `06 12 34 56 78`. Absent when the number is invalid. */\n nationalFormat?: string;\n}\n\n/**\n * IBAN-specific diagnostics. Present on `iban` validations.\n *\n * Structure and checksum are reported separately because they fail for different reasons:\n * `structureValid` answers \"could this be an account number in that country\" (the SWIFT\n * registry's length and layout), `checksumValid` answers \"was it typed correctly\" (mod-97).\n * There is no bank name or BIC — that needs a registry the API does not hold.\n */\nexport interface IbanDetails {\n /** The IBAN's country, from its first two characters. */\n countryCode?: string;\n /** Length and character layout match the registry entry for that country. */\n structureValid?: boolean;\n /** The mod-97 check digits are correct. */\n checksumValid?: boolean;\n /** Length of the value as submitted, spaces removed. */\n length?: number;\n /** Length the registry requires for that country; absent for an unknown country. */\n expectedLength?: number;\n /** Print format, in groups of four. Present only for a valid IBAN. */\n formatted?: string;\n}\n\n/**\n * Canadian Social Insurance Number diagnostics. Present on `nas` validations.\n *\n * There is no province and no expiry date: the first digit no longer reliably identifies a\n * province, and a temporary resident's SIN expires with their permit, which only the document\n * shows.\n */\nexport interface NasDetails {\n /** The Luhn check digit is correct. */\n checksumValid?: boolean;\n /**\n * A 9-series number, issued to temporary residents. It expires with the holder's permit, and the\n * number itself does not say when — check the document.\n */\n temporaryResident?: boolean;\n /**\n * The first digit belongs to a series issued to individuals. `false` for numbers starting with\n * 0 or 8 — including 046 454 286, the government's sample number, which is why it is safe to\n * use in tests.\n */\n individualSeries?: boolean;\n /** Printed form, e.g. `046 454 286`. */\n formatted?: string;\n}\n\n/** The kinds of Spanish tax identification number. */\nexport type NifType =\n /** Spanish national with a DNI: 8 digits and a letter. */\n | 'DNI'\n /** Foreign national: X, Y or Z, 7 digits and a letter. */\n | 'NIE'\n /** Spanish national under 14 without a DNI. */\n | 'NIF_K'\n /** Spanish national resident abroad, staying under six months. */\n | 'NIF_L'\n /** Foreign national without a NIE. */\n | 'NIF_M'\n /** A company or other entity: a letter for the legal form, 7 digits, a control character. */\n | 'ENTITY';\n\n/** Spanish NIF diagnostics. Present on `nif` validations. */\nexport interface NifDetails {\n type?: NifType;\n /** The number belongs to a person rather than a company or other entity. */\n naturalPerson?: boolean;\n /** The control character is correct. */\n checksumValid?: boolean;\n /** For an entity, the letter that encodes its legal form, e.g. `B`. */\n entityLetter?: string;\n /** For an entity, its legal form, e.g. `Private limited company (Sociedad de responsabilidad limitada)`. */\n entityType?: string;\n}\n\n/** Outcome of a single validation call. */\nexport interface ValidationResult {\n /** Whether the value passed every check the applied level ran. */\n valid: boolean;\n /** Human-readable explanation of the verdict. */\n message?: string;\n /** Canonical form of the input — `null` when the value is invalid. */\n normalizedValue: string | null;\n /** The value exactly as submitted. */\n originalValue?: string;\n /** Depth applied to this request. */\n validationLevel?: ValidationLevel;\n /** Present for email validations from `STANDARD` depth upward. */\n emailDetails?: EmailDetails;\n /** Present for VAT validations. */\n vatDetails?: VatDetails;\n /** Present for phone validations. The E.164 form is `normalizedValue`. */\n phoneDetails?: PhoneDetails;\n /** Present for IBAN validations. */\n ibanDetails?: IbanDetails;\n /** Present for Canadian SIN (`nas`) validations. */\n nasDetails?: NasDetails;\n /** Present for Spanish NIF validations. */\n nifDetails?: NifDetails;\n /** Quota state reported by the response headers. */\n quota?: QuotaInfo;\n /** The unmodified JSON body, for fields this SDK version does not model yet. */\n raw: Record<string, unknown>;\n}\n\n/** Quota counters read from the `X-RateLimit-*` response headers. */\nexport interface QuotaInfo {\n /** Validations included in the current billing period. */\n limit?: number;\n /** Validations left before overage or blocking. */\n remaining?: number;\n /** When the current period resets. */\n resetAt?: Date;\n /** True once you are past the included quota and into per-unit billing. */\n overage?: boolean;\n}\n\nexport interface RetryOptions {\n /**\n * Retry attempts after the first failure. Defaults to 2, so up to 3 requests in total.\n * Only connection failures, 429 and 5xx are retried — never a 400 or 401, which will not\n * succeed on a second try.\n */\n attempts?: number;\n /** Delay before the first retry, in ms. Doubles each attempt. Defaults to 200. */\n backoffMs?: number;\n /** Upper bound on a single backoff delay, in ms. Defaults to 2000. */\n maxBackoffMs?: number;\n}\n\nexport interface VerifNowOptions {\n /** API key created in the VerifNow dashboard. Sent as the `X-API-KEY` header. */\n apiKey: string;\n /** Override the API origin. Defaults to `https://api.verifnow.io`. */\n baseUrl?: string;\n /** Abort a single request after this many ms. Defaults to 5000. */\n timeoutMs?: number;\n /** Retry policy, or `false` to disable retries entirely. */\n retry?: RetryOptions | false;\n /** Extra headers merged into every request. */\n headers?: Record<string, string>;\n /**\n * Replacement for the global `fetch`, for tests or a custom agent.\n * Defaults to `globalThis.fetch`.\n */\n fetch?: typeof globalThis.fetch;\n}\n"],"mappings":"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","names":["asDate"]}
|
|
1
|
+
{"version":3,"sources":["../src/errors.ts","../src/version.ts","../src/client.ts","../src/types.ts"],"sourcesContent":["import type { QuotaInfo } from './types.js';\n\n/**\n * Base class for every error this SDK throws.\n *\n * The SDK fails loudly on purpose. A validation client that swallows a network failure and\n * reports `valid: true` turns an outage into silently accepted bad data, and the outage stays\n * invisible until someone audits the database. Catch these and decide explicitly — accepting the\n * input on failure is a reasonable choice, but it should be a choice.\n *\n * @example\n * ```ts\n * try {\n * const result = await client.validateEmail(input);\n * return result.valid;\n * } catch (error) {\n * if (error instanceof VerifNowRateLimitError) throw error; // back-pressure, do not swallow\n * logger.warn({ error }, 'VerifNow unavailable, accepting input unverified');\n * return true;\n * }\n * ```\n */\nexport class VerifNowError extends Error {\n /** HTTP status, when the failure came back from the API rather than the network. */\n readonly status?: number;\n /** Correlation id from the `X-Request-Id` response header, useful in support requests. */\n readonly requestId?: string;\n\n constructor(\n message: string,\n options: { status?: number; requestId?: string; cause?: unknown } = {},\n ) {\n super(message, { cause: options.cause });\n this.name = new.target.name;\n this.status = options.status;\n this.requestId = options.requestId;\n Error.captureStackTrace?.(this, new.target);\n }\n}\n\n/** The API key is missing, malformed, revoked, or not authorised for this endpoint (401/403). */\nexport class VerifNowAuthError extends VerifNowError {}\n\n/**\n * The request was rejected as malformed (400).\n *\n * Retrying is pointless — the payload itself needs to change.\n */\nexport class VerifNowRequestError extends VerifNowError {}\n\n/** The monthly quota or the concurrency limit was exceeded (429). */\nexport class VerifNowRateLimitError extends VerifNowError {\n /** Quota counters from the response headers, when present. */\n readonly quota?: QuotaInfo;\n /** Seconds to wait before retrying, derived from `Retry-After` or `X-RateLimit-Reset`. */\n readonly retryAfterSeconds?: number;\n\n constructor(\n message: string,\n options: {\n status?: number;\n requestId?: string;\n cause?: unknown;\n quota?: QuotaInfo;\n retryAfterSeconds?: number;\n } = {},\n ) {\n super(message, options);\n this.quota = options.quota;\n this.retryAfterSeconds = options.retryAfterSeconds;\n }\n}\n\n/** The API failed to process the request (5xx). Retried automatically before surfacing. */\nexport class VerifNowServerError extends VerifNowError {}\n\n/**\n * The API could not be reached at all: DNS failure, refused connection, TLS error, or the\n * request exceeded `timeoutMs`.\n *\n * A wrong `baseUrl` surfaces here, which is why it names the URL it tried.\n */\nexport class VerifNowConnectionError extends VerifNowError {\n /** True when the failure was the client-side timeout rather than a transport error. */\n readonly timedOut: boolean;\n\n constructor(\n message: string,\n options: { cause?: unknown; timedOut?: boolean } = {},\n ) {\n super(message, { cause: options.cause });\n this.timedOut = options.timedOut ?? false;\n }\n}\n\n/** The API returned a success status with a body this SDK could not parse. */\nexport class VerifNowResponseError extends VerifNowError {}\n","/**\n * SDK version, sent to the API as `X-VerifNow-SDK: node/<version>` so calls made through an\n * official SDK can be told apart from hand-rolled integrations.\n *\n * Kept in sync with `package.json` by a test — bump both together.\n */\nexport const VERSION = '1.6.0';\n","import {\n VerifNowAuthError,\n VerifNowConnectionError,\n VerifNowError,\n VerifNowRateLimitError,\n VerifNowRequestError,\n VerifNowResponseError,\n VerifNowServerError,\n} from './errors.js';\nimport type {\n EmailDetails,\n EmailSignals,\n IbanDetails,\n NasDetails,\n NifDetails,\n SsnDetails,\n PhoneDetails,\n QuotaInfo,\n RetryOptions,\n ValidationResult,\n ValidationRule,\n VatDetails,\n VerifNowOptions,\n} from './types.js';\nimport { VERSION } from './version.js';\n\nconst DEFAULT_BASE_URL = 'https://api.verifnow.io';\nconst DEFAULT_TIMEOUT_MS = 5_000;\nconst DEFAULT_RETRY: Required<RetryOptions> = {\n attempts: 2,\n backoffMs: 200,\n maxBackoffMs: 2_000,\n};\n\n/** Per-call overrides. */\nexport interface RequestOptions {\n /** Override the client timeout for this call. */\n timeoutMs?: number;\n /** Cancel the call from your own controller. Combined with the timeout. */\n signal?: AbortSignal;\n}\n\n/**\n * Client for the VerifNow validation API.\n *\n * @example\n * ```ts\n * import { VerifNow } from '@verifnow/sdk';\n *\n * const client = new VerifNow({ apiKey: process.env.VERIFNOW_API_KEY! });\n * const result = await client.validateEmail('user@example.com');\n *\n * if (!result.valid) console.log(result.message);\n * if (result.emailDetails?.signals?.typoDetected) {\n * console.log('Did you mean', result.emailDetails.signals.suggestedDomain);\n * }\n * ```\n */\nexport class VerifNow {\n readonly #apiKey: string;\n readonly #baseUrl: string;\n readonly #timeoutMs: number;\n readonly #retry: Required<RetryOptions> | null;\n readonly #headers: Record<string, string>;\n readonly #fetch: typeof globalThis.fetch;\n\n constructor(options: VerifNowOptions) {\n if (!options?.apiKey || options.apiKey.trim() === '') {\n throw new VerifNowError(\n 'A VerifNow API key is required. Create one in the dashboard and pass it as `apiKey`.',\n );\n }\n\n const fetchImpl = options.fetch ?? globalThis.fetch;\n if (typeof fetchImpl !== 'function') {\n throw new VerifNowError(\n 'No global fetch available. Use Node 18 or later, or pass a `fetch` implementation.',\n );\n }\n\n this.#apiKey = options.apiKey.trim();\n // Trailing slashes would produce `//api/v1/...`, which some proxies reject.\n this.#baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\\/+$/, '');\n this.#timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n this.#retry =\n options.retry === false ? null : { ...DEFAULT_RETRY, ...(options.retry ?? {}) };\n this.#headers = options.headers ?? {};\n this.#fetch = fetchImpl.bind(globalThis);\n }\n\n /** Validate an email address: syntax, DNS/MX, typo, disposable, role-based, quality score. */\n validateEmail(value: string, options?: RequestOptions): Promise<ValidationResult> {\n return this.validate('email', value, options);\n }\n\n /**\n * Validate a phone number against its country's numbering plan.\n *\n * The number must include its country code (`+33…` or `0033…`). Valid numbers come back in\n * E.164 as `normalizedValue`, with country and line type in `phoneDetails`.\n */\n validatePhone(value: string, options?: RequestOptions): Promise<ValidationResult> {\n return this.validate('phone', value, options);\n }\n\n /**\n * Validate an IBAN against the SWIFT registry entry for its country, then its check digits.\n *\n * `ibanDetails` reports the two separately: check digits catch a typo, the registry catches an\n * account number that could never exist in that country.\n */\n validateIban(value: string, options?: RequestOptions): Promise<ValidationResult> {\n return this.validate('iban', value, options);\n }\n\n /** Validate a VAT number. */\n validateVat(value: string, options?: RequestOptions): Promise<ValidationResult> {\n return this.validate('vat', value, options);\n }\n\n /**\n * Validate a Canadian Social Insurance Number: format and Luhn check digit.\n *\n * `nasDetails` flags a temporary resident's number (it expires with their permit) and numbers\n * from series not issued to individuals. Only collect a SIN where the law requires it.\n */\n validateNas(value: string, options?: RequestOptions): Promise<ValidationResult> {\n return this.validate('nas', value, options);\n }\n\n /**\n * Validate a US Social Security Number against the numbers the SSA never issues.\n *\n * An SSN has no check digit: a typo that lands on another possible number cannot be caught, and\n * only the SSA can confirm a number was issued. `ssnDetails.itin` flags an IRS ITIN.\n */\n validateSsn(value: string, options?: RequestOptions): Promise<ValidationResult> {\n return this.validate('ssn', value, options);\n }\n\n /**\n * Validate a Spanish NIF: a DNI, a NIE (foreign nationals), the K/L/M series, or a company NIF.\n *\n * `nifDetails` says which, whether it belongs to a person, and for a company its legal form.\n * Spanish only — a Portuguese NIF is a different scheme and is not accepted here.\n */\n validateNif(value: string, options?: RequestOptions): Promise<ValidationResult> {\n return this.validate('nif', value, options);\n }\n\n /**\n * Validate a value against any rule.\n *\n * The typed helpers above call this. Use it directly when the rule is chosen at runtime.\n */\n async validate(\n rule: ValidationRule,\n value: string,\n options: RequestOptions = {},\n ): Promise<ValidationResult> {\n if (typeof value !== 'string' || value.trim() === '') {\n // Caught here rather than server-side: an empty value consumes quota and can only fail.\n throw new VerifNowRequestError(\n `Cannot validate an empty value for rule \"${rule}\".`,\n );\n }\n\n const url = `${this.#baseUrl}/api/v1/validate/${rule}`;\n const body = JSON.stringify({ value });\n const maxAttempts = this.#retry ? this.#retry.attempts + 1 : 1;\n\n let lastError: VerifNowError | undefined;\n\n for (let attempt = 0; attempt < maxAttempts; attempt++) {\n try {\n return await this.#requestOnce(url, body, options);\n } catch (error) {\n if (!(error instanceof VerifNowError)) throw error;\n lastError = error;\n\n const isLastAttempt = attempt === maxAttempts - 1;\n if (isLastAttempt || !this.#retry) throw error;\n\n const delay = this.#retryDelay(error, attempt);\n if (delay === null) throw error;\n\n await sleep(delay);\n }\n }\n\n /* c8 ignore next -- the loop either returns or throws */\n throw lastError ?? new VerifNowError('Request failed');\n }\n\n /**\n * How long to wait before retrying, or `null` when the error should surface immediately.\n *\n * A 429 is retried only when the reset is close: the concurrency limit clears in\n * milliseconds, but a spent monthly quota does not, and sleeping on it helps nobody.\n */\n #retryDelay(error: VerifNowError, attempt: number): number | null {\n const retry = this.#retry!;\n const backoff = Math.min(retry.backoffMs * 2 ** attempt, retry.maxBackoffMs);\n\n if (error instanceof VerifNowRateLimitError) {\n const waitMs = (error.retryAfterSeconds ?? 0) * 1000;\n if (waitMs > retry.maxBackoffMs) return null;\n return Math.max(waitMs, backoff);\n }\n\n if (error instanceof VerifNowServerError) return backoff;\n // A timeout is retried: the deadline is ours, and the next attempt gets a fresh one.\n if (error instanceof VerifNowConnectionError) return backoff;\n\n // 400, 401 and unparseable bodies will fail identically on a second attempt.\n return null;\n }\n\n async #requestOnce(\n url: string,\n body: string,\n options: RequestOptions,\n ): Promise<ValidationResult> {\n const timeoutMs = options.timeoutMs ?? this.#timeoutMs;\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), timeoutMs);\n const abortFromCaller = () => controller.abort();\n options.signal?.addEventListener('abort', abortFromCaller, { once: true });\n\n let response: Response;\n try {\n response = await this.#fetch(url, {\n method: 'POST',\n headers: {\n ...this.#headers,\n 'Content-Type': 'application/json',\n Accept: 'application/json',\n 'X-API-KEY': this.#apiKey,\n 'X-VerifNow-SDK': `node/${VERSION}`,\n },\n body,\n signal: controller.signal,\n });\n } catch (cause) {\n // The caller's own cancellation is theirs to handle, not a transport failure.\n if (options.signal?.aborted) throw cause;\n\n const timedOut = controller.signal.aborted;\n throw new VerifNowConnectionError(\n timedOut\n ? `VerifNow request to ${url} timed out after ${timeoutMs}ms.`\n : `Could not reach the VerifNow API at ${url}. Check \\`baseUrl\\` and network access.`,\n { cause, timedOut },\n );\n } finally {\n clearTimeout(timer);\n options.signal?.removeEventListener('abort', abortFromCaller);\n }\n\n return this.#handleResponse(response);\n }\n\n async #handleResponse(response: Response): Promise<ValidationResult> {\n const requestId = response.headers.get('X-Request-Id') ?? undefined;\n const quota = parseQuota(response.headers);\n\n if (response.ok) {\n let payload: unknown;\n try {\n payload = await response.json();\n } catch (cause) {\n throw new VerifNowResponseError(\n 'VerifNow returned a success status with a body that is not valid JSON.',\n { status: response.status, requestId, cause },\n );\n }\n\n if (payload === null || typeof payload !== 'object') {\n throw new VerifNowResponseError(\n 'VerifNow returned an unexpected response shape.',\n { status: response.status, requestId },\n );\n }\n\n return mapResult(payload as Record<string, unknown>, quota);\n }\n\n const message = await readErrorMessage(response);\n const context = { status: response.status, requestId };\n\n if (response.status === 401 || response.status === 403) {\n throw new VerifNowAuthError(\n `VerifNow rejected the API key (${response.status}): ${message}`,\n context,\n );\n }\n\n if (response.status === 429) {\n throw new VerifNowRateLimitError(`VerifNow rate limit reached: ${message}`, {\n ...context,\n quota,\n retryAfterSeconds: parseRetryAfter(response.headers, quota),\n });\n }\n\n if (response.status >= 500) {\n throw new VerifNowServerError(\n `VerifNow returned ${response.status}: ${message}`,\n context,\n );\n }\n\n throw new VerifNowRequestError(\n `VerifNow rejected the request (${response.status}): ${message}`,\n context,\n );\n }\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\nfunction toNumber(value: string | null): number | undefined {\n if (value === null) return undefined;\n const parsed = Number(value);\n return Number.isFinite(parsed) ? parsed : undefined;\n}\n\nfunction parseQuota(headers: Headers): QuotaInfo | undefined {\n const limit = toNumber(headers.get('X-RateLimit-Limit'));\n const remaining = toNumber(headers.get('X-RateLimit-Remaining'));\n const resetSeconds = toNumber(headers.get('X-RateLimit-Reset'));\n const overage = headers.get('X-Quota-Overage') === 'true';\n\n if (\n limit === undefined &&\n remaining === undefined &&\n resetSeconds === undefined &&\n !overage\n ) {\n return undefined;\n }\n\n return {\n limit,\n remaining,\n resetAt: resetSeconds === undefined ? undefined : new Date(resetSeconds * 1000),\n overage,\n };\n}\n\nfunction parseRetryAfter(headers: Headers, quota?: QuotaInfo): number | undefined {\n const retryAfter = headers.get('Retry-After');\n if (retryAfter !== null) {\n const seconds = Number(retryAfter);\n if (Number.isFinite(seconds)) return seconds;\n\n // RFC 7231 also allows an HTTP-date.\n const asDate = Date.parse(retryAfter);\n if (!Number.isNaN(asDate)) {\n return Math.max(0, Math.ceil((asDate - Date.now()) / 1000));\n }\n }\n\n if (quota?.resetAt) {\n return Math.max(0, Math.ceil((quota.resetAt.getTime() - Date.now()) / 1000));\n }\n\n return undefined;\n}\n\nasync function readErrorMessage(response: Response): Promise<string> {\n try {\n const text = await response.text();\n if (!text) return response.statusText || 'no details';\n\n try {\n const parsed = JSON.parse(text) as Record<string, unknown>;\n const message = parsed.message ?? parsed.error;\n if (typeof message === 'string' && message !== '') return message;\n } catch {\n // Not JSON — a proxy or the servlet container's default error page.\n }\n\n return text.slice(0, 500);\n } catch {\n return response.statusText || 'no details';\n }\n}\n\nfunction asString(value: unknown): string | undefined {\n return typeof value === 'string' ? value : undefined;\n}\n\nfunction asBoolean(value: unknown): boolean | undefined {\n return typeof value === 'boolean' ? value : undefined;\n}\n\nfunction asNumber(value: unknown): number | undefined {\n return typeof value === 'number' && Number.isFinite(value) ? value : undefined;\n}\n\nfunction mapSignals(raw: unknown): EmailSignals | undefined {\n if (raw === null || typeof raw !== 'object') return undefined;\n const s = raw as Record<string, unknown>;\n\n return {\n syntaxValid: asBoolean(s.syntax_valid),\n mxValid: asBoolean(s.mx_valid),\n typoDetected: asBoolean(s.typo_detected),\n suggestedDomain: asString(s.suggested_domain),\n disposable: asBoolean(s.disposable),\n roleBased: asBoolean(s.role_based),\n freeProvider: asBoolean(s.free_provider),\n domainAgeDays: asNumber(s.domain_age_days),\n mxProvider: asString(s.mx_provider),\n mxQualityScore: asNumber(s.mx_quality_score),\n };\n}\n\nfunction mapEmailDetails(raw: unknown): EmailDetails | undefined {\n if (raw === null || typeof raw !== 'object') return undefined;\n const d = raw as Record<string, unknown>;\n\n return {\n signals: mapSignals(d.signals),\n riskScore: asNumber(d.risk_score),\n riskLevel: asString(d.risk_level) as EmailDetails['riskLevel'],\n deliverability: asString(d.deliverability) as EmailDetails['deliverability'],\n appliedLevel: asString(d.applied_level) as EmailDetails['appliedLevel'],\n };\n}\n\n/**\n * Reads `registered`, preserving the difference between `false` and `null`.\n *\n * `asBoolean` cannot be used here: it maps `null` to `undefined`, which would erase the one\n * distinction the whole VAT design exists to carry. `false` means the registry answered and the\n * number is not there; `null` means nobody could ask. A caller that cannot tell them apart will\n * reject real businesses whenever VIES is down.\n *\n * A missing key is read as `null` for the same reason — unknown, not absent.\n */\nfunction asRegistered(value: unknown): boolean | null {\n return typeof value === 'boolean' ? value : null;\n}\n\nfunction asDate(value: unknown): Date | undefined {\n if (typeof value !== 'string') return undefined;\n const parsed = new Date(value);\n return Number.isNaN(parsed.getTime()) ? undefined : parsed;\n}\n\nfunction mapVatDetails(raw: unknown): VatDetails | undefined {\n if (raw === null || typeof raw !== 'object') return undefined;\n const d = raw as Record<string, unknown>;\n\n return {\n formatValid: asBoolean(d.format_valid),\n registered: asRegistered(d.registered),\n countryCode: asString(d.country_code),\n source: asString(d.source) as VatDetails['source'],\n checkedAt: asDate(d.checked_at),\n traderName: asString(d.trader_name),\n traderAddress: asString(d.trader_address),\n viesAvailable: asBoolean(d.vies_available),\n consultationNumber: asString(d.consultation_number),\n };\n}\n\nfunction mapPhoneDetails(raw: unknown): PhoneDetails | undefined {\n if (raw === null || typeof raw !== 'object') return undefined;\n const d = raw as Record<string, unknown>;\n\n return {\n countryCode: asString(d.country_code),\n callingCode: asNumber(d.calling_code),\n lineType: asString(d.line_type) as PhoneDetails['lineType'],\n internationalFormat: asString(d.international_format),\n nationalFormat: asString(d.national_format),\n };\n}\n\nfunction mapIbanDetails(raw: unknown): IbanDetails | undefined {\n if (raw === null || typeof raw !== 'object') return undefined;\n const d = raw as Record<string, unknown>;\n\n return {\n countryCode: asString(d.country_code),\n structureValid: asBoolean(d.structure_valid),\n checksumValid: asBoolean(d.checksum_valid),\n length: asNumber(d.length),\n expectedLength: asNumber(d.expected_length),\n formatted: asString(d.formatted),\n };\n}\n\nfunction mapNasDetails(raw: unknown): NasDetails | undefined {\n if (raw === null || typeof raw !== 'object') return undefined;\n const d = raw as Record<string, unknown>;\n\n return {\n checksumValid: asBoolean(d.checksum_valid),\n temporaryResident: asBoolean(d.temporary_resident),\n individualSeries: asBoolean(d.individual_series),\n formatted: asString(d.formatted),\n };\n}\n\nfunction mapNifDetails(raw: unknown): NifDetails | undefined {\n if (raw === null || typeof raw !== 'object') return undefined;\n const d = raw as Record<string, unknown>;\n\n return {\n type: asString(d.type) as NifDetails['type'],\n naturalPerson: asBoolean(d.natural_person),\n checksumValid: asBoolean(d.checksum_valid),\n entityLetter: asString(d.entity_letter),\n entityType: asString(d.entity_type),\n };\n}\n\nfunction mapSsnDetails(raw: unknown): SsnDetails | undefined {\n if (raw === null || typeof raw !== 'object') return undefined;\n const d = raw as Record<string, unknown>;\n\n return { itin: asBoolean(d.itin) };\n}\n\n/**\n * Maps the API's snake_case diagnostics onto camelCase, so a TypeScript caller is not switching\n * naming conventions mid-expression. The untouched body stays available on `raw`.\n */\nfunction mapResult(\n payload: Record<string, unknown>,\n quota: QuotaInfo | undefined,\n): ValidationResult {\n return {\n valid: payload.valid === true,\n message: asString(payload.message),\n normalizedValue: asString(payload.normalizedValue) ?? null,\n originalValue: asString(payload.originalValue),\n validationLevel: asString(payload.validationLevel) as ValidationResult['validationLevel'],\n emailDetails: mapEmailDetails(payload.emailDetails),\n vatDetails: mapVatDetails(payload.vatDetails),\n phoneDetails: mapPhoneDetails(payload.phoneDetails),\n ibanDetails: mapIbanDetails(payload.ibanDetails),\n nasDetails: mapNasDetails(payload.nasDetails),\n nifDetails: mapNifDetails(payload.nifDetails),\n ssnDetails: mapSsnDetails(payload.ssnDetails),\n quota,\n raw: payload,\n };\n}\n","/**\n * Validation rules exposed by the VerifNow API.\n *\n * Each maps to `POST /api/v1/validate/{rule}`.\n */\nexport type ValidationRule =\n | 'email'\n | 'phone'\n | 'iban'\n | 'vat'\n | 'nas'\n | 'ssn'\n | 'nif';\n\nexport const VALIDATION_RULES: readonly ValidationRule[] = [\n 'email',\n 'phone',\n 'iban',\n 'vat',\n 'nas',\n 'ssn',\n 'nif',\n] as const;\n\n/**\n * Depth of checks applied to a request, decided by the plan attached to the API key.\n *\n * `STANDARD` runs on the FREE and STARTER plans, `ADVANCED` on GROWTH, `PREMIUM` on PRO.\n * Branch on this rather than on the plan name: it is the only value that tells you which\n * signals are actually present in the response.\n */\nexport type ValidationLevel = 'BASIC' | 'STANDARD' | 'ADVANCED' | 'PREMIUM';\n\n/** Categorical risk assessment. Returned from `ADVANCED` depth upward. */\nexport type RiskLevel = 'LOW' | 'MEDIUM' | 'HIGH';\n\nexport type Deliverability =\n | 'DELIVERABLE'\n | 'RISKY'\n | 'UNDELIVERABLE'\n | 'UNKNOWN';\n\n/**\n * Per-signal breakdown behind an email verdict.\n *\n * Fields are `undefined` when the applied level does not compute them — the last four require\n * `ADVANCED` depth or higher. Check `ValidationResult.appliedLevel` before relying on one.\n */\nexport interface EmailSignals {\n /** The address matches the syntax pattern for the applied level. */\n syntaxValid?: boolean;\n /** The domain resolves and publishes MX (or fallback A) records. */\n mxValid?: boolean;\n /** A likely typo was found in the domain, e.g. `gmail.con`. */\n typoDetected?: boolean;\n /** The correction proposed when `typoDetected` is true. */\n suggestedDomain?: string;\n /** The domain belongs to a throwaway mailbox provider. */\n disposable?: boolean;\n /** The local part is a shared mailbox: `info@`, `admin@`, `noreply@`. */\n roleBased?: boolean;\n /** The domain is a consumer mailbox provider. Requires ADVANCED. */\n freeProvider?: boolean;\n /** Estimated age of the domain in days. Requires ADVANCED. */\n domainAgeDays?: number;\n /** Identified mail provider, e.g. `google`. Requires ADVANCED. */\n mxProvider?: string;\n /** Mail server quality between 0 and 1. Requires ADVANCED. */\n mxQualityScore?: number;\n}\n\n/** Email-specific diagnostics. Absent when the applied level is `BASIC`. */\nexport interface EmailDetails {\n signals?: EmailSignals;\n /** Aggregated risk on a 0–100 scale, where 0 is the lowest risk. */\n riskScore?: number;\n /** Categorical risk. Requires ADVANCED depth or higher. */\n riskLevel?: RiskLevel;\n deliverability?: Deliverability;\n /** The depth actually applied, echoed back by the API. */\n appliedLevel?: ValidationLevel;\n}\n\n/**\n * Where a VAT registration verdict came from.\n *\n * VIES publishes no SLA and drops member states several times a month, so a VAT answer is not\n * always a live one. Branch on this rather than on `ValidationResult.valid` whenever the\n * difference matters for your own compliance.\n */\nexport type VatSource =\n /** Confirmed against VIES during this request. */\n | 'LIVE'\n /** Served from a VIES answer less than 24 hours old. */\n | 'CACHE'\n /** VIES was unreachable, so an older cached answer was used. */\n | 'STALE'\n /** VIES was unreachable and nothing was cached. Registration is unknown. */\n | 'UNVERIFIED'\n /** The country is outside VIES, so no registry lookup is possible. */\n | 'NOT_APPLICABLE';\n\n/** VAT-specific diagnostics. Present on `vat` validations. */\nexport interface VatDetails {\n /** The number matches its member state's structure. Decided locally, never depends on VIES. */\n formatValid?: boolean;\n /**\n * Present in the member state's registry.\n *\n * **`null` means unknown, never \"not registered.\"** It is returned when VIES could not be\n * consulted. Treating `null` as `false` rejects legitimate customers during someone else's\n * outage — the single most expensive mistake available in VAT validation.\n */\n registered: boolean | null;\n /** Member state the number belongs to, e.g. `IE`. Greece is `EL`, Northern Ireland `XI`. */\n countryCode?: string;\n source?: VatSource;\n /** When the registration was last confirmed against VIES. */\n checkedAt?: Date;\n /** Registered trading name, when the member state discloses it. Germany does not. */\n traderName?: string;\n /** Registered address, when the member state discloses it. */\n traderAddress?: string;\n /** Whether VIES could answer for this country during the request. */\n viesAvailable?: boolean;\n /**\n * The consultation number VIES issued for this lookup — the receipt a tax authority accepts as\n * evidence that you checked. Present only when your account has its own VAT number configured,\n * because VIES issues one only to an identified requester.\n */\n consultationNumber?: string;\n}\n\n/**\n * Kind of line, according to the country's numbering plan.\n *\n * A `PREMIUM_RATE` or `VOIP` number is still `valid` — it exists. This is how you decide to\n * exclude one, rather than the API deciding for you.\n */\nexport type PhoneLineType =\n | 'MOBILE'\n | 'FIXED_LINE'\n /** The plan does not distinguish the two — the case for the US and Canada. */\n | 'FIXED_LINE_OR_MOBILE'\n | 'TOLL_FREE'\n | 'PREMIUM_RATE'\n | 'SHARED_COST'\n | 'VOIP'\n | 'PERSONAL_NUMBER'\n | 'PAGER'\n | 'UAN'\n | 'VOICEMAIL'\n | 'UNKNOWN';\n\n/**\n * Phone-specific diagnostics. Present whenever the input parsed as an international number —\n * including when it is invalid for its country, so you can tell the user which country it was\n * read as.\n */\nexport interface PhoneDetails {\n /** ISO 3166-1 alpha-2 country, e.g. `FR`. Absent when the calling code is shared by several. */\n countryCode?: string;\n /** International calling code without the plus sign, e.g. `33`. */\n callingCode?: number;\n /** Absent when the number is invalid. */\n lineType?: PhoneLineType;\n /** e.g. `+33 6 12 34 56 78`. Absent when the number is invalid. */\n internationalFormat?: string;\n /** e.g. `06 12 34 56 78`. Absent when the number is invalid. */\n nationalFormat?: string;\n}\n\n/**\n * IBAN-specific diagnostics. Present on `iban` validations.\n *\n * Structure and checksum are reported separately because they fail for different reasons:\n * `structureValid` answers \"could this be an account number in that country\" (the SWIFT\n * registry's length and layout), `checksumValid` answers \"was it typed correctly\" (mod-97).\n * There is no bank name or BIC — that needs a registry the API does not hold.\n */\nexport interface IbanDetails {\n /** The IBAN's country, from its first two characters. */\n countryCode?: string;\n /** Length and character layout match the registry entry for that country. */\n structureValid?: boolean;\n /** The mod-97 check digits are correct. */\n checksumValid?: boolean;\n /** Length of the value as submitted, spaces removed. */\n length?: number;\n /** Length the registry requires for that country; absent for an unknown country. */\n expectedLength?: number;\n /** Print format, in groups of four. Present only for a valid IBAN. */\n formatted?: string;\n}\n\n/**\n * Canadian Social Insurance Number diagnostics. Present on `nas` validations.\n *\n * There is no province and no expiry date: the first digit no longer reliably identifies a\n * province, and a temporary resident's SIN expires with their permit, which only the document\n * shows.\n */\nexport interface NasDetails {\n /** The Luhn check digit is correct. */\n checksumValid?: boolean;\n /**\n * A 9-series number, issued to temporary residents. It expires with the holder's permit, and the\n * number itself does not say when — check the document.\n */\n temporaryResident?: boolean;\n /**\n * The first digit belongs to a series issued to individuals. `false` for numbers starting with\n * 0 or 8 — including 046 454 286, the government's sample number, which is why it is safe to\n * use in tests.\n */\n individualSeries?: boolean;\n /** Printed form, e.g. `046 454 286`. */\n formatted?: string;\n}\n\n/** The kinds of Spanish tax identification number. */\nexport type NifType =\n /** Spanish national with a DNI: 8 digits and a letter. */\n | 'DNI'\n /** Foreign national: X, Y or Z, 7 digits and a letter. */\n | 'NIE'\n /** Spanish national under 14 without a DNI. */\n | 'NIF_K'\n /** Spanish national resident abroad, staying under six months. */\n | 'NIF_L'\n /** Foreign national without a NIE. */\n | 'NIF_M'\n /** A company or other entity: a letter for the legal form, 7 digits, a control character. */\n | 'ENTITY';\n\n/** Spanish NIF diagnostics. Present on `nif` validations. */\nexport interface NifDetails {\n type?: NifType;\n /** The number belongs to a person rather than a company or other entity. */\n naturalPerson?: boolean;\n /** The control character is correct. */\n checksumValid?: boolean;\n /** For an entity, the letter that encodes its legal form, e.g. `B`. */\n entityLetter?: string;\n /** For an entity, its legal form, e.g. `Private limited company (Sociedad de responsabilidad limitada)`. */\n entityType?: string;\n}\n\n/**\n * US SSN diagnostics. Present on `ssn` validations.\n *\n * An SSN has no check digit, and since 2011 its first digits say nothing about a state, so there is\n * little a number can reveal about itself. The one thing worth knowing is whether it is an ITIN.\n */\nexport interface SsnDetails {\n /**\n * The number is an IRS ITIN, not an SSN: it starts with 9 and its fourth and fifth digits are in\n * 50-65, 70-88, 90-92 or 94-99. `valid` is `false` for the SSN, but an ITIN is an acceptable\n * taxpayer number where one is accepted (a W-9, for instance).\n */\n itin?: boolean;\n}\n\n/** Outcome of a single validation call. */\nexport interface ValidationResult {\n /** Whether the value passed every check the applied level ran. */\n valid: boolean;\n /** Human-readable explanation of the verdict. */\n message?: string;\n /** Canonical form of the input — `null` when the value is invalid. */\n normalizedValue: string | null;\n /** The value exactly as submitted. */\n originalValue?: string;\n /** Depth applied to this request. */\n validationLevel?: ValidationLevel;\n /** Present for email validations from `STANDARD` depth upward. */\n emailDetails?: EmailDetails;\n /** Present for VAT validations. */\n vatDetails?: VatDetails;\n /** Present for phone validations. The E.164 form is `normalizedValue`. */\n phoneDetails?: PhoneDetails;\n /** Present for IBAN validations. */\n ibanDetails?: IbanDetails;\n /** Present for Canadian SIN (`nas`) validations. */\n nasDetails?: NasDetails;\n /** Present for Spanish NIF validations. */\n nifDetails?: NifDetails;\n /** Present for US SSN validations. */\n ssnDetails?: SsnDetails;\n /** Quota state reported by the response headers. */\n quota?: QuotaInfo;\n /** The unmodified JSON body, for fields this SDK version does not model yet. */\n raw: Record<string, unknown>;\n}\n\n/** Quota counters read from the `X-RateLimit-*` response headers. */\nexport interface QuotaInfo {\n /** Validations included in the current billing period. */\n limit?: number;\n /** Validations left before overage or blocking. */\n remaining?: number;\n /** When the current period resets. */\n resetAt?: Date;\n /** True once you are past the included quota and into per-unit billing. */\n overage?: boolean;\n}\n\nexport interface RetryOptions {\n /**\n * Retry attempts after the first failure. Defaults to 2, so up to 3 requests in total.\n * Only connection failures, 429 and 5xx are retried — never a 400 or 401, which will not\n * succeed on a second try.\n */\n attempts?: number;\n /** Delay before the first retry, in ms. Doubles each attempt. Defaults to 200. */\n backoffMs?: number;\n /** Upper bound on a single backoff delay, in ms. Defaults to 2000. */\n maxBackoffMs?: number;\n}\n\nexport interface VerifNowOptions {\n /** API key created in the VerifNow dashboard. Sent as the `X-API-KEY` header. */\n apiKey: string;\n /** Override the API origin. Defaults to `https://api.verifnow.io`. */\n baseUrl?: string;\n /** Abort a single request after this many ms. Defaults to 5000. */\n timeoutMs?: number;\n /** Retry policy, or `false` to disable retries entirely. */\n retry?: RetryOptions | false;\n /** Extra headers merged into every request. */\n headers?: Record<string, string>;\n /**\n * Replacement for the global `fetch`, for tests or a custom agent.\n * Defaults to `globalThis.fetch`.\n */\n fetch?: typeof globalThis.fetch;\n}\n"],"mappings":"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","names":["asDate"]}
|
package/package.json
CHANGED