UNPKG

evatr-api

Version:

Checks a VAT-ID using the eVatR REST-API of the German Federal Central Tax Office (Bundeszentralamt für Steuern, BZSt)

290 lines 10.4 kB
"use strict"; /** * Main client class for the eVatR API */ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.EvatrClient = void 0; const axios_1 = __importDefault(require("axios")); const constants_1 = require("./constants"); const status_loader_1 = require("./status-loader"); /** * EvatrClient - Main client for interacting with the eVatR API */ class EvatrClient { constructor(config = {}) { const { timeout = 30000, headers = {} } = config; this.httpClient = axios_1.default.create({ timeout, headers: { 'Content-Type': 'application/json', Accept: 'application/json', ...headers, }, }); // Add response interceptor for error handling this.httpClient.interceptors.response.use((response) => response, (error) => { const evatrError = new Error(error.response?.data?.message || error.message || 'Unknown error'); evatrError.name = 'EvatrApiError'; evatrError.http = error.response?.status; evatrError.status = error.response?.data?.status; evatrError.field = error.response?.data?.field; throw evatrError; }); } async validateSimple(request, extended) { const payload = { vatIdOwn: request.vatIdOwn, vatIdForeign: request.vatIdForeign, includeRaw: request.includeRaw, }; return this.performValidation(payload, extended); } async validateQualified(request, extended) { const payload = { vatIdOwn: request.vatIdOwn, vatIdForeign: request.vatIdForeign, company: request.company, location: request.location, street: request.street, zip: request.zip, includeRaw: request.includeRaw, }; return this.performValidation(payload, extended); } async validate(request, extended) { return this.performValidation(request, extended); } /** * Get status messages from the API * @returns Promise<StatusMessage[]> */ async getStatusMessages() { try { const response = await this.httpClient.get(constants_1.ENDPOINTS.STATUS_MESSAGES); return response.data.map((apiMsg) => ({ status: apiMsg.status, category: apiMsg.kategorie === 'Ergebnis' ? 'Result' : apiMsg.kategorie === 'Fehler' ? 'Error' : apiMsg.kategorie === 'Hinweis' ? 'Hint' : undefined, http: apiMsg.httpcode, message: apiMsg.meldung, field: apiMsg.feld, })); } catch (error) { throw this.handleError(error); } } /** * Get availability of VIES per EU member state. * Returns a map keyed by ISO alpha-2 country code (e.g., "DE") with boolean availability. */ async getAvailability() { try { const response = await this.httpClient.get(constants_1.ENDPOINTS.EU_MEMBER_STATES); const availability = {}; for (const apiState of response.data) { availability[apiState.alpha2] = apiState.verfuegbar; } return availability; } catch (error) { throw this.handleError(error); } } /** * Get status message by status code * @param statusCode Status code (e.g., "evatr-0000") * @returns StatusMessage or undefined if not found */ getStatusMessage(statusCode) { return status_loader_1.StatusMessages.getStatusMessage(statusCode); } /** * Check if a status code indicates success * @param statusCode Status code to check * @returns boolean */ isSuccessStatus(statusCode) { return status_loader_1.StatusMessages.isSuccessStatus(statusCode); } /** * Check if a status code indicates an error * @param statusCode Status code to check * @returns boolean */ isErrorStatus(statusCode) { return status_loader_1.StatusMessages.isErrorStatus(statusCode); } /** * Check if a status code indicates a warning/hint * @param statusCode Status code to check * @returns boolean */ isWarningStatus(statusCode) { return status_loader_1.StatusMessages.isWarningStatus(statusCode); } /** * Validate VAT-ID format (basic syntax check) * @param vatId VAT-ID to validate * @returns boolean */ static checkVatIdSyntax(vatId) { if (!vatId || typeof vatId !== 'string') { return false; } const cleanVatId = this.normalizeVatId(vatId); // Must start with exactly 2 letters (country code) if (!/^[A-Z]{2}/.test(cleanVatId)) { return false; } const countryCode = cleanVatId.substring(0, 2); const pattern = constants_1.VATID_PATTERNS[countryCode]; if (!pattern) { return false; } return pattern.test(cleanVatId); } /** * Extract country code from VAT-ID * @param vatId VAT-ID * @returns string Country code */ static getCountryCode(vatId) { return this.normalizeVatId(vatId).substring(0, 2); } /** * Format VAT-ID by removing spaces and converting to uppercase * @param vatId VAT-ID to format * @returns string Formatted VAT-ID */ static normalizeVatId(vatId) { return vatId.replace(/[^a-zA-Z0-9]/g, '').toUpperCase(); } /** * Maps parameters to API property names */ mapToApiParams(request) { return { anfragendeUstid: request.vatIdOwn, angefragteUstid: request.vatIdForeign, firmenname: request.company, ort: request.location, strasse: request.street, plz: request.zip, }; } /** * Maps API response to parameters */ mapFromApiResponse(response, vatIdOwn, vatIdForeign) { return { id: response.id, timestamp: response.anfrageZeitpunkt, status: response.status, vatIdOwn, vatIdForeign, validFrom: response.gueltigAb, validTill: response.gueltigBis, company: response.ergFirmenname, street: response.ergStrasse, zip: response.ergPlz, location: response.ergOrt, }; } /** * Maps basic Response to ExtendedResponse with date objects and additional info */ mapToExtendedResponse(response) { const statusMessage = this.getStatusMessage(response.status); return { id: response.id, timestamp: { original: response.timestamp, date: new Date(response.timestamp), }, valid: this.isSuccessStatus(response.status), status: response.status, message: statusMessage?.message || undefined, vatIdOwn: response.vatIdOwn, vatIdForeign: response.vatIdForeign, validFrom: response.validFrom ? new Date(response.validFrom) : undefined, validTill: response.validTill ? new Date(response.validTill) : undefined, company: response.company, street: response.street, zip: response.zip, location: response.location, raw: response.raw, }; } /** * Internal method to perform the actual validation request */ async performValidation(request, extended) { try { // Validate input if (!request.vatIdOwn || !request.vatIdForeign) { throw new Error('Both vatIdOwn and vatIdForeign are required'); } // Format VAT-IDs const formattedRequest = { ...request, vatIdOwn: EvatrClient.normalizeVatId(request.vatIdOwn), vatIdForeign: EvatrClient.normalizeVatId(request.vatIdForeign), }; // Validate VAT-ID formats if (!EvatrClient.checkVatIdSyntax(formattedRequest.vatIdOwn)) { throw new Error(`Invalid format for vatIdOwn: ${formattedRequest.vatIdOwn}`); } if (!EvatrClient.checkVatIdSyntax(formattedRequest.vatIdForeign)) { throw new Error(`Invalid format for vatIdForeign: ${formattedRequest.vatIdForeign}`); } // Map to API parameters for the actual request const apiPayload = this.mapToApiParams(formattedRequest); const response = await this.httpClient.post(constants_1.ENDPOINTS.VALIDATION, apiPayload); // Map API response to English property names const basicResponse = this.mapFromApiResponse(response.data, formattedRequest.vatIdOwn, formattedRequest.vatIdForeign); // Include raw response data if requested if (request.includeRaw === true) { const rawData = { headers: response.headers, data: response.data, }; basicResponse.raw = JSON.stringify(rawData); } if (extended) { return this.mapToExtendedResponse(basicResponse); } return basicResponse; } catch (error) { throw this.handleError(error); } } /** * Handle and transform errors */ // eslint-disable-next-line @typescript-eslint/no-explicit-any handleError(error) { if (error.name === 'EvatrApiError') { return error; } const evatrError = new Error(error.message || 'Unknown error occurred'); evatrError.name = 'EvatrApiError'; if (error.response) { evatrError.http = error.response.status; evatrError.status = error.response.data?.status; evatrError.field = error.response.data?.field; } return evatrError; } } exports.EvatrClient = EvatrClient; //# sourceMappingURL=client.js.map