Paquete Java 8+ para gestión y registro de facturación electrónica VeriFactu según las especificaciones de la Agencia Tributaria (AEAT).
- Modelos POJO para invoices, breakdowns y recipients
- Enums para campos fiscales (invoice type, tax type, regime, operation type, etc.)
- Helpers para operaciones de hash, XML, validación de NIF/CIF y generación de QR
- Servicio AEAT client configurable con Apache CXF
- Firma digital XAdES con EU DSS 5.11.x
- Validación automática de facturas (NIF/CIF, consistencia de importes, campos obligatorios)
- Modos duales: VERIFACTU y NO VERIFACTU (Requerimiento)
- Generación de QR: URLs automáticas para códigos QR según modo y entorno
- Campos avanzados: Encadenamiento blockchain, facturas rectificativas, subsanación
- Multi-tenant: Soporte para múltiples instalaciones bajo el mismo NIF
- Clientes extranjeros: Soporte para identificadores internacionales
- Tests unitarios para todos los componentes core
- Listo para extensión y uso en producción
Clona el repositorio y construye el paquete:
git clone https://github.com/squareetlabs/verifactu-sdk.git
cd verifactu-sdk
mvn clean installAñade el repositorio a tu pom.xml o settings.xml:
<repositories>
<repository>
<id>github</id>
<url>https://maven.pkg.github.com/squareetlabs/verifactu-sdk</url>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
</repositories>Y la dependencia:
<dependency>
<groupId>com.squareetlabs</groupId>
<artifactId>verifactu-sdk</artifactId>
<version>1.0.0</version>
</dependency>Nota: Necesitarás configurar tus credenciales de GitHub (token) en tu settings.xml para acceder al paquete.
Crea una instancia de VeriFactuConfig con los parámetros de tu empresa:
VeriFactuConfig config = new VeriFactuConfig(
"Mi Empresa S.L.", // Nombre del emisor
"B12345678" // NIF/CIF del emisor
);
// Configuración adicional opcional
config.setSystemId("01"); // ID del sistema informático
config.setDefaultCurrency("EUR"); // Moneda por defecto
config.setVeriFactuMode(true); // Modo VERIFACTU (true) o NO VERIFACTU (false)
config.setTipoUsoPosibleSoloVerifactu("N"); // Tipo de uso posible solo VeriFactu
config.setTipoUsoPosibleMultiOt("S"); // Tipo de uso posible multi-OT
config.setIndicadorMultiplesOt("N"); // Indicador múltiples OT- VERIFACTU mode (true): Genera hash de encadenamiento y cumple con todos los requisitos VeriFactu
- NO VERIFACTU mode (false): Modo de requerimiento sin encadenamiento blockchain
AeatClient client = new AeatClient(
"/path/to/certificate.p12", // Ruta al certificado digital
"certificatePassword", // Contraseña del certificado
config, // Configuración VeriFactu
false, // false = pre-producción, true = producción
true // true = modo VERIFACTU, false = NO VERIFACTU
);import com.squareetlabs.verifactu.models.*;
import com.squareetlabs.verifactu.services.*;
import java.time.LocalDate;
import java.util.Arrays;
import java.util.Map;
public class InvoiceExample {
public static void main(String[] args) {
// Configurar cliente
VeriFactuConfig config = new VeriFactuConfig("Mi Empresa S.L.", "B12345678");
AeatClient client = new AeatClient("/path/to/cert.p12", "password", config, false, true);
// Crear factura
Invoice invoice = new Invoice();
invoice.setInvoiceNumber("F2024-001");
invoice.setIssueDate(LocalDate.now());
invoice.setInvoiceType(InvoiceType.STANDARD);
invoice.setOperationType(OperationType.STANDARD);
invoice.setTotalAmount(121.00);
invoice.setTaxAmount(21.00);
invoice.setIssuerTaxId("B12345678");
// Añadir desglose (breakdown)
Breakdown breakdown = new Breakdown(
TaxType.IVA, // Tipo de impuesto
RegimeType.GENERAL, // Régimen fiscal
21.0, // Tipo impositivo
100.0, // Base imponible
21.0 // Cuota
);
invoice.setBreakdowns(Arrays.asList(breakdown));
// Añadir destinatario (opcional)
Recipient recipient = new Recipient();
recipient.setName("Cliente S.L.");
recipient.setTaxId("B87654321");
recipient.setCountry("ES");
invoice.setRecipients(Arrays.asList(recipient));
// Enviar factura a AEAT
Map<String, Object> response = client.sendInvoice(invoice, null);
System.out.println("Estado: " + response.get("status"));
System.out.println("Hash generado: " + response.get("hash"));
System.out.println("QR URL: " + response.get("qrUrl"));
}
}El paquete soporta todos los tipos de factura según la normativa AEAT:
InvoiceType.STANDARD- Factura estándar (F1)InvoiceType.SIMPLIFIED- Factura simplificada (F2)InvoiceType.SUBSTITUTE- Factura sin identificación del destinatario (F3)InvoiceType.EXPORT- Asiento resumen de facturas (F4)InvoiceType.RECTIFICATIVE_R1- Factura rectificativa (Error fundado en derecho)InvoiceType.RECTIFICATIVE_R2- Factura rectificativa (Art. 80.1, 80.2, 80.6 LIVA)InvoiceType.RECTIFICATIVE_R3- Factura rectificativa (Art. 80.3, 80.4 LIVA)InvoiceType.RECTIFICATIVE_R4- Factura rectificativa (Resto)InvoiceType.RECTIFICATIVE_R5- Factura rectificativa en facturas simplificadas
Invoice invoice = new Invoice();
invoice.setInvoiceNumber("F2024-001");
invoice.setIssueDate(LocalDate.now());
invoice.setInvoiceType(InvoiceType.STANDARD);
invoice.setOperationType(OperationType.STANDARD);
invoice.setTotalAmount(121.00);
invoice.setTaxAmount(21.00);
invoice.setIssuerTaxId("B12345678");
Breakdown breakdown = new Breakdown(TaxType.IVA, RegimeType.GENERAL, 21.0, 100.0, 21.0);
invoice.setBreakdowns(Arrays.asList(breakdown));Invoice invoice = new Invoice();
invoice.setInvoiceNumber("FS2024-001");
invoice.setIssueDate(LocalDate.now());
invoice.setInvoiceType(InvoiceType.SIMPLIFIED);
invoice.setOperationType(OperationType.STANDARD);
invoice.setTotalAmount(121.00);
invoice.setTaxAmount(21.00);
invoice.setIssuerTaxId("B12345678");
invoice.setSimplified(true); // Marca como simplificada
Breakdown breakdown = new Breakdown(TaxType.IVA, RegimeType.GENERAL, 21.0, 100.0, 21.0);
invoice.setBreakdowns(Arrays.asList(breakdown));Invoice invoice = new Invoice();
invoice.setInvoiceNumber("FR2024-001");
invoice.setIssueDate(LocalDate.now());
invoice.setInvoiceType(InvoiceType.RECTIFICATIVE_R1);
invoice.setOperationType(OperationType.STANDARD);
invoice.setCorrectionType("S"); // Sustitución
invoice.setIssuerTaxId("B12345678");
// Importes NUEVOS de la factura rectificativa
invoice.setTotalAmount(150.00);
invoice.setTaxAmount(31.50);
// Importes ORIGINALES de la factura que se corrige (ImporteRectificacion)
invoice.setCorrectedBaseAmount(100.00); // Base original
invoice.setCorrectedTaxAmount(21.00); // IVA original
invoice.setCorrectedSurchargeAmount(5.20); // Recargo original (opcional)
// Referencia a la factura original
invoice.setOriginalInvoiceNumber("F2024-001");
invoice.setOriginalIssueDate(LocalDate.of(2024, 1, 15));
Breakdown breakdown = new Breakdown(TaxType.IVA, RegimeType.GENERAL, 21.0, 150.0, 31.50);
invoice.setBreakdowns(Arrays.asList(breakdown));Invoice invoice = new Invoice();
invoice.setInvoiceNumber("FR2024-001");
invoice.setIssueDate(LocalDate.now());
invoice.setInvoiceType(InvoiceType.RECTIFICATIVE_R1);
invoice.setOperationType(OperationType.STANDARD);
invoice.setTotalAmount(121.00);
invoice.setTaxAmount(21.00);
invoice.setIssuerTaxId("B12345678");
// Referencia a la factura original
invoice.setOriginalInvoiceNumber("F2024-001");
invoice.setOriginalIssueDate(LocalDate.of(2024, 1, 15));
Breakdown breakdown = new Breakdown(TaxType.IVA, RegimeType.GENERAL, 21.0, 100.0, 21.0);
invoice.setBreakdowns(Arrays.asList(breakdown));Invoice invoice = new Invoice();
invoice.setInvoiceNumber("FE2024-001");
invoice.setIssueDate(LocalDate.now());
invoice.setInvoiceType(InvoiceType.EXPORT);
invoice.setOperationType(OperationType.EXPORT);
invoice.setTotalAmount(1000.00);
invoice.setTaxAmount(0.0);
invoice.setIssuerTaxId("B12345678");
Recipient recipient = new Recipient();
recipient.setName("Foreign Client Ltd.");
recipient.setTaxId("FR123456789");
recipient.setCountry("FR");
invoice.setRecipients(Arrays.asList(recipient));
Breakdown breakdown = new Breakdown(TaxType.IVA, RegimeType.EXPORT, 0.0, 1000.0, 0.0);
invoice.setBreakdowns(Arrays.asList(breakdown));Invoice invoice = new Invoice();
invoice.setInvoiceNumber("F2024-002");
invoice.setIssueDate(LocalDate.now());
invoice.setInvoiceType(InvoiceType.STANDARD);
invoice.setOperationType(OperationType.STANDARD);
invoice.setTotalAmount(242.00);
invoice.setTaxAmount(42.00);
invoice.setIssuerTaxId("B12345678");
// Encadenamiento con factura anterior
invoice.setPreviousInvoiceNumber("F2024-001");
invoice.setPreviousHash("ABC123DEF456..."); // Hash de la factura anterior
Breakdown breakdown = new Breakdown(TaxType.IVA, RegimeType.GENERAL, 21.0, 200.0, 42.0);
invoice.setBreakdowns(Arrays.asList(breakdown));previousInvoiceNumber: Número de la factura anterior en la cadenapreviousHash: Hash de la factura anteriorpreviousIssueDate: Fecha de emisión de la factura anterior
originalInvoiceNumber: Número de la factura original a rectificaroriginalIssueDate: Fecha de emisión de la factura originalrectificationType: Tipo de rectificación (R1-R5)correctionType: Tipo de corrección ("S" = Sustitución, "I" = Por diferencia)correctedBaseAmount: Base imponible original (requerido para tipo "S")correctedTaxAmount: Cuota de IVA original (requerido para tipo "S")correctedSurchargeAmount: Cuota de recargo original (opcional)
subsanationInvoiceNumber: Número de factura de subsanaciónsubsanationDate: Fecha de subsanaciónrejectionCode: Código de rechazo AEAT
installationId: ID de la instalación (para múltiples instalaciones)deviceId: ID del dispositivo emisor
aeatStatus: Estado de la respuesta AEAT (ACCEPTED, REJECTED, PENDING)aeatResponseCode: Código de respuesta AEATaeatResponseMessage: Mensaje de respuesta AEATaeatRegistrationDate: Fecha de registro en AEAT
description: Descripción de la facturasimplified: Indica si es factura simplificadathirdPartyIssuer: Indica si la factura es emitida por terceromacroInvoice: Indica si es una macro-factura
// Enviar factura
Map<String, Object> response = client.sendInvoice(invoice, null);
// Verificar respuesta
if ("ACCEPTED".equals(response.get("status"))) {
System.out.println("Factura aceptada");
System.out.println("Hash: " + response.get("hash"));
System.out.println("CSV: " + response.get("csv"));
System.out.println("QR URL: " + response.get("qrUrl"));
} else {
System.out.println("Factura rechazada: " + response.get("message"));
}// Implementar consulta de estado según necesidades
// El cliente AEAT proporciona métodos para verificar el estadoEl paquete incluye validación automática mediante InvoiceValidator:
import com.squareetlabs.verifactu.helpers.InvoiceValidator;
InvoiceValidator validator = new InvoiceValidator();
List<String> errors = validator.validate(invoice);
if (errors.isEmpty()) {
System.out.println("Factura válida");
} else {
System.out.println("Errores de validación:");
errors.forEach(System.out::println);
}- NIF/CIF: Validación de formato y dígito de control
- Consistencia de importes: Base imponible + IVA = Total
- Campos obligatorios: Número, fecha, tipo, importes
- Desgloses: Al menos un breakdown por factura
- Facturas rectificativas: Referencia a factura original obligatoria
import com.squareetlabs.verifactu.helpers.HashHelper;
String hash = HashHelper.generateHash(
"B12345678", // NIF emisor
"F2024-001", // Número factura
"2024-01-27", // Fecha emisión
"121.00", // Importe total
"ABC123..." // Hash anterior (opcional)
);import com.squareetlabs.verifactu.helpers.NifValidator;
boolean isValid = NifValidator.validate("B12345678");
String nifType = NifValidator.getType("B12345678"); // CIF, NIF, NIE, etc.import com.squareetlabs.verifactu.helpers.QrHelper;
String qrUrl = QrHelper.generateQrUrl(
"B12345678",
"F2024-001",
"2024-01-27",
"121.00",
"ABC123...",
true, // Modo VERIFACTU
false // Pre-producción
);
// Generar imagen QR
byte[] qrImage = QrHelper.generateQrImage(qrUrl, 300, 300);import com.squareetlabs.verifactu.services.SignatureService;
SignatureService signatureService = new SignatureService(
"/path/to/cert.p12",
"password"
);
// Firmar XML
byte[] xmlBytes = xmlString.getBytes();
byte[] signedXml = signatureService.signXml(xmlBytes);
// Verificar firma
boolean isValid = signatureService.verifySignature(signedXml);Ejecuta todos los tests unitarios:
mvn testInvoiceValidatorTest: Validación de facturasNifValidatorTest: Validación de NIF/CIFHashHelperTest: Generación de hashesVeriFactuComplianceTest: Cumplimiento con especificaciones AEATAeatClientTest: Funcionalidad del cliente AEAT
src/
├── main/java/com/squareetlabs/verifactu/
│ ├── contracts/ # Interfaces
│ │ ├── VeriFactuInvoice.java
│ │ ├── VeriFactuBreakdown.java
│ │ └── VeriFactuRecipient.java
│ ├── models/ # Modelos POJO
│ │ ├── Invoice.java
│ │ ├── Breakdown.java
│ │ ├── Recipient.java
│ │ ├── InvoiceType.java
│ │ ├── TaxType.java
│ │ ├── RegimeType.java
│ │ └── OperationType.java
│ ├── services/ # Servicios
│ │ ├── AeatClient.java
│ │ ├── SignatureService.java
│ │ └── VeriFactuConfig.java
│ ├── helpers/ # Utilidades
│ │ ├── HashHelper.java
│ │ ├── XmlHelper.java
│ │ ├── NifValidator.java
│ │ ├── InvoiceValidator.java
│ │ └── QrHelper.java
│ └── exceptions/ # Excepciones personalizadas
└── test/java/ # Tests unitarios
- Apache CXF 3.5.7: SOAP/HTTP Secure transport
- EU DSS 5.11.1: XAdES (XML Advanced Electronic Signatures)
- JAXB 2.3.8: XML binding
- ZXing 3.5.3: Generación de códigos QR
- JUnit 4.13.2: Testing
- Java 8 o superior
- Maven 3+
- Certificado digital válido para firma electrónica
Las contribuciones son bienvenidas. Por favor:
- Fork el proyecto
- Crea una rama para tu feature (
git checkout -b feature/nueva-funcionalidad) - Commit tus cambios (
git commit -m 'feat: añadir nueva funcionalidad') - Push a la rama (
git push origin feature/nueva-funcionalidad) - Abre un Pull Request
Este paquete es open-source bajo la Licencia MIT.
- Documentación técnica AEAT: https://sede.agenciatributaria.gob.es/Sede/iva/sistemas-informaticos-facturacion-verifactu/informacion-tecnica.html
- Issues: https://github.com/squareetlabs/verifactu-sdk/issues
- Alberto Rial Barreiro - SquareetLabs
- Jacobo Cantorna Cigarrán - SquareetLabs
Si este paquete te ha sido útil, ¡no olvides darle una estrella en GitHub! ⭐