开发文档
所有文档
- 计费记录设计文档
- 验证和错误
- Web 服务描述文档(v1.0.3)
(1.673 KB) - Web 服务描述文档(v1.0.3,英文版)
(1,715 KB) - Web 服务的 WSDL
- Web 服务示意图
- 生成记录指纹或哈希的技术规范详情(v0.1.2)
(1.11 MB) - 发票二维码技术规格说明文档(v0.4.7)
(1.9 MB) - 生成账单记录电子签名的技术规范
- 生成账单记录电子签名的技术规范(v0.1.5)
(1.46 MB) 这个暂不开发 NO VERIFACTU - ZIP文件:包含账单记录签名样本的附件
(5.74 KB)
- 生成账单记录电子签名的技术规范(v0.1.5)
- 无法核实的计费记录验证服务说明文档(v0.2)
(576 KB) 这个暂不开发 NO VERIFACTU - 开发公司常见问题解答(更新于 2025 年 9 月 19 日)
(666 KB) - 负责任声明示例
(708 KB)
测试环境(带证书)
发送票务地址:
https://prewww10.aeat.es/wlpl/TIKE-CONT/ws/SistemaFacturacion/VerifactuSOAP
生产环境 (带证书)
发送票务地址:
https://www10.agenciatributaria.gob.es/wlpl/TIKE-CONT/ws/SistemaFacturacion/VerifactuSOAP
常用节点意思介绍
| 节点 | 含义 | 可用值 / 格式 | 说明 |
|---|---|---|---|
<sum1:Subsanacion> |
修正标志 | S 或 省略 |
S 表示此记录为修正(例如纠正错误或重新提交) |
<sum1:TipoFactura> |
发票类型 | F1 普通发票F2 简易发票R1 作废R2 更正R3 替换 |
可以参见 TipoFactura 段落 |
<sum1:ClaveRegimen> |
税制代码 | 01 普通制度02 简易制度03 免税 |
取决于发票适用税法 |
<sum1:CalificacionOperacion> |
交易类型 | S1 受IVA约束N1 不受约束E1 免税 |
常见餐饮类一般使用 S1 |
<sum1:TipoImpositivo> |
税率 | 4, 10, 21 , 0 几种税率 | 西班牙标准IVA税率 |
<sum1:TipoHuella> |
指纹类型 | 01 |
表示 SHA-256 Base64 |
<sum1:TipoUsoPosibleSoloVerifactu> |
是否专属VeriFactu | S 或 N |
如果你的系统只用于VeriFactu,则为 S |
<sum1:TipoUsoPosibleMultiOT> |
是否支持多义务人 | S 或 N |
SaaS类一般 S |
<sum1:IndicadorMultiplesOT> |
是否有多个义务人使用中 | S 或 N |
如果服务器下多家餐厅,则为 S |
<sum1:NumeroInstalacion> |
安装编号 | 数字或字符串 | 每个部署/门店唯一 |
<sum1:Huella> |
指纹 | Base64 编码字符串 | SHA-256(关键字段串联) |
TipoFactura 的几种类型
| Código | 名称 | 适用情形 | 关键点 |
|---|---|---|---|
| F1 | Factura completa (Art. 6, 7.2 y 7.3 del RD 1619/2012) |
标准完整发票,识别买方、含增值税细节。 | 是最常见的发票类型。必须包含: - 客户身份(NIF / NombreRazon) - 分项税额 (Desglose) |
| F2 | Factura simplificada (Art. 6.1.d RD 1619/2012) |
金额较小、无需识别买方的发票(小票、餐饮等)。 | 通常对应 Ticket。买方信息可选。 |
| F3 | Factura emitida en sustitución de facturas simplificadas | 当企业把之前已申报的简易发票(F2)替换为完整发票(F1)。 | 属于更正性质的“补开发票”,用于“由 ticket 转发票”。这正是你现在在做的情况。 |
| R1 | Factura rectificativa (Art. 80.1 y 80.2 y error fundado en derecho) | 纠正税基、税率或金额错误(因判决、行政原因等)。 | Rectificación 依据法条 80.1 / 80.2。 |
| R2 | Factura rectificativa (Art. 80.3) | 因退货、折扣、无效合同等导致的修正。 | 对应业务更改(例如取消销售)。 |
| R3 | Factura rectificativa (Art. 80.4) | 客户破产或无力偿付引起的修正。 | 专用于破产情形。 |
| R4 | Factura rectificativa (Resto) | 其他未涵盖情形的更正发票。 | 一般性修正用途。 |
| R5 | Factura rectificativa simplificada | 针对简易发票的修正。 | 通常用于 ticket 更正。 |
QR 生成地址 以及规则
QR 尺寸 40*40 mm
以地址为准生成 QR 码,并在下方 打印: Factura verificable en la sede electrónica de la AEAT
测试环境地址:
https://prewww2.aeat.es/wlpl/TIKE-CONT/ValidarQR?nif=XXXXXXXXY&numserie=YYYY...YYYY&fecha=DD- DD-AAAA&importe=NNNNNNNNN.DD
生产环境地址:
XML 返回的资料进行拼接 " &" = " 链接 xia
nif= XXXXXXXXXXXX &numseria= 123456 &fecha= &importe=
URL base: https://prewww2.aeat.es/wlpl/TIKE-CONT/ValidarQR?
Parámetro nif: 89890001K
Parámetro numserie: 12345678&G33
Parámetro fecha: 01-01-2024
Parámetro importe: 241.4
开发流程
设置中 添加 加载电子证书按钮 + 密码框 , 解析 + 使用
1 - 解析证书 ( 获取证书中的 ID, 姓名资料 )
- CN : CERTIFICADO FISICA PRUEBAS -99999910G' (- 前是公司/个人名字 - 后面是税号)
- SN : 公司或者个人名称
- O : 有这个字段代表公司 否则是个人
- SERIALNUMBER - 后面是 税号
C 是国家 - NotAfter 证书到期时间
参考一下代码
using System;
using System.Security.Cryptography.X509Certificates;
using System.Text.RegularExpressions;
namespace CertInfoExtractor
{
public enum CertType
{
Unknown,
Personal,
Organization
}
public class CertInfo
{
public CertType Type { get; set; }
public string Name { get; set; } // 姓名或公司名
public string TaxNumber { get; set; } // 税号 / NIF / CIF
public DateTime NotAfter { get; set; } // 到期时间
public int DaysRemaining { get; set; } // 剩余天数
}
public class CertParser
{
public static CertInfo Parse(string pfxPath, string password)
{
var cert = new X509Certificate2(pfxPath, password,
X509KeyStorageFlags.Exportable | X509KeyStorageFlags.MachineKeySet);
string subject = cert.Subject;
string issuer = cert.Issuer;
string cn = GetField(subject, "CN");
string org = GetField(subject, "O");
string serial = GetField(subject, "SERIALNUMBER");
// 税号兜底正则
if (string.IsNullOrEmpty(serial))
{
var m = Regex.Match(subject, @"(SERIALNUMBER|NIF|CIF|NIE|UID)\s*=\s*([A-Z0-9\-\/]{6,20})",
RegexOptions.IgnoreCase);
if (m.Success)
serial = m.Groups[2].Value.Trim();
}
// 判断类型
CertType type = DetectType(org, serial, issuer);
// 名称:企业用 O,个人用 CN
string name = (type == CertType.Organization && !string.IsNullOrEmpty(org))
? org
: cn;
var notAfter = cert.NotAfter;
int daysRemaining = (int)(notAfter - DateTime.Now).TotalDays;
return new CertInfo
{
Type = type,
Name = name,
TaxNumber = serial,
NotAfter = notAfter,
DaysRemaining = daysRemaining
};
}
private static CertType DetectType(string org, string serial, string issuer)
{
bool hasOrg = !string.IsNullOrEmpty(org);
bool looksLikeCompanyId = Regex.IsMatch(serial ?? "", @"^[A-Z]\d{7,8}", RegexOptions.IgnoreCase);
bool looksLikePersonId = Regex.IsMatch(serial ?? "", @"\d{7,8}[A-Z]$", RegexOptions.IgnoreCase);
if (hasOrg || looksLikeCompanyId)
return CertType.Organization;
if (looksLikePersonId)
return CertType.Personal;
if (issuer.Contains("Persona Física", StringComparison.OrdinalIgnoreCase))
return CertType.Personal;
if (issuer.Contains("Representante", StringComparison.OrdinalIgnoreCase))
return CertType.Organization;
return CertType.Unknown;
}
private static string GetField(string subject, string key)
{
var m = Regex.Match(subject, key + @"\s*=\s*([^,]+)", RegexOptions.IgnoreCase);
return m.Success ? m.Groups[1].Value.Trim() : null;
}
// 测试入口
public static void Main()
{
string pfxPath = @"C:\certs\yourcert.pfx";
string password = "yourPassword";
var info = Parse(pfxPath, password);
Console.WriteLine("🔍 证书类型: " + info.Type);
Console.WriteLine("📛 名称: " + info.Name);
Console.WriteLine("🧾 税号: " + info.TaxNumber);
Console.WriteLine("📅 到期时间: " + info.NotAfter.ToString("yyyy-MM-dd HH:mm:ss"));
Console.WriteLine("⏳ 剩余天数: " + info.DaysRemaining);
}
}
}
系统信息部分 SistemaInformatico XML
<sum1:SistemaInformatico>
<sum1:NombreRazon>JIECHENG INFORMATICA SL</sum1:NombreRazon>
<sum1:NIF>B67287789</sum1:NIF>
<sum1:NombreSistemaInformatico>JIECHENG TPV</sum1:NombreSistemaInformatico>
<sum1:IdSistemaInformatico>J2</sum1:IdSistemaInformatico>
<sum1:Version>1.0.03</sum1:Version>
<sum1:NumeroInstalacion>383</sum1:NumeroInstalacion> // 主机1 副机 2
<sum1:TipoUsoPosibleSoloVerifactu>N</sum1:TipoUsoPosibleSoloVerifactu>
<sum1:TipoUsoPosibleMultiOT>S</sum1:TipoUsoPosibleMultiOT>
<sum1:IndicadorMultiplesOT>S</sum1:IndicadorMultiplesOT>
</sum1:SistemaInformatico>
2- 解析证书后 参考文档生成指纹 生成记录指纹或哈希的技术规范详情
发送发票 /小票 hash 计算
数据需要以以上顺序生成 string 进行连接
如 nombreCampo1=valorCampo1&nombreCampo2=valorCampo2&nombreCampoN=valorCampoN
可参考一下代码 (官方代码 翻译 可能需要操作 合并xml )
using System;
using System.Security.Cryptography;
using System.Text;
public class GeneradorHuella
{
/// <summary>
/// 计算 SHA-256 哈希并输出 Base64(与 Java 逻辑完全一致)
/// </summary>
public static string GetHashVerifactu(string msg)
{
try
{
using (var sha = SHA256.Create())
{
byte[] hash = sha.ComputeHash(Encoding.UTF8.GetBytes(msg));
return Convert.ToBase64String(hash);
}
}
catch (Exception e)
{
throw new Exception("生成 SHA 哈希时发生错误", e);
}
}
/// <summary>
/// 生成 RegistroAlta 记录对应的字段拼接字符串
/// 顺序与 Java 完全相同
/// </summary>
public static string GetReferenciaRegistroAlta(
string nifEmisor,
string numFacturaSerie,
string fechaExpedicion,
string tipoFactura,
string cuotaTotal,
string importeTotal,
string huellaAnterior,
string fechaHoraUsoRegistro)
{
var sb = new StringBuilder();
sb.Append(GetValorCampo("IDEmisorFactura", nifEmisor, true))
.Append(GetValorCampo("NumSerieFactura", numFacturaSerie, true))
.Append(GetValorCampo("FechaExpedicionFactura", fechaExpedicion, true))
.Append(GetValorCampo("TipoFactura", tipoFactura, true))
.Append(GetValorCampo("CuotaTotal", cuotaTotal, true))
.Append(GetValorCampo("ImporteTotal", importeTotal, true))
.Append(GetValorCampo("Huella", huellaAnterior, true))
.Append(GetValorCampo("FechaHoraUsoRegistro", fechaHoraUsoRegistro, false)); // 最后一项不加 &
return sb.ToString();
}
/// <summary>
/// 生成一个字段的格式:Nombre=Valor 或 Nombre=Valor&
/// </summary>
public static string GetValorCampo(string nombre, string valor, bool separador)
{
string campo = nombre + "=" + (valor == null ? "" : valor.Trim());
if (separador)
return campo + "&";
else
return campo;
}
/// <summary>
/// 计算 Alta 记录的 huella(Base64 SHA-256)
/// </summary>
public static string CalcularHuellaAlta(
string nifEmisor,
string numFacturaSerie,
string fechaExpedicion,
string tipoFactura,
string cuotaTotal,
string importeTotal,
string huellaAnterior,
string fechaHoraUsoRegistro)
{
string referencia = GetReferenciaRegistroAlta(
nifEmisor,
numFacturaSerie,
fechaExpedicion,
tipoFactura,
cuotaTotal,
importeTotal,
huellaAnterior,
fechaHoraUsoRegistro);
return GetHashVerifactu(referencia);
}
}
其他方法:
📌 1. 主入口(你只需要调用这个)
string xml = File.ReadAllText("miFactura.xml");
string huella = VerifactuHashGenerator.CalcularHuellaDesdeXml(xml);
Console.WriteLine("Huella generada = " + huella);
🚀 2. VerifactuHashGenerator.cs(完整可运行)
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Security.Cryptography;
using System.Text;
using System.Xml;
public static class VerifactuHashGenerator
{
// =============================
// PUBLIC ENTRY
// =============================
public static string CalcularHuellaDesdeXml(string xml)
{
XmlDocument doc = new XmlDocument();
doc.LoadXml(xml);
// Detectar tipo de registro (Alta / Evento / Anulación)
string tipo = DetectarTipoRegistro(doc);
// Obtener campos según el tipo
var campos = tipo switch
{
"RegistroAlta" => ExtraerCamposRegistroAlta(doc),
"RegistroEvento" => ExtraerCamposRegistroEvento(doc),
"RegistroAnulacion" => ExtraerCamposRegistroAnulacion(doc),
_ => throw new Exception("Tipo de registro no reconocido")
};
// Construir cadena ordenada
string cadena = ConstruirCadena(campos);
// Calcular hash HEX
return Sha256Hex(cadena);
}
// =============================
// DETECCIÓN DE TIPO DE REGISTRO
// =============================
private static string DetectarTipoRegistro(XmlDocument doc)
{
if (doc.GetElementsByTagName("RegistroAlta").Count > 0) return "RegistroAlta";
if (doc.GetElementsByTagName("RegistroEvento").Count > 0) return "RegistroEvento";
if (doc.GetElementsByTagName("RegistroAnulacion").Count > 0) return "RegistroAnulacion";
throw new Exception("No se encontró nodo RegistroAlta / RegistroEvento / RegistroAnulacion");
}
// =============================
// CAMPO EXTRACTION (ALTA)
// =============================
private static List<(string, string)> ExtraerCamposRegistroAlta(XmlDocument doc)
{
var lista = new List<(string, string)>();
lista.Add(("IDEmisorFactura", Get(doc, "IDEmisorFactura")));
lista.Add(("NumSerieFactura", Get(doc, "NumSerieFactura")));
lista.Add(("FechaExpedicionFactura", Get(doc, "FechaExpedicionFactura")));
lista.Add(("TipoFactura", Get(doc, "TipoFactura")));
lista.Add(("CuotaTotal", NormalizarNumero(Get(doc, "CuotaTotal"))));
lista.Add(("ImporteTotal", NormalizarNumero(Get(doc, "ImporteTotal"))));
lista.Add(("Huella", Get(doc, "Huella")));
lista.Add(("FechaHoraUsoRegistro", Get(doc, "FechaHoraUsoRegistro")));
return lista;
}
// =============================
// CAMPO EXTRACTION (EVENTO)
// =============================
private static List<(string, string)> ExtraerCamposRegistroEvento(XmlDocument doc)
{
var lista = new List<(string, string)>();
lista.Add(("IDEmisorFactura", Get(doc, "IDEmisorFactura")));
lista.Add(("NumSerieFactura", Get(doc, "NumSerieFactura")));
lista.Add(("TipoEvento", Get(doc, "TipoEvento")));
lista.Add(("DescripcionEvento", Get(doc, "DescripcionEvento")));
lista.Add(("Huella", Get(doc, "Huella")));
lista.Add(("FechaHoraUsoRegistro", Get(doc, "FechaHoraUsoRegistro")));
return lista;
}
// =============================
// CAMPO EXTRACTION (ANULACION)
// =============================
private static List<(string, string)> ExtraerCamposRegistroAnulacion(XmlDocument doc)
{
var lista = new List<(string, string)>();
lista.Add(("IDEmisorFactura", Get(doc, "IDEmisorFactura")));
lista.Add(("NumSerieFactura", Get(doc, "NumSerieFactura")));
lista.Add(("FechaExpedicionFactura", Get(doc, "FechaExpedicionFactura")));
lista.Add(("TipoFactura", Get(doc, "TipoFactura")));
lista.Add(("MotivoAnulacion", Get(doc, "MotivoAnulacion")));
lista.Add(("Huella", Get(doc, "Huella")));
lista.Add(("FechaHoraUsoRegistro", Get(doc, "FechaHoraUsoRegistro")));
return lista;
}
// =============================
// XML GETTER
// =============================
private static string Get(XmlDocument doc, string tag)
{
var list = doc.GetElementsByTagName(tag);
if (list.Count == 0) return "";
return list[0].InnerText.Trim();
}
// =============================
// STRING BUILDING
// =============================
private static string ConstruirCadena(List<(string campo, string valor)> lista)
{
var sb = new StringBuilder();
for (int i = 0; i < lista.Count; i++)
{
bool addAmp = i < lista.Count - 1;
sb.Append(lista[i].campo)
.Append("=")
.Append(lista[i].valor);
if (addAmp) sb.Append("&");
}
return sb.ToString();
}
// =============================
// NORMALIZACIÓN DE NUMEROS
// =============================
private static string NormalizarNumero(string raw)
{
if (decimal.TryParse(raw, NumberStyles.Any, CultureInfo.InvariantCulture, out var d))
return d.ToString("G29", CultureInfo.InvariantCulture);
return raw?.Trim();
}
// =============================
// SHA256 → HEX
// =============================
private static string Sha256Hex(string input)
{
using var sha = SHA256.Create();
byte[] bytes = sha.ComputeHash(Encoding.UTF8.GetBytes(input));
var sb = new StringBuilder();
foreach (var b in bytes)
sb.Append(b.ToString("X2"));
return sb.ToString();
}
}
以下是针对西班牙税务局 (AEAT) Verifactu SOAP 服务的完整实现,使用 XML 和客户端证书发送请求:
1. 创建 SOAP 请求类
using System.Xml.Serialization;
[XmlRoot(ElementName = "Envelope", Namespace = "http://schemas.xmlsoap.org/soap/envelope/")]
public class SoapEnvelope
{
[XmlElement(ElementName = "Header", Namespace = "http://schemas.xmlsoap.org/soap/envelope/")]
public SoapHeader Header { get; set; } = new SoapHeader();
[XmlElement(ElementName = "Body", Namespace = "http://schemas.xmlsoap.org/soap/envelope/")]
public SoapBody Body { get; set; } = new SoapBody();
}
public class SoapHeader
{
// 可以根据需要添加 SOAP 头内容
}
public class SoapBody
{
[XmlElement(Namespace = "https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/tike/cont/ws/SistemaFacturacion.xsd")]
public VerifactuRequest Verifactu { get; set; } = new VerifactuRequest();
}
public class VerifactuRequest
{
[XmlElement(ElementName = "Presentador")]
public Presentador Presentador { get; set; } = new Presentador();
[XmlElement(ElementName = "Registro")]
public Registro[] Registros { get; set; } = Array.Empty<Registro>();
}
public class Presentador
{
[XmlElement(ElementName = "NIF")]
public string NIF { get; set; } = string.Empty;
[XmlElement(ElementName = "Nombre")]
public string Nombre { get; set; } = string.Empty;
}
public class Registro
{
[XmlElement(ElementName = "TipoRegistro")]
public string TipoRegistro { get; set; } = string.Empty;
[XmlElement(ElementName = "Modelo")]
public string Modelo { get; set; } = string.Empty;
[XmlElement(ElementName = "Ejercicio")]
public string Ejercicio { get; set; } = string.Empty;
[XmlElement(ElementName = "Periodo")]
public string Periodo { get; set; } = string.Empty;
[XmlElement(ElementName = "IdRegistro")]
public string IdRegistro { get; set; } = string.Empty;
[XmlElement(ElementName = "Datos")]
public string Datos { get; set; } = string.Empty;
}
2. 主要的 SOAP 客户端实现
using System;
using System.IO;
using System.Net;
using System.Net.Http;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading.Tasks;
using System.Xml;
using System.Xml.Serialization;
public class AeatSoapClient
{
private readonly string _certificatePath;
private readonly string _certificatePassword;
private readonly string _soapEndpoint;
public AeatSoapClient(string certificatePath, string certificatePassword)
{
_certificatePath = certificatePath;
_certificatePassword = certificatePassword;
_soapEndpoint = "https://prewww10.aeat.es/wlpl/TIKE-CONT/ws/SistemaFacturacion/VerifactuSOAP";
}
public async Task<string> SendVerifactuRequest(VerifactuRequest requestData)
{
try
{
// 创建 SOAP 信封
var soapEnvelope = new SoapEnvelope
{
Body = new SoapBody { Verifactu = requestData }
};
// 序列化 XML
string soapXml = SerializeSoapEnvelope(soapEnvelope);
// 创建 HTTP 客户端并发送请求
using (var handler = CreateHttpClientHandler())
using (var client = new HttpClient(handler))
{
// 设置 SOAP 特定的 HTTP 头
var content = new StringContent(soapXml, Encoding.UTF8, "text/xml");
content.Headers.Add("SOAPAction", ""); // 根据实际 WSDL 可能需要设置具体的 SOAPAction
// 发送请求
HttpResponseMessage response = await client.PostAsync(_soapEndpoint, content);
if (response.IsSuccessStatusCode)
{
return await response.Content.ReadAsStringAsync();
}
else
{
throw new HttpRequestException($"SOAP 请求失败: {response.StatusCode} - {response.ReasonPhrase}");
}
}
}
catch (Exception ex)
{
throw new Exception($"发送 Verifactu 请求时出错: {ex.Message}", ex);
}
}
private HttpClientHandler CreateHttpClientHandler()
{
// 加载客户端证书
X509Certificate2 clientCertificate = new X509Certificate2(_certificatePath, _certificatePassword);
var handler = new HttpClientHandler();
handler.ClientCertificates.Add(clientCertificate);
// 对于测试环境,可以忽略服务器证书验证
// 生产环境中应该使用正确的证书验证
handler.ServerCertificateCustomValidationCallback = (message, cert, chain, sslPolicyErrors) =>
{
// 在生产环境中,应该根据实际需求进行适当的证书验证
// 这里为了示例暂时返回 true
return true;
};
return handler;
}
private string SerializeSoapEnvelope(SoapEnvelope envelope)
{
var namespaces = new XmlSerializerNamespaces();
namespaces.Add("soap", "http://schemas.xmlsoap.org/soap/envelope/");
namespaces.Add("tns", "https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/tike/cont/ws/SistemaFacturacion.xsd");
var settings = new XmlWriterSettings
{
Encoding = Encoding.UTF8,
Indent = false,
OmitXmlDeclaration = false
};
var serializer = new XmlSerializer(typeof(SoapEnvelope));
using (var stringWriter = new StringWriter())
using (var xmlWriter = XmlWriter.Create(stringWriter, settings))
{
serializer.Serialize(xmlWriter, envelope, namespaces);
return stringWriter.ToString();
}
}
}
3. 使用示例
class Program
{
static async Task Main(string[] args)
{
try
{
// 初始化 SOAP 客户端
var aeatClient = new AeatSoapClient(
certificatePath: @"C:\certificates\aeat_client.pfx",
certificatePassword: "tu_password"
);
// 创建请求数据
var verifactuRequest = new VerifactuRequest
{
Presentador = new Presentador
{
NIF = "12345678A",
Nombre = "EMPRESA DEMO SL"
},
Registros = new[]
{
new Registro
{
TipoRegistro = "ALTA",
Modelo = "340",
Ejercicio = "2024",
Periodo = "01",
IdRegistro = "001",
Datos = "DATOS_XML_ESPECIFICOS_DEL_MODELO"
}
}
};
// 发送请求
string response = await aeatClient.SendVerifactuRequest(verifactuRequest);
Console.WriteLine("Respuesta recibida:");
Console.WriteLine(response);
// 处理响应...
ProcessResponse(response);
}
catch (Exception ex)
{
Console.WriteLine($"错误: {ex.Message}");
if (ex.InnerException != null)
{
Console.WriteLine($"内部错误: {ex.InnerException.Message}");
}
}
}
static void ProcessResponse(string responseXml)
{
// 这里添加解析响应 XML 的逻辑
Console.WriteLine("Procesando respuesta...");
}
}
4. 高级配置版本(支持更多选项)
public class AdvancedAeatSoapClient
{
private readonly X509Certificate2 _clientCertificate;
private readonly string _soapEndpoint;
public AdvancedAeatSoapClient(string certificatePath, string certificatePassword, bool useProduction = false)
{
_clientCertificate = new X509Certificate2(certificatePath, certificatePassword);
_soapEndpoint = useProduction ?
"https://www10.aeat.es/wlpl/TIKE-CONT/ws/SistemaFacturacion/VerifactuSOAP" :
"https://prewww10.aeat.es/wlpl/TIKE-CONT/ws/SistemaFacturacion/VerifactuSOAP";
}
public AdvancedAeatSoapClient(byte[] certificateBytes, string certificatePassword)
{
_clientCertificate = new X509Certificate2(certificateBytes, certificatePassword);
_soapEndpoint = "https://prewww10.aeat.es/wlpl/TIKE-CONT/ws/SistemaFacturacion/VerifactuSOAP";
}
public async Task<string> SendRequestWithTimeout(string soapXml, TimeSpan timeout)
{
using (var handler = CreateHttpClientHandler())
using (var client = new HttpClient(handler) { Timeout = timeout })
{
var content = new StringContent(soapXml, Encoding.UTF8, "text/xml");
// 添加必要的 HTTP 头
client.DefaultRequestHeaders.Add("User-Agent", "AEAT-SOAP-Client/1.0");
var response = await client.PostAsync(_soapEndpoint, content);
response.EnsureSuccessStatusCode();
return await response.Content.ReadAsStringAsync();
}
}
private HttpClientHandler CreateHttpClientHandler()
{
return new HttpClientHandler
{
ClientCertificates = { _clientCertificate },
ServerCertificateCustomValidationCallback = (message, cert, chain, sslPolicyErrors) =>
{
// 在生产环境中实现适当的证书验证逻辑
return sslPolicyErrors == System.Net.Security.SslPolicyErrors.None;
}
};
}
}