Files
vat-api.eu/server/utils/vatRates.ts
2026-02-13 22:02:30 +01:00

259 lines
5.3 KiB
TypeScript

export interface VatRateResponse {
country: string;
country_code: string;
standard_rate: number;
reduced_rates: number[];
currency: string;
}
const FALLBACK_RATES: VatRateResponse[] = [
{
country: "Austria",
country_code: "AT",
standard_rate: 20,
reduced_rates: [10, 13],
currency: "EUR",
},
{
country: "Belgium",
country_code: "BE",
standard_rate: 21,
reduced_rates: [6, 12],
currency: "EUR",
},
{
country: "Bulgaria",
country_code: "BG",
standard_rate: 20,
reduced_rates: [9],
currency: "EUR",
},
{
country: "Croatia",
country_code: "HR",
standard_rate: 25,
reduced_rates: [5, 13],
currency: "EUR",
},
{
country: "Cyprus",
country_code: "CY",
standard_rate: 19,
reduced_rates: [5, 9],
currency: "EUR",
},
{
country: "Czech Republic",
country_code: "CZ",
standard_rate: 21,
reduced_rates: [12, 15],
currency: "EUR",
},
{
country: "Denmark",
country_code: "DK",
standard_rate: 25,
reduced_rates: [],
currency: "EUR",
},
{
country: "Estonia",
country_code: "EE",
standard_rate: 22,
reduced_rates: [9],
currency: "EUR",
},
{
country: "Finland",
country_code: "FI",
standard_rate: 25.5,
reduced_rates: [10, 14],
currency: "EUR",
},
{
country: "France",
country_code: "FR",
standard_rate: 20,
reduced_rates: [5.5, 10],
currency: "EUR",
},
{
country: "Germany",
country_code: "DE",
standard_rate: 19,
reduced_rates: [7],
currency: "EUR",
},
{
country: "Greece",
country_code: "GR",
standard_rate: 24,
reduced_rates: [6, 13],
currency: "EUR",
},
{
country: "Hungary",
country_code: "HU",
standard_rate: 27,
reduced_rates: [5, 18],
currency: "EUR",
},
{
country: "Ireland",
country_code: "IE",
standard_rate: 23,
reduced_rates: [9, 13.5],
currency: "EUR",
},
{
country: "Italy",
country_code: "IT",
standard_rate: 22,
reduced_rates: [5, 10],
currency: "EUR",
},
{
country: "Latvia",
country_code: "LV",
standard_rate: 21,
reduced_rates: [5, 12],
currency: "EUR",
},
{
country: "Lithuania",
country_code: "LT",
standard_rate: 21,
reduced_rates: [5, 9],
currency: "EUR",
},
{
country: "Luxembourg",
country_code: "LU",
standard_rate: 17,
reduced_rates: [8],
currency: "EUR",
},
{
country: "Malta",
country_code: "MT",
standard_rate: 18,
reduced_rates: [5, 7],
currency: "EUR",
},
{
country: "Netherlands",
country_code: "NL",
standard_rate: 21,
reduced_rates: [9],
currency: "EUR",
},
{
country: "Poland",
country_code: "PL",
standard_rate: 23,
reduced_rates: [5, 8],
currency: "EUR",
},
{
country: "Portugal",
country_code: "PT",
standard_rate: 23,
reduced_rates: [6, 13],
currency: "EUR",
},
{
country: "Romania",
country_code: "RO",
standard_rate: 19,
reduced_rates: [5, 9],
currency: "EUR",
},
{
country: "Slovakia",
country_code: "SK",
standard_rate: 23,
reduced_rates: [5, 10],
currency: "EUR",
},
{
country: "Slovenia",
country_code: "SI",
standard_rate: 22,
reduced_rates: [5, 9.5],
currency: "EUR",
},
{
country: "Spain",
country_code: "ES",
standard_rate: 21,
reduced_rates: [4, 10],
currency: "EUR",
},
{
country: "Sweden",
country_code: "SE",
standard_rate: 25,
reduced_rates: [6, 12],
currency: "EUR",
},
];
const TWENTY_FOUR_HOURS = 24 * 60 * 60 * 1000;
let cache: { data: VatRateResponse[]; date: string; fetchedAt: number } | null =
null;
function todayDateString(): string {
return new Date().toISOString().slice(0, 10);
}
function isCacheValid(): boolean {
if (!cache) return false;
const age = Date.now() - cache.fetchedAt;
return age < TWENTY_FOUR_HOURS && cache.date === todayDateString();
}
function transformVatstackResponse(vatstackRates: any[]): VatRateResponse[] {
return vatstackRates.map((r: any) => ({
country: r.country_name ?? "",
country_code: r.country_code ?? "",
standard_rate: r.standard_rate ?? 0,
reduced_rates: r.reduced_rates ?? [],
currency: "EUR",
}));
}
export async function getAllRates(): Promise<VatRateResponse[]> {
if (isCacheValid()) return cache!.data;
const apiKey = "pk_live_3038ce089ee7197879fcf95e624da019";
try {
const response: any = await $fetch("https://api.vatstack.com/v1/rates", {
headers: { "X-API-Key": apiKey },
query: { member_state: true, limit: 100 },
});
const items: any[] = response.rates ?? response;
const euOnly = items.filter((r: any) => r.member_state === true);
const rates = transformVatstackResponse(euOnly);
cache = { data: rates, date: todayDateString(), fetchedAt: Date.now() };
return rates;
} catch {
// Fall back to hardcoded rates on fetch failure
cache = {
data: FALLBACK_RATES,
date: todayDateString(),
fetchedAt: Date.now(),
};
return FALLBACK_RATES;
}
}
export async function getRateByCode(
code: string,
): Promise<VatRateResponse | undefined> {
const rates = await getAllRates();
return rates.find((r) => r.country_code === code.toUpperCase());
}