@verifnow/sdk 1.7.0 → 1.9.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 +44 -0
- package/dist/index.cjs +84 -14
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +88 -4
- package/dist/index.d.ts +88 -4
- package/dist/index.js +84 -14
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -78,9 +78,53 @@ if (vat.registered === null) {
|
|
|
78
78
|
return accept({ verified: true, stale: vat.source === 'STALE' });
|
|
79
79
|
```
|
|
80
80
|
|
|
81
|
+
### Does the number belong to this company?
|
|
82
|
+
|
|
83
|
+
Pass the name you expect — from a supplier form, for instance — and the response says whether it
|
|
84
|
+
matches the registered holder:
|
|
85
|
+
|
|
86
|
+
```ts
|
|
87
|
+
const result = await client.validateVat('ESA28015865', { traderName: 'Telefonica' });
|
|
88
|
+
|
|
89
|
+
result.vatDetails?.traderNameMatch; // 'MATCH' | 'MISMATCH' | 'NOT_AVAILABLE'
|
|
90
|
+
result.vatDetails?.traderNameMatchSource; // 'VERIFNOW' | 'VIES'
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Who compares depends on the member state. Where VIES publishes the holder's name (most of them),
|
|
94
|
+
VerifNow compares, ignoring case, accents, punctuation and legal forms. Spain publishes no name but
|
|
95
|
+
has VIES check one. Germany does neither, and the answer is `NOT_AVAILABLE` rather than a guess. A
|
|
96
|
+
`MISMATCH` is a question for a human, not proof of fraud.
|
|
97
|
+
|
|
81
98
|
Per-country VIES availability is public and needs no API key:
|
|
82
99
|
[`GET /api/v1/status/vies`](https://www.verifnow.io/en/status).
|
|
83
100
|
|
|
101
|
+
### VAT rates
|
|
102
|
+
|
|
103
|
+
The rates of the 27 member states, retrieved daily from the Commission's
|
|
104
|
+
[TEDB](https://ec.europa.eu/taxation_customs/tedb/). Public reference data: these calls spend no
|
|
105
|
+
quota.
|
|
106
|
+
|
|
107
|
+
```ts
|
|
108
|
+
const france = await client.vatRate('FR'); // GR is accepted for Greece (EL)
|
|
109
|
+
|
|
110
|
+
france.standardRate; // 20
|
|
111
|
+
france.reducedRates; // [2.1, 5.5, 10] — which one applies depends on the product
|
|
112
|
+
france.regionalRates; // [{ rate: 8.5, note: 'The standard VAT rate in Martinique, …', euVatArea: false }, …]
|
|
113
|
+
france.situationOn; // '2026-07-01' — the date TEDB says these rates apply from
|
|
114
|
+
france.fetchedAt; // Date — when VerifNow last retrieved them
|
|
115
|
+
|
|
116
|
+
const all = await client.vatRates(); // all.rates: one entry per member state
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
`euVatArea: false` marks the Canary Islands and the French overseas territories, which the VAT
|
|
120
|
+
Directive excludes: goods shipped there from another member state are an export, not a distance
|
|
121
|
+
sale at that rate.
|
|
122
|
+
|
|
123
|
+
**These are the rates a member state has, not the rate an invoice carries.** In B2B trade between
|
|
124
|
+
member states the invoice is usually zero-rated under the reverse charge, whatever the buyer's
|
|
125
|
+
country rate is. Multiplying an amount by the buyer's standard rate is wrong in exactly the case a
|
|
126
|
+
VAT number is collected for.
|
|
127
|
+
|
|
84
128
|
## Validators
|
|
85
129
|
|
|
86
130
|
```ts
|
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.9.0";
|
|
80
80
|
|
|
81
81
|
// src/client.ts
|
|
82
82
|
var DEFAULT_BASE_URL = "https://api.verifnow.io";
|
|
@@ -134,9 +134,18 @@ var VerifNow = class {
|
|
|
134
134
|
validateIban(value, options) {
|
|
135
135
|
return this.validate("iban", value, options);
|
|
136
136
|
}
|
|
137
|
-
/**
|
|
138
|
-
|
|
139
|
-
|
|
137
|
+
/**
|
|
138
|
+
* Validate a VAT number.
|
|
139
|
+
*
|
|
140
|
+
* Pass `traderName` to ask whether the number belongs to that company:
|
|
141
|
+
* `vatDetails.traderNameMatch` answers `MATCH`, `MISMATCH` or `NOT_AVAILABLE`, and
|
|
142
|
+
* `traderNameMatchSource` says who compared — VerifNow against the name VIES publishes, or VIES
|
|
143
|
+
* itself where it withholds the name but checks one (Spain). Germany does neither.
|
|
144
|
+
*/
|
|
145
|
+
validateVat(value, options = {}) {
|
|
146
|
+
const { traderName, ...requestOptions } = options;
|
|
147
|
+
const extra = traderName && traderName.trim() !== "" ? { traderName } : void 0;
|
|
148
|
+
return this.#validate("vat", value, requestOptions, extra);
|
|
140
149
|
}
|
|
141
150
|
/**
|
|
142
151
|
* Validate a Canadian Social Insurance Number: format and Luhn check digit.
|
|
@@ -170,19 +179,55 @@ var VerifNow = class {
|
|
|
170
179
|
*
|
|
171
180
|
* The typed helpers above call this. Use it directly when the rule is chosen at runtime.
|
|
172
181
|
*/
|
|
173
|
-
|
|
182
|
+
validate(rule, value, options = {}) {
|
|
183
|
+
return this.#validate(rule, value, options);
|
|
184
|
+
}
|
|
185
|
+
async #validate(rule, value, options, extra) {
|
|
174
186
|
if (typeof value !== "string" || value.trim() === "") {
|
|
175
187
|
throw new VerifNowRequestError(
|
|
176
188
|
`Cannot validate an empty value for rule "${rule}".`
|
|
177
189
|
);
|
|
178
190
|
}
|
|
179
191
|
const url = `${this.#baseUrl}/api/v1/validate/${rule}`;
|
|
180
|
-
const body = JSON.stringify({ value });
|
|
192
|
+
const body = JSON.stringify({ value, ...extra });
|
|
193
|
+
return this.#withRetry(
|
|
194
|
+
() => this.#requestOnce("POST", url, body, options, (payload, quota) => mapResult(payload, quota))
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* EU VAT rates of every member state, from the European Commission's TEDB.
|
|
199
|
+
*
|
|
200
|
+
* Public reference data: the call spends no quota. These are the rates a member state has, not
|
|
201
|
+
* the rate a sale is charged — in B2B trade between member states the invoice is usually
|
|
202
|
+
* zero-rated under the reverse charge whatever the buyer's country rate is.
|
|
203
|
+
*/
|
|
204
|
+
async vatRates(options = {}) {
|
|
205
|
+
const url = `${this.#baseUrl}/api/v1/vat/rates`;
|
|
206
|
+
return this.#withRetry(
|
|
207
|
+
() => this.#requestOnce("GET", url, void 0, options, (payload) => mapVatRates(payload))
|
|
208
|
+
);
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* One EU member state's VAT rates. Accepts `GR` for Greece as well as `EL`.
|
|
212
|
+
*
|
|
213
|
+
* A code outside the 27 member states throws {@link VerifNowRequestError} (HTTP 404).
|
|
214
|
+
*/
|
|
215
|
+
async vatRate(countryCode, options = {}) {
|
|
216
|
+
if (typeof countryCode !== "string" || countryCode.trim() === "") {
|
|
217
|
+
throw new VerifNowRequestError('A member state code is required, e.g. "FR".');
|
|
218
|
+
}
|
|
219
|
+
const url = `${this.#baseUrl}/api/v1/vat/rates/${encodeURIComponent(countryCode.trim())}`;
|
|
220
|
+
return this.#withRetry(
|
|
221
|
+
() => this.#requestOnce("GET", url, void 0, options, (payload) => mapCountryVatRates(payload))
|
|
222
|
+
);
|
|
223
|
+
}
|
|
224
|
+
/** Runs one request under the retry policy. */
|
|
225
|
+
async #withRetry(attemptOnce) {
|
|
181
226
|
const maxAttempts = this.#retry ? this.#retry.attempts + 1 : 1;
|
|
182
227
|
let lastError;
|
|
183
228
|
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
184
229
|
try {
|
|
185
|
-
return await
|
|
230
|
+
return await attemptOnce();
|
|
186
231
|
} catch (error) {
|
|
187
232
|
if (!(error instanceof VerifNowError)) throw error;
|
|
188
233
|
lastError = error;
|
|
@@ -213,7 +258,7 @@ var VerifNow = class {
|
|
|
213
258
|
if (error instanceof VerifNowConnectionError) return backoff;
|
|
214
259
|
return null;
|
|
215
260
|
}
|
|
216
|
-
async #requestOnce(url, body, options) {
|
|
261
|
+
async #requestOnce(method, url, body, options, map) {
|
|
217
262
|
const timeoutMs = options.timeoutMs ?? this.#timeoutMs;
|
|
218
263
|
const controller = new AbortController();
|
|
219
264
|
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
@@ -222,10 +267,10 @@ var VerifNow = class {
|
|
|
222
267
|
let response;
|
|
223
268
|
try {
|
|
224
269
|
response = await this.#fetch(url, {
|
|
225
|
-
method
|
|
270
|
+
method,
|
|
226
271
|
headers: {
|
|
227
272
|
...this.#headers,
|
|
228
|
-
"Content-Type": "application/json",
|
|
273
|
+
...body === void 0 ? {} : { "Content-Type": "application/json" },
|
|
229
274
|
Accept: "application/json",
|
|
230
275
|
"X-API-KEY": this.#apiKey,
|
|
231
276
|
"X-VerifNow-SDK": `node/${VERSION}`
|
|
@@ -244,9 +289,9 @@ var VerifNow = class {
|
|
|
244
289
|
clearTimeout(timer);
|
|
245
290
|
options.signal?.removeEventListener("abort", abortFromCaller);
|
|
246
291
|
}
|
|
247
|
-
return this.#handleResponse(response);
|
|
292
|
+
return this.#handleResponse(response, map);
|
|
248
293
|
}
|
|
249
|
-
async #handleResponse(response) {
|
|
294
|
+
async #handleResponse(response, map) {
|
|
250
295
|
const requestId = response.headers.get("X-Request-Id") ?? void 0;
|
|
251
296
|
const quota = parseQuota(response.headers);
|
|
252
297
|
if (response.ok) {
|
|
@@ -265,7 +310,7 @@ var VerifNow = class {
|
|
|
265
310
|
{ status: response.status, requestId }
|
|
266
311
|
);
|
|
267
312
|
}
|
|
268
|
-
return
|
|
313
|
+
return map(payload, quota);
|
|
269
314
|
}
|
|
270
315
|
const message = await readErrorMessage(response);
|
|
271
316
|
const context = { status: response.status, requestId };
|
|
@@ -403,7 +448,9 @@ function mapVatDetails(raw) {
|
|
|
403
448
|
traderName: asString(d.trader_name),
|
|
404
449
|
traderAddress: asString(d.trader_address),
|
|
405
450
|
viesAvailable: asBoolean(d.vies_available),
|
|
406
|
-
consultationNumber: asString(d.consultation_number)
|
|
451
|
+
consultationNumber: asString(d.consultation_number),
|
|
452
|
+
traderNameMatch: asString(d.trader_name_match),
|
|
453
|
+
traderNameMatchSource: asString(d.trader_name_match_source)
|
|
407
454
|
};
|
|
408
455
|
}
|
|
409
456
|
function mapPhoneDetails(raw) {
|
|
@@ -456,6 +503,29 @@ function mapSsnDetails(raw) {
|
|
|
456
503
|
const d = raw;
|
|
457
504
|
return { itin: asBoolean(d.itin) };
|
|
458
505
|
}
|
|
506
|
+
function mapCountryVatRates(raw) {
|
|
507
|
+
const numbers = (value) => Array.isArray(value) ? value.filter((v) => asNumber(v) !== void 0) : [];
|
|
508
|
+
return {
|
|
509
|
+
countryCode: asString(raw.countryCode) ?? "",
|
|
510
|
+
standardRate: asNumber(raw.standardRate) ?? Number.NaN,
|
|
511
|
+
reducedRates: numbers(raw.reducedRates),
|
|
512
|
+
regionalRates: Array.isArray(raw.regionalRates) ? raw.regionalRates.filter((r) => r !== null && typeof r === "object").map((r) => ({
|
|
513
|
+
rate: asNumber(r.rate) ?? Number.NaN,
|
|
514
|
+
note: asString(r.note),
|
|
515
|
+
euVatArea: asBoolean(r.euVatArea)
|
|
516
|
+
})) : [],
|
|
517
|
+
situationOn: asString(raw.situationOn),
|
|
518
|
+
fetchedAt: asDate(raw.fetchedAt)
|
|
519
|
+
};
|
|
520
|
+
}
|
|
521
|
+
function mapVatRates(raw) {
|
|
522
|
+
const rates = Array.isArray(raw.rates) ? raw.rates.filter((r) => r !== null && typeof r === "object").map(mapCountryVatRates) : [];
|
|
523
|
+
return {
|
|
524
|
+
source: asString(raw.source) ?? "TEDB",
|
|
525
|
+
sourceUrl: asString(raw.sourceUrl),
|
|
526
|
+
rates
|
|
527
|
+
};
|
|
528
|
+
}
|
|
459
529
|
function mapResult(payload, quota) {
|
|
460
530
|
return {
|
|
461
531
|
valid: payload.valid === true,
|
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 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.7.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 sepa: asBoolean(d.sepa),\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 /**\n * The country is inside the SEPA schemes' geographical scope, so a bank there may collect a\n * SEPA direct debit. Whether this particular bank does is published per bank, not per country,\n * and cannot be read from an IBAN.\n */\n sepa?: boolean;\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"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/errors.ts","../src/version.ts","../src/client.ts","../src/types.ts"],"sourcesContent":["export { VerifNow, type RequestOptions, type VatValidationOptions } 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 CountryVatRates,\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 RegionalVatRate,\n type TraderNameMatch,\n type TraderNameMatchSource,\n type RetryOptions,\n type RiskLevel,\n type ValidationLevel,\n type ValidationResult,\n type ValidationRule,\n type VatDetails,\n type VatRates,\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.9.0';\n","import {\n VerifNowAuthError,\n VerifNowConnectionError,\n VerifNowError,\n VerifNowRateLimitError,\n VerifNowRequestError,\n VerifNowResponseError,\n VerifNowServerError,\n} from './errors.js';\nimport type {\n CountryVatRates,\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 VatRates,\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/** Options for `validateVat`: the per-call options, plus the company expected to hold the number. */\nexport interface VatValidationOptions extends RequestOptions {\n /** The company you expect to hold this VAT number, e.g. from a supplier form. At most 200 characters. */\n traderName?: string;\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 /**\n * Validate a VAT number.\n *\n * Pass `traderName` to ask whether the number belongs to that company:\n * `vatDetails.traderNameMatch` answers `MATCH`, `MISMATCH` or `NOT_AVAILABLE`, and\n * `traderNameMatchSource` says who compared — VerifNow against the name VIES publishes, or VIES\n * itself where it withholds the name but checks one (Spain). Germany does neither.\n */\n validateVat(value: string, options: VatValidationOptions = {}): Promise<ValidationResult> {\n const { traderName, ...requestOptions } = options;\n const extra = traderName && traderName.trim() !== '' ? { traderName } : undefined;\n return this.#validate('vat', value, requestOptions, extra);\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 validate(\n rule: ValidationRule,\n value: string,\n options: RequestOptions = {},\n ): Promise<ValidationResult> {\n return this.#validate(rule, value, options);\n }\n\n async #validate(\n rule: ValidationRule,\n value: string,\n options: RequestOptions,\n extra?: Record<string, string>,\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, ...extra });\n return this.#withRetry(() =>\n this.#requestOnce('POST', url, body, options, (payload, quota) => mapResult(payload, quota)),\n );\n }\n\n /**\n * EU VAT rates of every member state, from the European Commission's TEDB.\n *\n * Public reference data: the call spends no quota. These are the rates a member state has, not\n * the rate a sale is charged — in B2B trade between member states the invoice is usually\n * zero-rated under the reverse charge whatever the buyer's country rate is.\n */\n async vatRates(options: RequestOptions = {}): Promise<VatRates> {\n const url = `${this.#baseUrl}/api/v1/vat/rates`;\n return this.#withRetry(() =>\n this.#requestOnce('GET', url, undefined, options, (payload) => mapVatRates(payload)),\n );\n }\n\n /**\n * One EU member state's VAT rates. Accepts `GR` for Greece as well as `EL`.\n *\n * A code outside the 27 member states throws {@link VerifNowRequestError} (HTTP 404).\n */\n async vatRate(countryCode: string, options: RequestOptions = {}): Promise<CountryVatRates> {\n if (typeof countryCode !== 'string' || countryCode.trim() === '') {\n throw new VerifNowRequestError('A member state code is required, e.g. \"FR\".');\n }\n const url = `${this.#baseUrl}/api/v1/vat/rates/${encodeURIComponent(countryCode.trim())}`;\n return this.#withRetry(() =>\n this.#requestOnce('GET', url, undefined, options, (payload) => mapCountryVatRates(payload)),\n );\n }\n\n /** Runs one request under the retry policy. */\n async #withRetry<T>(attemptOnce: () => Promise<T>): Promise<T> {\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 attemptOnce();\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<T>(\n method: 'GET' | 'POST',\n url: string,\n body: string | undefined,\n options: RequestOptions,\n map: (payload: Record<string, unknown>, quota: QuotaInfo | undefined) => T,\n ): Promise<T> {\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,\n headers: {\n ...this.#headers,\n ...(body === undefined ? {} : { '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, map);\n }\n\n async #handleResponse<T>(\n response: Response,\n map: (payload: Record<string, unknown>, quota: QuotaInfo | undefined) => T,\n ): Promise<T> {\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 map(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 traderNameMatch: asString(d.trader_name_match) as VatDetails['traderNameMatch'],\n traderNameMatchSource: asString(d.trader_name_match_source) as VatDetails['traderNameMatchSource'],\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 sepa: asBoolean(d.sepa),\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 mapCountryVatRates(raw: Record<string, unknown>): CountryVatRates {\n const numbers = (value: unknown): number[] =>\n Array.isArray(value) ? value.filter((v): v is number => asNumber(v) !== undefined) : [];\n\n return {\n countryCode: asString(raw.countryCode) ?? '',\n standardRate: asNumber(raw.standardRate) ?? Number.NaN,\n reducedRates: numbers(raw.reducedRates),\n regionalRates: Array.isArray(raw.regionalRates)\n ? raw.regionalRates\n .filter((r): r is Record<string, unknown> => r !== null && typeof r === 'object')\n .map((r) => ({\n rate: asNumber(r.rate) ?? Number.NaN,\n note: asString(r.note),\n euVatArea: asBoolean(r.euVatArea),\n }))\n : [],\n situationOn: asString(raw.situationOn),\n fetchedAt: asDate(raw.fetchedAt),\n };\n}\n\nfunction mapVatRates(raw: Record<string, unknown>): VatRates {\n const rates = Array.isArray(raw.rates)\n ? raw.rates\n .filter((r): r is Record<string, unknown> => r !== null && typeof r === 'object')\n .map(mapCountryVatRates)\n : [];\n return {\n source: asString(raw.source) ?? 'TEDB',\n sourceUrl: asString(raw.sourceUrl),\n rates,\n };\n}\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 */\n/** Whether a supplied company name belongs to a VAT number's registered holder. */\nexport type TraderNameMatch = 'MATCH' | 'MISMATCH' | 'NOT_AVAILABLE';\n\n/** Who compared the names. */\nexport type TraderNameMatchSource = 'VIES' | 'VERIFNOW';\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 * Present when a `traderName` was sent: whether it belongs to the registered holder. `MISMATCH`\n * is a question for a human, not proof of fraud — trading names and group companies differ.\n */\n traderNameMatch?: TraderNameMatch;\n /** Who compared: `VERIFNOW`, against the name VIES published, or `VIES` itself (Spain). */\n traderNameMatchSource?: TraderNameMatchSource;\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 /**\n * The country is inside the SEPA schemes' geographical scope, so a bank there may collect a\n * SEPA direct debit. Whether this particular bank does is published per bank, not per country,\n * and cannot be read from an IBAN.\n */\n sepa?: boolean;\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/** A VAT rate applying to part of a member state only — an overseas department, an island. */\nexport interface RegionalVatRate {\n rate: number;\n /** Where it applies, in the words of the Commission's TEDB. */\n note?: string;\n /**\n * `false` for the Canary Islands and the French overseas territories, which the VAT Directive\n * excludes (Article 6(1)): goods shipped there from another member state are an export, not a\n * distance sale at this rate.\n */\n euVatArea?: boolean;\n}\n\n/**\n * One EU member state's VAT rates, from the European Commission's TEDB.\n *\n * These are the rates the member state has, not the rate a sale is charged: which one applies\n * depends on who sells to whom and what. In B2B trade between member states the invoice is usually\n * zero-rated under the reverse charge, whatever the buyer's country rate is.\n */\nexport interface CountryVatRates {\n /** Member state as TEDB and VIES name it: Greece is `EL`. */\n countryCode: string;\n /** The national standard rate, e.g. `20` for France. */\n standardRate: number;\n /**\n * Every reduced, super-reduced and parking rate on the mainland territory, ascending.\n * TEDB's own sub-labels are inconsistent between member states, so they are not reproduced.\n */\n reducedRates: number[];\n /** Rates for part of the territory only, e.g. 8.5 % in Martinique, Guadeloupe and Réunion. */\n regionalRates: RegionalVatRate[];\n /**\n * The date TEDB says these rates apply from, as `YYYY-MM-DD`. Kept as a string: a date without\n * a time zone turned into a `Date` can land on the previous day.\n */\n situationOn?: string;\n /** When VerifNow last retrieved them from TEDB. */\n fetchedAt?: Date;\n}\n\n/** VAT rates of every EU member state. */\nexport interface VatRates {\n /** Always `TEDB`, the Commission's Taxes in Europe Database. */\n source: string;\n sourceUrl?: string;\n /** One entry per member state retrieved so far — normally all 27. */\n rates: CountryVatRates[];\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/dist/index.d.cts
CHANGED
|
@@ -62,6 +62,10 @@ interface EmailDetails {
|
|
|
62
62
|
* always a live one. Branch on this rather than on `ValidationResult.valid` whenever the
|
|
63
63
|
* difference matters for your own compliance.
|
|
64
64
|
*/
|
|
65
|
+
/** Whether a supplied company name belongs to a VAT number's registered holder. */
|
|
66
|
+
type TraderNameMatch = 'MATCH' | 'MISMATCH' | 'NOT_AVAILABLE';
|
|
67
|
+
/** Who compared the names. */
|
|
68
|
+
type TraderNameMatchSource = 'VIES' | 'VERIFNOW';
|
|
65
69
|
type VatSource =
|
|
66
70
|
/** Confirmed against VIES during this request. */
|
|
67
71
|
'LIVE'
|
|
@@ -102,6 +106,13 @@ interface VatDetails {
|
|
|
102
106
|
* because VIES issues one only to an identified requester.
|
|
103
107
|
*/
|
|
104
108
|
consultationNumber?: string;
|
|
109
|
+
/**
|
|
110
|
+
* Present when a `traderName` was sent: whether it belongs to the registered holder. `MISMATCH`
|
|
111
|
+
* is a question for a human, not proof of fraud — trading names and group companies differ.
|
|
112
|
+
*/
|
|
113
|
+
traderNameMatch?: TraderNameMatch;
|
|
114
|
+
/** Who compared: `VERIFNOW`, against the name VIES published, or `VIES` itself (Spain). */
|
|
115
|
+
traderNameMatchSource?: TraderNameMatchSource;
|
|
105
116
|
}
|
|
106
117
|
/**
|
|
107
118
|
* Kind of line, according to the country's numbering plan.
|
|
@@ -252,6 +263,53 @@ interface ValidationResult {
|
|
|
252
263
|
/** The unmodified JSON body, for fields this SDK version does not model yet. */
|
|
253
264
|
raw: Record<string, unknown>;
|
|
254
265
|
}
|
|
266
|
+
/** A VAT rate applying to part of a member state only — an overseas department, an island. */
|
|
267
|
+
interface RegionalVatRate {
|
|
268
|
+
rate: number;
|
|
269
|
+
/** Where it applies, in the words of the Commission's TEDB. */
|
|
270
|
+
note?: string;
|
|
271
|
+
/**
|
|
272
|
+
* `false` for the Canary Islands and the French overseas territories, which the VAT Directive
|
|
273
|
+
* excludes (Article 6(1)): goods shipped there from another member state are an export, not a
|
|
274
|
+
* distance sale at this rate.
|
|
275
|
+
*/
|
|
276
|
+
euVatArea?: boolean;
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* One EU member state's VAT rates, from the European Commission's TEDB.
|
|
280
|
+
*
|
|
281
|
+
* These are the rates the member state has, not the rate a sale is charged: which one applies
|
|
282
|
+
* depends on who sells to whom and what. In B2B trade between member states the invoice is usually
|
|
283
|
+
* zero-rated under the reverse charge, whatever the buyer's country rate is.
|
|
284
|
+
*/
|
|
285
|
+
interface CountryVatRates {
|
|
286
|
+
/** Member state as TEDB and VIES name it: Greece is `EL`. */
|
|
287
|
+
countryCode: string;
|
|
288
|
+
/** The national standard rate, e.g. `20` for France. */
|
|
289
|
+
standardRate: number;
|
|
290
|
+
/**
|
|
291
|
+
* Every reduced, super-reduced and parking rate on the mainland territory, ascending.
|
|
292
|
+
* TEDB's own sub-labels are inconsistent between member states, so they are not reproduced.
|
|
293
|
+
*/
|
|
294
|
+
reducedRates: number[];
|
|
295
|
+
/** Rates for part of the territory only, e.g. 8.5 % in Martinique, Guadeloupe and Réunion. */
|
|
296
|
+
regionalRates: RegionalVatRate[];
|
|
297
|
+
/**
|
|
298
|
+
* The date TEDB says these rates apply from, as `YYYY-MM-DD`. Kept as a string: a date without
|
|
299
|
+
* a time zone turned into a `Date` can land on the previous day.
|
|
300
|
+
*/
|
|
301
|
+
situationOn?: string;
|
|
302
|
+
/** When VerifNow last retrieved them from TEDB. */
|
|
303
|
+
fetchedAt?: Date;
|
|
304
|
+
}
|
|
305
|
+
/** VAT rates of every EU member state. */
|
|
306
|
+
interface VatRates {
|
|
307
|
+
/** Always `TEDB`, the Commission's Taxes in Europe Database. */
|
|
308
|
+
source: string;
|
|
309
|
+
sourceUrl?: string;
|
|
310
|
+
/** One entry per member state retrieved so far — normally all 27. */
|
|
311
|
+
rates: CountryVatRates[];
|
|
312
|
+
}
|
|
255
313
|
/** Quota counters read from the `X-RateLimit-*` response headers. */
|
|
256
314
|
interface QuotaInfo {
|
|
257
315
|
/** Validations included in the current billing period. */
|
|
@@ -300,6 +358,11 @@ interface RequestOptions {
|
|
|
300
358
|
/** Cancel the call from your own controller. Combined with the timeout. */
|
|
301
359
|
signal?: AbortSignal;
|
|
302
360
|
}
|
|
361
|
+
/** Options for `validateVat`: the per-call options, plus the company expected to hold the number. */
|
|
362
|
+
interface VatValidationOptions extends RequestOptions {
|
|
363
|
+
/** The company you expect to hold this VAT number, e.g. from a supplier form. At most 200 characters. */
|
|
364
|
+
traderName?: string;
|
|
365
|
+
}
|
|
303
366
|
/**
|
|
304
367
|
* Client for the VerifNow validation API.
|
|
305
368
|
*
|
|
@@ -335,8 +398,15 @@ declare class VerifNow {
|
|
|
335
398
|
* account number that could never exist in that country.
|
|
336
399
|
*/
|
|
337
400
|
validateIban(value: string, options?: RequestOptions): Promise<ValidationResult>;
|
|
338
|
-
/**
|
|
339
|
-
|
|
401
|
+
/**
|
|
402
|
+
* Validate a VAT number.
|
|
403
|
+
*
|
|
404
|
+
* Pass `traderName` to ask whether the number belongs to that company:
|
|
405
|
+
* `vatDetails.traderNameMatch` answers `MATCH`, `MISMATCH` or `NOT_AVAILABLE`, and
|
|
406
|
+
* `traderNameMatchSource` says who compared — VerifNow against the name VIES publishes, or VIES
|
|
407
|
+
* itself where it withholds the name but checks one (Spain). Germany does neither.
|
|
408
|
+
*/
|
|
409
|
+
validateVat(value: string, options?: VatValidationOptions): Promise<ValidationResult>;
|
|
340
410
|
/**
|
|
341
411
|
* Validate a Canadian Social Insurance Number: format and Luhn check digit.
|
|
342
412
|
*
|
|
@@ -364,6 +434,20 @@ declare class VerifNow {
|
|
|
364
434
|
* The typed helpers above call this. Use it directly when the rule is chosen at runtime.
|
|
365
435
|
*/
|
|
366
436
|
validate(rule: ValidationRule, value: string, options?: RequestOptions): Promise<ValidationResult>;
|
|
437
|
+
/**
|
|
438
|
+
* EU VAT rates of every member state, from the European Commission's TEDB.
|
|
439
|
+
*
|
|
440
|
+
* Public reference data: the call spends no quota. These are the rates a member state has, not
|
|
441
|
+
* the rate a sale is charged — in B2B trade between member states the invoice is usually
|
|
442
|
+
* zero-rated under the reverse charge whatever the buyer's country rate is.
|
|
443
|
+
*/
|
|
444
|
+
vatRates(options?: RequestOptions): Promise<VatRates>;
|
|
445
|
+
/**
|
|
446
|
+
* One EU member state's VAT rates. Accepts `GR` for Greece as well as `EL`.
|
|
447
|
+
*
|
|
448
|
+
* A code outside the 27 member states throws {@link VerifNowRequestError} (HTTP 404).
|
|
449
|
+
*/
|
|
450
|
+
vatRate(countryCode: string, options?: RequestOptions): Promise<CountryVatRates>;
|
|
367
451
|
}
|
|
368
452
|
|
|
369
453
|
/**
|
|
@@ -448,6 +532,6 @@ declare class VerifNowResponseError extends VerifNowError {
|
|
|
448
532
|
*
|
|
449
533
|
* Kept in sync with `package.json` by a test — bump both together.
|
|
450
534
|
*/
|
|
451
|
-
declare const VERSION = "1.
|
|
535
|
+
declare const VERSION = "1.9.0";
|
|
452
536
|
|
|
453
|
-
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 };
|
|
537
|
+
export { type CountryVatRates, type Deliverability, type EmailDetails, type EmailSignals, type IbanDetails, type NasDetails, type NifDetails, type NifType, type PhoneDetails, type PhoneLineType, type QuotaInfo, type RegionalVatRate, type RequestOptions, type RetryOptions, type RiskLevel, type SsnDetails, type TraderNameMatch, type TraderNameMatchSource, VALIDATION_RULES, VERSION, type ValidationLevel, type ValidationResult, type ValidationRule, type VatDetails, type VatRates, type VatSource, type VatValidationOptions, VerifNow, VerifNowAuthError, VerifNowConnectionError, VerifNowError, type VerifNowOptions, VerifNowRateLimitError, VerifNowRequestError, VerifNowResponseError, VerifNowServerError };
|
package/dist/index.d.ts
CHANGED
|
@@ -62,6 +62,10 @@ interface EmailDetails {
|
|
|
62
62
|
* always a live one. Branch on this rather than on `ValidationResult.valid` whenever the
|
|
63
63
|
* difference matters for your own compliance.
|
|
64
64
|
*/
|
|
65
|
+
/** Whether a supplied company name belongs to a VAT number's registered holder. */
|
|
66
|
+
type TraderNameMatch = 'MATCH' | 'MISMATCH' | 'NOT_AVAILABLE';
|
|
67
|
+
/** Who compared the names. */
|
|
68
|
+
type TraderNameMatchSource = 'VIES' | 'VERIFNOW';
|
|
65
69
|
type VatSource =
|
|
66
70
|
/** Confirmed against VIES during this request. */
|
|
67
71
|
'LIVE'
|
|
@@ -102,6 +106,13 @@ interface VatDetails {
|
|
|
102
106
|
* because VIES issues one only to an identified requester.
|
|
103
107
|
*/
|
|
104
108
|
consultationNumber?: string;
|
|
109
|
+
/**
|
|
110
|
+
* Present when a `traderName` was sent: whether it belongs to the registered holder. `MISMATCH`
|
|
111
|
+
* is a question for a human, not proof of fraud — trading names and group companies differ.
|
|
112
|
+
*/
|
|
113
|
+
traderNameMatch?: TraderNameMatch;
|
|
114
|
+
/** Who compared: `VERIFNOW`, against the name VIES published, or `VIES` itself (Spain). */
|
|
115
|
+
traderNameMatchSource?: TraderNameMatchSource;
|
|
105
116
|
}
|
|
106
117
|
/**
|
|
107
118
|
* Kind of line, according to the country's numbering plan.
|
|
@@ -252,6 +263,53 @@ interface ValidationResult {
|
|
|
252
263
|
/** The unmodified JSON body, for fields this SDK version does not model yet. */
|
|
253
264
|
raw: Record<string, unknown>;
|
|
254
265
|
}
|
|
266
|
+
/** A VAT rate applying to part of a member state only — an overseas department, an island. */
|
|
267
|
+
interface RegionalVatRate {
|
|
268
|
+
rate: number;
|
|
269
|
+
/** Where it applies, in the words of the Commission's TEDB. */
|
|
270
|
+
note?: string;
|
|
271
|
+
/**
|
|
272
|
+
* `false` for the Canary Islands and the French overseas territories, which the VAT Directive
|
|
273
|
+
* excludes (Article 6(1)): goods shipped there from another member state are an export, not a
|
|
274
|
+
* distance sale at this rate.
|
|
275
|
+
*/
|
|
276
|
+
euVatArea?: boolean;
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* One EU member state's VAT rates, from the European Commission's TEDB.
|
|
280
|
+
*
|
|
281
|
+
* These are the rates the member state has, not the rate a sale is charged: which one applies
|
|
282
|
+
* depends on who sells to whom and what. In B2B trade between member states the invoice is usually
|
|
283
|
+
* zero-rated under the reverse charge, whatever the buyer's country rate is.
|
|
284
|
+
*/
|
|
285
|
+
interface CountryVatRates {
|
|
286
|
+
/** Member state as TEDB and VIES name it: Greece is `EL`. */
|
|
287
|
+
countryCode: string;
|
|
288
|
+
/** The national standard rate, e.g. `20` for France. */
|
|
289
|
+
standardRate: number;
|
|
290
|
+
/**
|
|
291
|
+
* Every reduced, super-reduced and parking rate on the mainland territory, ascending.
|
|
292
|
+
* TEDB's own sub-labels are inconsistent between member states, so they are not reproduced.
|
|
293
|
+
*/
|
|
294
|
+
reducedRates: number[];
|
|
295
|
+
/** Rates for part of the territory only, e.g. 8.5 % in Martinique, Guadeloupe and Réunion. */
|
|
296
|
+
regionalRates: RegionalVatRate[];
|
|
297
|
+
/**
|
|
298
|
+
* The date TEDB says these rates apply from, as `YYYY-MM-DD`. Kept as a string: a date without
|
|
299
|
+
* a time zone turned into a `Date` can land on the previous day.
|
|
300
|
+
*/
|
|
301
|
+
situationOn?: string;
|
|
302
|
+
/** When VerifNow last retrieved them from TEDB. */
|
|
303
|
+
fetchedAt?: Date;
|
|
304
|
+
}
|
|
305
|
+
/** VAT rates of every EU member state. */
|
|
306
|
+
interface VatRates {
|
|
307
|
+
/** Always `TEDB`, the Commission's Taxes in Europe Database. */
|
|
308
|
+
source: string;
|
|
309
|
+
sourceUrl?: string;
|
|
310
|
+
/** One entry per member state retrieved so far — normally all 27. */
|
|
311
|
+
rates: CountryVatRates[];
|
|
312
|
+
}
|
|
255
313
|
/** Quota counters read from the `X-RateLimit-*` response headers. */
|
|
256
314
|
interface QuotaInfo {
|
|
257
315
|
/** Validations included in the current billing period. */
|
|
@@ -300,6 +358,11 @@ interface RequestOptions {
|
|
|
300
358
|
/** Cancel the call from your own controller. Combined with the timeout. */
|
|
301
359
|
signal?: AbortSignal;
|
|
302
360
|
}
|
|
361
|
+
/** Options for `validateVat`: the per-call options, plus the company expected to hold the number. */
|
|
362
|
+
interface VatValidationOptions extends RequestOptions {
|
|
363
|
+
/** The company you expect to hold this VAT number, e.g. from a supplier form. At most 200 characters. */
|
|
364
|
+
traderName?: string;
|
|
365
|
+
}
|
|
303
366
|
/**
|
|
304
367
|
* Client for the VerifNow validation API.
|
|
305
368
|
*
|
|
@@ -335,8 +398,15 @@ declare class VerifNow {
|
|
|
335
398
|
* account number that could never exist in that country.
|
|
336
399
|
*/
|
|
337
400
|
validateIban(value: string, options?: RequestOptions): Promise<ValidationResult>;
|
|
338
|
-
/**
|
|
339
|
-
|
|
401
|
+
/**
|
|
402
|
+
* Validate a VAT number.
|
|
403
|
+
*
|
|
404
|
+
* Pass `traderName` to ask whether the number belongs to that company:
|
|
405
|
+
* `vatDetails.traderNameMatch` answers `MATCH`, `MISMATCH` or `NOT_AVAILABLE`, and
|
|
406
|
+
* `traderNameMatchSource` says who compared — VerifNow against the name VIES publishes, or VIES
|
|
407
|
+
* itself where it withholds the name but checks one (Spain). Germany does neither.
|
|
408
|
+
*/
|
|
409
|
+
validateVat(value: string, options?: VatValidationOptions): Promise<ValidationResult>;
|
|
340
410
|
/**
|
|
341
411
|
* Validate a Canadian Social Insurance Number: format and Luhn check digit.
|
|
342
412
|
*
|
|
@@ -364,6 +434,20 @@ declare class VerifNow {
|
|
|
364
434
|
* The typed helpers above call this. Use it directly when the rule is chosen at runtime.
|
|
365
435
|
*/
|
|
366
436
|
validate(rule: ValidationRule, value: string, options?: RequestOptions): Promise<ValidationResult>;
|
|
437
|
+
/**
|
|
438
|
+
* EU VAT rates of every member state, from the European Commission's TEDB.
|
|
439
|
+
*
|
|
440
|
+
* Public reference data: the call spends no quota. These are the rates a member state has, not
|
|
441
|
+
* the rate a sale is charged — in B2B trade between member states the invoice is usually
|
|
442
|
+
* zero-rated under the reverse charge whatever the buyer's country rate is.
|
|
443
|
+
*/
|
|
444
|
+
vatRates(options?: RequestOptions): Promise<VatRates>;
|
|
445
|
+
/**
|
|
446
|
+
* One EU member state's VAT rates. Accepts `GR` for Greece as well as `EL`.
|
|
447
|
+
*
|
|
448
|
+
* A code outside the 27 member states throws {@link VerifNowRequestError} (HTTP 404).
|
|
449
|
+
*/
|
|
450
|
+
vatRate(countryCode: string, options?: RequestOptions): Promise<CountryVatRates>;
|
|
367
451
|
}
|
|
368
452
|
|
|
369
453
|
/**
|
|
@@ -448,6 +532,6 @@ declare class VerifNowResponseError extends VerifNowError {
|
|
|
448
532
|
*
|
|
449
533
|
* Kept in sync with `package.json` by a test — bump both together.
|
|
450
534
|
*/
|
|
451
|
-
declare const VERSION = "1.
|
|
535
|
+
declare const VERSION = "1.9.0";
|
|
452
536
|
|
|
453
|
-
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 };
|
|
537
|
+
export { type CountryVatRates, type Deliverability, type EmailDetails, type EmailSignals, type IbanDetails, type NasDetails, type NifDetails, type NifType, type PhoneDetails, type PhoneLineType, type QuotaInfo, type RegionalVatRate, type RequestOptions, type RetryOptions, type RiskLevel, type SsnDetails, type TraderNameMatch, type TraderNameMatchSource, VALIDATION_RULES, VERSION, type ValidationLevel, type ValidationResult, type ValidationRule, type VatDetails, type VatRates, type VatSource, type VatValidationOptions, 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.9.0";
|
|
45
45
|
|
|
46
46
|
// src/client.ts
|
|
47
47
|
var DEFAULT_BASE_URL = "https://api.verifnow.io";
|
|
@@ -99,9 +99,18 @@ var VerifNow = class {
|
|
|
99
99
|
validateIban(value, options) {
|
|
100
100
|
return this.validate("iban", value, options);
|
|
101
101
|
}
|
|
102
|
-
/**
|
|
103
|
-
|
|
104
|
-
|
|
102
|
+
/**
|
|
103
|
+
* Validate a VAT number.
|
|
104
|
+
*
|
|
105
|
+
* Pass `traderName` to ask whether the number belongs to that company:
|
|
106
|
+
* `vatDetails.traderNameMatch` answers `MATCH`, `MISMATCH` or `NOT_AVAILABLE`, and
|
|
107
|
+
* `traderNameMatchSource` says who compared — VerifNow against the name VIES publishes, or VIES
|
|
108
|
+
* itself where it withholds the name but checks one (Spain). Germany does neither.
|
|
109
|
+
*/
|
|
110
|
+
validateVat(value, options = {}) {
|
|
111
|
+
const { traderName, ...requestOptions } = options;
|
|
112
|
+
const extra = traderName && traderName.trim() !== "" ? { traderName } : void 0;
|
|
113
|
+
return this.#validate("vat", value, requestOptions, extra);
|
|
105
114
|
}
|
|
106
115
|
/**
|
|
107
116
|
* Validate a Canadian Social Insurance Number: format and Luhn check digit.
|
|
@@ -135,19 +144,55 @@ var VerifNow = class {
|
|
|
135
144
|
*
|
|
136
145
|
* The typed helpers above call this. Use it directly when the rule is chosen at runtime.
|
|
137
146
|
*/
|
|
138
|
-
|
|
147
|
+
validate(rule, value, options = {}) {
|
|
148
|
+
return this.#validate(rule, value, options);
|
|
149
|
+
}
|
|
150
|
+
async #validate(rule, value, options, extra) {
|
|
139
151
|
if (typeof value !== "string" || value.trim() === "") {
|
|
140
152
|
throw new VerifNowRequestError(
|
|
141
153
|
`Cannot validate an empty value for rule "${rule}".`
|
|
142
154
|
);
|
|
143
155
|
}
|
|
144
156
|
const url = `${this.#baseUrl}/api/v1/validate/${rule}`;
|
|
145
|
-
const body = JSON.stringify({ value });
|
|
157
|
+
const body = JSON.stringify({ value, ...extra });
|
|
158
|
+
return this.#withRetry(
|
|
159
|
+
() => this.#requestOnce("POST", url, body, options, (payload, quota) => mapResult(payload, quota))
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* EU VAT rates of every member state, from the European Commission's TEDB.
|
|
164
|
+
*
|
|
165
|
+
* Public reference data: the call spends no quota. These are the rates a member state has, not
|
|
166
|
+
* the rate a sale is charged — in B2B trade between member states the invoice is usually
|
|
167
|
+
* zero-rated under the reverse charge whatever the buyer's country rate is.
|
|
168
|
+
*/
|
|
169
|
+
async vatRates(options = {}) {
|
|
170
|
+
const url = `${this.#baseUrl}/api/v1/vat/rates`;
|
|
171
|
+
return this.#withRetry(
|
|
172
|
+
() => this.#requestOnce("GET", url, void 0, options, (payload) => mapVatRates(payload))
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* One EU member state's VAT rates. Accepts `GR` for Greece as well as `EL`.
|
|
177
|
+
*
|
|
178
|
+
* A code outside the 27 member states throws {@link VerifNowRequestError} (HTTP 404).
|
|
179
|
+
*/
|
|
180
|
+
async vatRate(countryCode, options = {}) {
|
|
181
|
+
if (typeof countryCode !== "string" || countryCode.trim() === "") {
|
|
182
|
+
throw new VerifNowRequestError('A member state code is required, e.g. "FR".');
|
|
183
|
+
}
|
|
184
|
+
const url = `${this.#baseUrl}/api/v1/vat/rates/${encodeURIComponent(countryCode.trim())}`;
|
|
185
|
+
return this.#withRetry(
|
|
186
|
+
() => this.#requestOnce("GET", url, void 0, options, (payload) => mapCountryVatRates(payload))
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
/** Runs one request under the retry policy. */
|
|
190
|
+
async #withRetry(attemptOnce) {
|
|
146
191
|
const maxAttempts = this.#retry ? this.#retry.attempts + 1 : 1;
|
|
147
192
|
let lastError;
|
|
148
193
|
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
149
194
|
try {
|
|
150
|
-
return await
|
|
195
|
+
return await attemptOnce();
|
|
151
196
|
} catch (error) {
|
|
152
197
|
if (!(error instanceof VerifNowError)) throw error;
|
|
153
198
|
lastError = error;
|
|
@@ -178,7 +223,7 @@ var VerifNow = class {
|
|
|
178
223
|
if (error instanceof VerifNowConnectionError) return backoff;
|
|
179
224
|
return null;
|
|
180
225
|
}
|
|
181
|
-
async #requestOnce(url, body, options) {
|
|
226
|
+
async #requestOnce(method, url, body, options, map) {
|
|
182
227
|
const timeoutMs = options.timeoutMs ?? this.#timeoutMs;
|
|
183
228
|
const controller = new AbortController();
|
|
184
229
|
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
@@ -187,10 +232,10 @@ var VerifNow = class {
|
|
|
187
232
|
let response;
|
|
188
233
|
try {
|
|
189
234
|
response = await this.#fetch(url, {
|
|
190
|
-
method
|
|
235
|
+
method,
|
|
191
236
|
headers: {
|
|
192
237
|
...this.#headers,
|
|
193
|
-
"Content-Type": "application/json",
|
|
238
|
+
...body === void 0 ? {} : { "Content-Type": "application/json" },
|
|
194
239
|
Accept: "application/json",
|
|
195
240
|
"X-API-KEY": this.#apiKey,
|
|
196
241
|
"X-VerifNow-SDK": `node/${VERSION}`
|
|
@@ -209,9 +254,9 @@ var VerifNow = class {
|
|
|
209
254
|
clearTimeout(timer);
|
|
210
255
|
options.signal?.removeEventListener("abort", abortFromCaller);
|
|
211
256
|
}
|
|
212
|
-
return this.#handleResponse(response);
|
|
257
|
+
return this.#handleResponse(response, map);
|
|
213
258
|
}
|
|
214
|
-
async #handleResponse(response) {
|
|
259
|
+
async #handleResponse(response, map) {
|
|
215
260
|
const requestId = response.headers.get("X-Request-Id") ?? void 0;
|
|
216
261
|
const quota = parseQuota(response.headers);
|
|
217
262
|
if (response.ok) {
|
|
@@ -230,7 +275,7 @@ var VerifNow = class {
|
|
|
230
275
|
{ status: response.status, requestId }
|
|
231
276
|
);
|
|
232
277
|
}
|
|
233
|
-
return
|
|
278
|
+
return map(payload, quota);
|
|
234
279
|
}
|
|
235
280
|
const message = await readErrorMessage(response);
|
|
236
281
|
const context = { status: response.status, requestId };
|
|
@@ -368,7 +413,9 @@ function mapVatDetails(raw) {
|
|
|
368
413
|
traderName: asString(d.trader_name),
|
|
369
414
|
traderAddress: asString(d.trader_address),
|
|
370
415
|
viesAvailable: asBoolean(d.vies_available),
|
|
371
|
-
consultationNumber: asString(d.consultation_number)
|
|
416
|
+
consultationNumber: asString(d.consultation_number),
|
|
417
|
+
traderNameMatch: asString(d.trader_name_match),
|
|
418
|
+
traderNameMatchSource: asString(d.trader_name_match_source)
|
|
372
419
|
};
|
|
373
420
|
}
|
|
374
421
|
function mapPhoneDetails(raw) {
|
|
@@ -421,6 +468,29 @@ function mapSsnDetails(raw) {
|
|
|
421
468
|
const d = raw;
|
|
422
469
|
return { itin: asBoolean(d.itin) };
|
|
423
470
|
}
|
|
471
|
+
function mapCountryVatRates(raw) {
|
|
472
|
+
const numbers = (value) => Array.isArray(value) ? value.filter((v) => asNumber(v) !== void 0) : [];
|
|
473
|
+
return {
|
|
474
|
+
countryCode: asString(raw.countryCode) ?? "",
|
|
475
|
+
standardRate: asNumber(raw.standardRate) ?? Number.NaN,
|
|
476
|
+
reducedRates: numbers(raw.reducedRates),
|
|
477
|
+
regionalRates: Array.isArray(raw.regionalRates) ? raw.regionalRates.filter((r) => r !== null && typeof r === "object").map((r) => ({
|
|
478
|
+
rate: asNumber(r.rate) ?? Number.NaN,
|
|
479
|
+
note: asString(r.note),
|
|
480
|
+
euVatArea: asBoolean(r.euVatArea)
|
|
481
|
+
})) : [],
|
|
482
|
+
situationOn: asString(raw.situationOn),
|
|
483
|
+
fetchedAt: asDate(raw.fetchedAt)
|
|
484
|
+
};
|
|
485
|
+
}
|
|
486
|
+
function mapVatRates(raw) {
|
|
487
|
+
const rates = Array.isArray(raw.rates) ? raw.rates.filter((r) => r !== null && typeof r === "object").map(mapCountryVatRates) : [];
|
|
488
|
+
return {
|
|
489
|
+
source: asString(raw.source) ?? "TEDB",
|
|
490
|
+
sourceUrl: asString(raw.sourceUrl),
|
|
491
|
+
rates
|
|
492
|
+
};
|
|
493
|
+
}
|
|
424
494
|
function mapResult(payload, quota) {
|
|
425
495
|
return {
|
|
426
496
|
valid: payload.valid === true,
|
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.7.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 sepa: asBoolean(d.sepa),\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 /**\n * The country is inside the SEPA schemes' geographical scope, so a bank there may collect a\n * SEPA direct debit. Whether this particular bank does is published per bank, not per country,\n * and cannot be read from an IBAN.\n */\n sepa?: boolean;\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"]}
|
|
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.9.0';\n","import {\n VerifNowAuthError,\n VerifNowConnectionError,\n VerifNowError,\n VerifNowRateLimitError,\n VerifNowRequestError,\n VerifNowResponseError,\n VerifNowServerError,\n} from './errors.js';\nimport type {\n CountryVatRates,\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 VatRates,\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/** Options for `validateVat`: the per-call options, plus the company expected to hold the number. */\nexport interface VatValidationOptions extends RequestOptions {\n /** The company you expect to hold this VAT number, e.g. from a supplier form. At most 200 characters. */\n traderName?: string;\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 /**\n * Validate a VAT number.\n *\n * Pass `traderName` to ask whether the number belongs to that company:\n * `vatDetails.traderNameMatch` answers `MATCH`, `MISMATCH` or `NOT_AVAILABLE`, and\n * `traderNameMatchSource` says who compared — VerifNow against the name VIES publishes, or VIES\n * itself where it withholds the name but checks one (Spain). Germany does neither.\n */\n validateVat(value: string, options: VatValidationOptions = {}): Promise<ValidationResult> {\n const { traderName, ...requestOptions } = options;\n const extra = traderName && traderName.trim() !== '' ? { traderName } : undefined;\n return this.#validate('vat', value, requestOptions, extra);\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 validate(\n rule: ValidationRule,\n value: string,\n options: RequestOptions = {},\n ): Promise<ValidationResult> {\n return this.#validate(rule, value, options);\n }\n\n async #validate(\n rule: ValidationRule,\n value: string,\n options: RequestOptions,\n extra?: Record<string, string>,\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, ...extra });\n return this.#withRetry(() =>\n this.#requestOnce('POST', url, body, options, (payload, quota) => mapResult(payload, quota)),\n );\n }\n\n /**\n * EU VAT rates of every member state, from the European Commission's TEDB.\n *\n * Public reference data: the call spends no quota. These are the rates a member state has, not\n * the rate a sale is charged — in B2B trade between member states the invoice is usually\n * zero-rated under the reverse charge whatever the buyer's country rate is.\n */\n async vatRates(options: RequestOptions = {}): Promise<VatRates> {\n const url = `${this.#baseUrl}/api/v1/vat/rates`;\n return this.#withRetry(() =>\n this.#requestOnce('GET', url, undefined, options, (payload) => mapVatRates(payload)),\n );\n }\n\n /**\n * One EU member state's VAT rates. Accepts `GR` for Greece as well as `EL`.\n *\n * A code outside the 27 member states throws {@link VerifNowRequestError} (HTTP 404).\n */\n async vatRate(countryCode: string, options: RequestOptions = {}): Promise<CountryVatRates> {\n if (typeof countryCode !== 'string' || countryCode.trim() === '') {\n throw new VerifNowRequestError('A member state code is required, e.g. \"FR\".');\n }\n const url = `${this.#baseUrl}/api/v1/vat/rates/${encodeURIComponent(countryCode.trim())}`;\n return this.#withRetry(() =>\n this.#requestOnce('GET', url, undefined, options, (payload) => mapCountryVatRates(payload)),\n );\n }\n\n /** Runs one request under the retry policy. */\n async #withRetry<T>(attemptOnce: () => Promise<T>): Promise<T> {\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 attemptOnce();\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<T>(\n method: 'GET' | 'POST',\n url: string,\n body: string | undefined,\n options: RequestOptions,\n map: (payload: Record<string, unknown>, quota: QuotaInfo | undefined) => T,\n ): Promise<T> {\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,\n headers: {\n ...this.#headers,\n ...(body === undefined ? {} : { '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, map);\n }\n\n async #handleResponse<T>(\n response: Response,\n map: (payload: Record<string, unknown>, quota: QuotaInfo | undefined) => T,\n ): Promise<T> {\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 map(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 traderNameMatch: asString(d.trader_name_match) as VatDetails['traderNameMatch'],\n traderNameMatchSource: asString(d.trader_name_match_source) as VatDetails['traderNameMatchSource'],\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 sepa: asBoolean(d.sepa),\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 mapCountryVatRates(raw: Record<string, unknown>): CountryVatRates {\n const numbers = (value: unknown): number[] =>\n Array.isArray(value) ? value.filter((v): v is number => asNumber(v) !== undefined) : [];\n\n return {\n countryCode: asString(raw.countryCode) ?? '',\n standardRate: asNumber(raw.standardRate) ?? Number.NaN,\n reducedRates: numbers(raw.reducedRates),\n regionalRates: Array.isArray(raw.regionalRates)\n ? raw.regionalRates\n .filter((r): r is Record<string, unknown> => r !== null && typeof r === 'object')\n .map((r) => ({\n rate: asNumber(r.rate) ?? Number.NaN,\n note: asString(r.note),\n euVatArea: asBoolean(r.euVatArea),\n }))\n : [],\n situationOn: asString(raw.situationOn),\n fetchedAt: asDate(raw.fetchedAt),\n };\n}\n\nfunction mapVatRates(raw: Record<string, unknown>): VatRates {\n const rates = Array.isArray(raw.rates)\n ? raw.rates\n .filter((r): r is Record<string, unknown> => r !== null && typeof r === 'object')\n .map(mapCountryVatRates)\n : [];\n return {\n source: asString(raw.source) ?? 'TEDB',\n sourceUrl: asString(raw.sourceUrl),\n rates,\n };\n}\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 */\n/** Whether a supplied company name belongs to a VAT number's registered holder. */\nexport type TraderNameMatch = 'MATCH' | 'MISMATCH' | 'NOT_AVAILABLE';\n\n/** Who compared the names. */\nexport type TraderNameMatchSource = 'VIES' | 'VERIFNOW';\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 * Present when a `traderName` was sent: whether it belongs to the registered holder. `MISMATCH`\n * is a question for a human, not proof of fraud — trading names and group companies differ.\n */\n traderNameMatch?: TraderNameMatch;\n /** Who compared: `VERIFNOW`, against the name VIES published, or `VIES` itself (Spain). */\n traderNameMatchSource?: TraderNameMatchSource;\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 /**\n * The country is inside the SEPA schemes' geographical scope, so a bank there may collect a\n * SEPA direct debit. Whether this particular bank does is published per bank, not per country,\n * and cannot be read from an IBAN.\n */\n sepa?: boolean;\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/** A VAT rate applying to part of a member state only — an overseas department, an island. */\nexport interface RegionalVatRate {\n rate: number;\n /** Where it applies, in the words of the Commission's TEDB. */\n note?: string;\n /**\n * `false` for the Canary Islands and the French overseas territories, which the VAT Directive\n * excludes (Article 6(1)): goods shipped there from another member state are an export, not a\n * distance sale at this rate.\n */\n euVatArea?: boolean;\n}\n\n/**\n * One EU member state's VAT rates, from the European Commission's TEDB.\n *\n * These are the rates the member state has, not the rate a sale is charged: which one applies\n * depends on who sells to whom and what. In B2B trade between member states the invoice is usually\n * zero-rated under the reverse charge, whatever the buyer's country rate is.\n */\nexport interface CountryVatRates {\n /** Member state as TEDB and VIES name it: Greece is `EL`. */\n countryCode: string;\n /** The national standard rate, e.g. `20` for France. */\n standardRate: number;\n /**\n * Every reduced, super-reduced and parking rate on the mainland territory, ascending.\n * TEDB's own sub-labels are inconsistent between member states, so they are not reproduced.\n */\n reducedRates: number[];\n /** Rates for part of the territory only, e.g. 8.5 % in Martinique, Guadeloupe and Réunion. */\n regionalRates: RegionalVatRate[];\n /**\n * The date TEDB says these rates apply from, as `YYYY-MM-DD`. Kept as a string: a date without\n * a time zone turned into a `Date` can land on the previous day.\n */\n situationOn?: string;\n /** When VerifNow last retrieved them from TEDB. */\n fetchedAt?: Date;\n}\n\n/** VAT rates of every EU member state. */\nexport interface VatRates {\n /** Always `TEDB`, the Commission's Taxes in Europe Database. */\n source: string;\n sourceUrl?: string;\n /** One entry per member state retrieved so far — normally all 27. */\n rates: CountryVatRates[];\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