Validar números de IVA de la UE
Verifica números de IVA europeos en el checkout para habilitar exenciones fiscales B2B y prevenir fraude usando la API de validación de IVA de Veille.
Descripción general
Las empresas de la UE están exentas de IVA al comprar a otras empresas de la UE — pero solo con un número de IVA válido. Esta guía muestra cómo validar un número de IVA en el checkout y recuperar los datos de la empresa asociada (nombre y dirección) de la base de datos VIES.
Prerrequisitos
- Una clave de API de Veille — consigue una en app.veille.io
- Instala las dependencias para tu lenguaje:
pip install requestsSin dependencias adicionales — usa la API nativa fetch (Node 18+).
Extensión curl habilitada (activa por defecto).
Sin dependencias adicionales — usa net/http (Go 1.18+).
Sin dependencias adicionales — usa java.net.http (Java 11+).
Sin dependencias adicionales — usa System.Net.Http (.NET 6+).
# Cargo.toml
[dependencies]
reqwest = { version = "0.12", features = ["json"] }
tokio = { version = "1", features = ["full"] }
serde_json = "1"Pasos
Valida un número de IVA
Llama a GET /v1/vat con el parámetro vat. El número debe incluir el prefijo del país (p. ej. ES12345678A).
import requests
API_KEY = "YOUR_API_KEY"
response = requests.get(
"https://api.veille.io/v1/vat",
headers={"x-api-key": API_KEY},
params={"vat": "ES12345678A"},
)
result = response.json()
print(result)const API_KEY = "YOUR_API_KEY";
const params = new URLSearchParams({ vat: "ES12345678A" });
const response = await fetch(`https://api.veille.io/v1/vat?${params}`, {
headers: { "x-api-key": API_KEY },
});
const result = await response.json();
console.log(result);<?php
$apiKey = "YOUR_API_KEY";
$params = http_build_query(["vat" => "ES12345678A"]);
$ch = curl_init("https://api.veille.io/v1/vat?{$params}");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, ["x-api-key: {$apiKey}"]);
$result = json_decode(curl_exec($ch), true);
curl_close($ch);
print_r($result);package main
import (
"encoding/json"
"fmt"
"io"
"net/http"
)
func main() {
req, _ := http.NewRequest("GET", "https://api.veille.io/v1/vat?vat=ES12345678A", nil)
req.Header.Set("x-api-key", "YOUR_API_KEY")
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
var result map[string]any
json.Unmarshal(body, &result)
fmt.Println(result)
}import java.net.URI;
import java.net.http.*;
var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder()
.uri(URI.create("https://api.veille.io/v1/vat?vat=ES12345678A"))
.header("x-api-key", "YOUR_API_KEY")
.GET().build();
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());using System.Net.Http;
using var client = new HttpClient();
client.DefaultRequestHeaders.Add("x-api-key", "YOUR_API_KEY");
var body = await client.GetStringAsync("https://api.veille.io/v1/vat?vat=ES12345678A");
Console.WriteLine(body);#[tokio::main]
async fn main() -> Result<(), reqwest::Error> {
let result = reqwest::Client::new()
.get("https://api.veille.io/v1/vat")
.header("x-api-key", "YOUR_API_KEY")
.query(&[("vat", "ES12345678A")])
.send().await?.json::<serde_json::Value>().await?;
println!("{:#?}", result);
Ok(())
}Lee los datos de la empresa
Un número válido devuelve is_valid: true, company_name, company_address y country_code.
if result.get("is_valid"):
print(f"✅ Número de IVA válido")
print(f" Empresa : {result.get('company_name')}")
print(f" Dirección: {result.get('company_address')}")
print(f" País : {result.get('country_code')}")
else:
print(f"❌ Número de IVA inválido: {result.get('error', 'No encontrado en VIES')}")if (result.is_valid) {
console.log("✅ Número de IVA válido");
console.log(` Empresa : ${result.company_name}`);
console.log(` Dirección: ${result.company_address}`);
console.log(` País : ${result.country_code}`);
} else {
console.log(`❌ Número de IVA inválido: ${result.error ?? "No encontrado en VIES"}`);
}if ($result["is_valid"] ?? false) {
echo "✅ Número de IVA válido\n";
echo " Empresa : {$result['company_name']}\n";
echo " Dirección: {$result['company_address']}\n";
echo " País : {$result['country_code']}\n";
} else {
echo "❌ Número de IVA inválido: " . ($result["error"] ?? "No encontrado en VIES") . "\n";
}if result["is_valid"].(bool) {
fmt.Printf("✅ Número de IVA válido\n Empresa : %v\n Dirección: %v\n País : %v\n",
result["company_name"], result["company_address"], result["country_code"])
} else {
errMsg := "No encontrado en VIES"
if e, ok := result["error"].(string); ok { errMsg = e }
fmt.Println("❌ Número de IVA inválido:", errMsg)
}import org.json.*;
var r = new JSONObject(response.body());
if (r.getBoolean("is_valid")) {
System.out.printf("✅ Número de IVA válido%n Empresa : %s%n Dirección: %s%n País : %s%n",
r.getString("company_name"), r.getString("company_address"), r.getString("country_code"));
} else {
System.out.println("❌ Número de IVA inválido: " + r.optString("error", "No encontrado en VIES"));
}using System.Text.Json;
var r = JsonDocument.Parse(body).RootElement;
if (r.GetProperty("is_valid").GetBoolean())
{
Console.WriteLine("✅ Número de IVA válido");
Console.WriteLine($" Empresa : {r.GetProperty("company_name")}");
Console.WriteLine($" Dirección: {r.GetProperty("company_address")}");
Console.WriteLine($" País : {r.GetProperty("country_code")}");
}
else
{
var error = r.TryGetProperty("error", out var e) ? e.GetString() : "No encontrado en VIES";
Console.WriteLine($"❌ Número de IVA inválido: {error}");
}if result["is_valid"].as_bool().unwrap_or(false) {
println!("✅ Número de IVA válido");
println!(" Empresa : {}", result["company_name"].as_str().unwrap_or(""));
println!(" Dirección: {}", result["company_address"].as_str().unwrap_or(""));
println!(" País : {}", result["country_code"].as_str().unwrap_or(""));
} else {
let err = result["error"].as_str().unwrap_or("No encontrado en VIES");
println!("❌ Número de IVA inválido: {}", err);
}Crea un handler de checkout B2B con IVA
Elimina el IVA del total del pedido cuando se proporciona un número de IVA de empresa de la UE verificado.
import requests
API_KEY = "YOUR_API_KEY"
IVA_RATE = 0.21 # 21% IVA (España)
def lookup_vat(number: str) -> dict:
r = requests.get("https://api.veille.io/v1/vat",
headers={"x-api-key": API_KEY}, params={"vat": number})
return r.json()
def calculate_checkout(subtotal: float, vat_number: str | None = None) -> dict:
iva_amount = subtotal * IVA_RATE
vat_info = None
if vat_number:
result = lookup_vat(vat_number)
if result.get("is_valid"):
iva_amount = 0 # Inversión del sujeto pasivo B2B — sin IVA
vat_info = {"empresa": result["company_name"], "país": result["country_code"]}
else:
return {"error": "El número de IVA proporcionado no es válido."}
total = subtotal + iva_amount
return {"subtotal": subtotal, "iva": iva_amount, "total": total, "empresa": vat_info}
print(calculate_checkout(100.00))
print(calculate_checkout(100.00, vat_number="ES12345678A"))
print(calculate_checkout(100.00, vat_number="INVALIDO123"))const API_KEY = "YOUR_API_KEY";
const IVA_RATE = 0.21;
async function lookupVat(number: string) {
const params = new URLSearchParams({ vat: number });
const res = await fetch(`https://api.veille.io/v1/vat?${params}`, {
headers: { "x-api-key": API_KEY },
});
return res.json();
}
async function calculateCheckout(subtotal: number, vatNumber?: string) {
let ivaAmount = subtotal * IVA_RATE;
let companyInfo: any = null;
if (vatNumber) {
const result = await lookupVat(vatNumber);
if (result.is_valid) {
ivaAmount = 0;
companyInfo = { empresa: result.company_name, pais: result.country_code };
} else {
return { error: "El número de IVA proporcionado no es válido." };
}
}
return { subtotal, iva: ivaAmount, total: subtotal + ivaAmount, empresa: companyInfo };
}
console.log(await calculateCheckout(100));
console.log(await calculateCheckout(100, "ES12345678A"));
console.log(await calculateCheckout(100, "INVALIDO123"));<?php
const IVA_RATE = 0.21;
function lookupVat(string $apiKey, string $number): array {
$params = http_build_query(["vat" => $number]);
$ch = curl_init("https://api.veille.io/v1/vat?{$params}");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, ["x-api-key: {$apiKey}"]);
$result = json_decode(curl_exec($ch), true);
curl_close($ch);
return $result;
}
function calculateCheckout(string $apiKey, float $subtotal, ?string $vatNumber = null): array {
$ivaAmount = $subtotal * IVA_RATE;
$empresa = null;
if ($vatNumber) {
$result = lookupVat($apiKey, $vatNumber);
if ($result["is_valid"] ?? false) {
$ivaAmount = 0;
$empresa = ["empresa" => $result["company_name"], "pais" => $result["country_code"]];
} else {
return ["error" => "Número de IVA inválido."];
}
}
return ["subtotal" => $subtotal, "iva" => $ivaAmount, "total" => $subtotal + $ivaAmount, "empresa" => $empresa];
}
print_r(calculateCheckout("YOUR_API_KEY", 100.0));
print_r(calculateCheckout("YOUR_API_KEY", 100.0, "ES12345678A"));package main
import (
"encoding/json"
"fmt"
"io"
"net/http"
)
const APIKey = "YOUR_API_KEY"
const IVARate = 0.21
func lookupVAT(number string) map[string]any {
req, _ := http.NewRequest("GET", "https://api.veille.io/v1/vat?vat="+number, nil)
req.Header.Set("x-api-key", APIKey)
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
var r map[string]any
json.Unmarshal(body, &r)
return r
}
func calculateCheckout(subtotal float64, vatNumber string) map[string]any {
ivaAmount := subtotal * IVARate
var empresa map[string]any
if vatNumber != "" {
result := lookupVAT(vatNumber)
if result["is_valid"].(bool) {
ivaAmount = 0
empresa = map[string]any{"empresa": result["company_name"], "pais": result["country_code"]}
} else {
return map[string]any{"error": "Número de IVA inválido."}
}
}
return map[string]any{"subtotal": subtotal, "iva": ivaAmount, "total": subtotal + ivaAmount, "empresa": empresa}
}
func main() {
fmt.Println(calculateCheckout(100, ""))
fmt.Println(calculateCheckout(100, "ES12345678A"))
fmt.Println(calculateCheckout(100, "INVALIDO123"))
}import java.net.URI;
import java.net.http.*;
import org.json.*;
public class Main {
static HttpClient client = HttpClient.newHttpClient();
static String API_KEY = "YOUR_API_KEY";
static double IVA_RATE = 0.21;
static JSONObject lookupVat(String number) throws Exception {
var req = HttpRequest.newBuilder()
.uri(URI.create("https://api.veille.io/v1/vat?vat=" + number))
.header("x-api-key", API_KEY).GET().build();
var resp = client.send(req, HttpResponse.BodyHandlers.ofString());
return new JSONObject(resp.body());
}
static JSONObject calculateCheckout(double subtotal, String vatNumber) throws Exception {
double ivaAmount = subtotal * IVA_RATE;
JSONObject empresa = null;
if (vatNumber != null && !vatNumber.isEmpty()) {
var result = lookupVat(vatNumber);
if (result.getBoolean("is_valid")) {
ivaAmount = 0;
empresa = new JSONObject().put("empresa", result.getString("company_name"))
.put("pais", result.getString("country_code"));
} else {
return new JSONObject().put("error", "Número de IVA inválido.");
}
}
return new JSONObject().put("subtotal", subtotal).put("iva", ivaAmount)
.put("total", subtotal + ivaAmount).put("empresa", empresa);
}
public static void main(String[] args) throws Exception {
System.out.println(calculateCheckout(100, null));
System.out.println(calculateCheckout(100, "ES12345678A"));
System.out.println(calculateCheckout(100, "INVALIDO123"));
}
}using System.Net.Http;
using System.Text.Json;
const double IVA_RATE = 0.21;
var apiKey = "YOUR_API_KEY";
using var client = new HttpClient();
client.DefaultRequestHeaders.Add("x-api-key", apiKey);
async Task<JsonElement> LookupVat(string number)
{
var body = await client.GetStringAsync($"https://api.veille.io/v1/vat?vat={number}");
return JsonDocument.Parse(body).RootElement;
}
async Task<string> CalculateCheckout(double subtotal, string? vatNumber = null)
{
double ivaAmount = subtotal * IVA_RATE;
string? empresa = null;
if (vatNumber != null)
{
var result = await LookupVat(vatNumber);
if (result.GetProperty("is_valid").GetBoolean())
{ ivaAmount = 0; empresa = result.GetProperty("company_name").GetString(); }
else return """{"error":"Número de IVA inválido."}""";
}
return $"""{{ "subtotal": {subtotal}, "iva": {ivaAmount}, "total": {subtotal + ivaAmount}, "empresa": "{empresa}" }}""";
}
Console.WriteLine(await CalculateCheckout(100));
Console.WriteLine(await CalculateCheckout(100, "ES12345678A"));
Console.WriteLine(await CalculateCheckout(100, "INVALIDO123"));use reqwest::Client;
use serde_json::{json, Value};
const IVA_RATE: f64 = 0.21;
const API_KEY: &str = "YOUR_API_KEY";
async fn lookup_vat(client: &Client, number: &str) -> Value {
client.get("https://api.veille.io/v1/vat")
.header("x-api-key", API_KEY)
.query(&[("vat", number)])
.send().await.unwrap().json::<Value>().await.unwrap()
}
async fn calculate_checkout(client: &Client, subtotal: f64, vat_number: Option<&str>) -> Value {
let mut iva_amount = subtotal * IVA_RATE;
let mut empresa = json!(null);
if let Some(number) = vat_number {
let result = lookup_vat(client, number).await;
if result["is_valid"].as_bool().unwrap_or(false) {
iva_amount = 0.0;
empresa = json!({"empresa": result["company_name"], "pais": result["country_code"]});
} else {
return json!({ "error": "Número de IVA inválido." });
}
}
json!({ "subtotal": subtotal, "iva": iva_amount, "total": subtotal + iva_amount, "empresa": empresa })
}
#[tokio::main]
async fn main() -> Result<(), reqwest::Error> {
let client = Client::new();
println!("{}", calculate_checkout(&client, 100.0, None).await);
println!("{}", calculate_checkout(&client, 100.0, Some("ES12345678A")).await);
println!("{}", calculate_checkout(&client, 100.0, Some("INVALIDO123")).await);
Ok(())
}Siempre almacena el nombre y la dirección de la empresa validada junto al registro del pedido. Las autoridades fiscales de la UE exigen prueba de que el mecanismo de inversión del sujeto pasivo fue correctamente aplicado.