squareetlabs / laravel-verifactu
Paquete Laravel para gestión y registro de facturación electrónica VeriFactu. Compatible con Laravel 10 a 12.x
Requires
- php: >=8.1
- illuminate/support: ^10.0|^11.0|^12.0
Requires (Dev)
- orchestra/testbench: ^8.0|^9.0|^10.4
- phpunit/phpunit: ^10.0|^11.5
README
Paquete Laravel 10/11/12 para gestión y registro de facturación electrónica VeriFactu
✨ Características principales
- 📄 Modelos Eloquent para invoices, breakdowns y recipients
- 🏷️ Enum types para campos fiscales (invoice type, tax type, regime, etc.)
- 🛠️ Helpers para operaciones de fecha, string y hash
- 🏛️ Servicio AEAT client (configurable e inyectable)
- ✅ Form Requests para validación
- 🔄 API Resources para respuestas RESTful
- 🧪 Factories y tests unitarios para todos los componentes core
- 🔌 Listo para extensión y uso en producción
📦 Instalación
composer require squareetlabs/laravel-verifactu
Publica la configuración y migraciones:
php artisan vendor:publish --provider="Squareetlabs\VeriFactu\Providers\VeriFactuServiceProvider"
php artisan migrate
⚙️ Configuración
Edita tu archivo .env
o config/verifactu.php
según tus necesidades:
return [ 'enabled' => true, 'default_currency' => 'EUR', 'issuer' => [ 'name' => env('VERIFACTU_ISSUER_NAME', ''), 'vat' => env('VERIFACTU_ISSUER_VAT', ''), ], // ... ];
🚀 Uso rápido
Crear una Invoice (Ejemplo de Controller)
use Squareetlabs\VeriFactu\Http\Requests\StoreInvoiceRequest; use Squareetlabs\VeriFactu\Models\Invoice; use Squareetlabs\VeriFactu\Http\Resources\InvoiceResource; public function store(StoreInvoiceRequest $request) { $invoice = Invoice::create($request->validated()); // Opcionalmente puedes asociar breakdowns y recipients // $invoice->breakdowns()->createMany([...]); // $invoice->recipients()->createMany([...]); return new InvoiceResource($invoice->load(['breakdowns', 'recipients'])); }
🧾 Ejemplos de tipos de Invoice
A continuación, ejemplos de cómo crear cada tipo de invoice usando el modelo y enums:
Factura estándar
use Squareetlabs\VeriFactu\Models\Invoice; use Squareetlabs\VeriFactu\Enums\InvoiceType; $invoice = Invoice::create([ 'number' => 'INV-STD-001', 'date' => '2024-07-01', 'customer_name' => 'Standard Customer', 'customer_tax_id' => 'C12345678', 'issuer_name' => 'Issuer S.A.', 'issuer_tax_id' => 'B87654321', 'amount' => 100.00, 'tax' => 21.00, 'total' => 121.00, 'type' => InvoiceType::STANDARD, ]);
Factura simplificada
$invoice = Invoice::create([ 'number' => 'INV-SIMP-001', 'date' => '2024-07-01', 'customer_name' => 'Simplified Customer', 'customer_tax_id' => 'C87654321', 'issuer_name' => 'Issuer S.A.', 'issuer_tax_id' => 'B87654321', 'amount' => 50.00, 'tax' => 10.50, 'total' => 60.50, 'type' => InvoiceType::SIMPLIFIED, ]);
Factura de sustitución
$invoice = Invoice::create([ 'number' => 'INV-SUB-001', 'date' => '2024-07-01', 'customer_name' => 'Substitute Customer', 'customer_tax_id' => 'C11223344', 'issuer_name' => 'Issuer S.A.', 'issuer_tax_id' => 'B87654321', 'amount' => 80.00, 'tax' => 16.80, 'total' => 96.80, 'type' => InvoiceType::SUBSTITUTE, // Puedes añadir aquí la relación con facturas sustituidas si implementas la lógica ]);
Factura rectificativa (R1)
$invoice = Invoice::create([ 'number' => 'INV-RECT-001', 'date' => '2024-07-01', 'customer_name' => 'Rectified Customer', 'customer_tax_id' => 'C55667788', 'issuer_name' => 'Issuer S.A.', 'issuer_tax_id' => 'B87654321', 'amount' => 120.00, 'tax' => 25.20, 'total' => 145.20, 'type' => InvoiceType::RECTIFICATIVE_R1, // Puedes añadir aquí la relación con facturas rectificadas y el motivo si implementas la lógica ]);
⚠️ Nota: Para facturas rectificativas y sustitutivas, si implementas los campos y relaciones adicionales (como facturas rectificadas/sustituidas, tipo de rectificación, importe de rectificación), deberás añadirlos en el array de creación.
📤 Envío de Invoice a AEAT (Ejemplo de Controller)
use Illuminate\Http\Request; use Squareetlabs\VeriFactu\Services\AeatClient; use Squareetlabs\VeriFactu\Models\Invoice; class InvoiceAeatController extends Controller { public function send(Request $request, AeatClient $aeatClient, $invoiceId) { $invoice = Invoice::with(['breakdowns', 'recipients'])->findOrFail($invoiceId); $result = $aeatClient->sendInvoice($invoice); // Puedes registrar el resultado, lanzar eventos, etc. return response()->json($result, $result['status'] === 'success' ? 200 : 422); } }
🔒 Nota: Protege este endpoint con autenticación/autorización adecuada.
📄 El resultado incluirá el XML enviado y recibido, útil para depuración.
❌ Si el certificado no es válido o hay error de validación, el array tendrá 'status' => 'error' y 'message'.
🧩 Validación y creación de Breakdown (Ejemplo de Controller)
use Squareetlabs\VeriFactu\Http\Requests\StoreBreakdownRequest; use Squareetlabs\VeriFactu\Models\Breakdown; public function storeBreakdown(StoreBreakdownRequest $request) { $breakdown = Breakdown::create($request->validated()); return response()->json($breakdown); }
🛠️ Uso de Helpers
use Squareetlabs\VeriFactu\Helpers\DateTimeHelper; use Squareetlabs\VeriFactu\Helpers\StringHelper; use Squareetlabs\VeriFactu\Helpers\HashHelper; $dateIso = DateTimeHelper::formatIso8601('2024-01-01 12:00:00'); $sanitized = StringHelper::sanitize(' &Hello <World> '); $hash = HashHelper::generateInvoiceHash([ 'issuer_tax_id' => 'A12345678', 'invoice_number' => 'INV-001', 'issue_date' => '2024-01-01', 'invoice_type' => 'F1', 'total_tax' => '21.00', 'total_amount' => '121.00', 'previous_hash' => '', 'generated_at' => '2024-01-01T12:00:00+01:00', ]);
⚡ Uso avanzado
📢 Integración de eventos y listeners
Puedes disparar eventos cuando se crean, actualizan o envían invoices a AEAT. Ejemplo:
// app/Events/InvoiceSentToAeat.php namespace App\Events; use Squareetlabs\VeriFactu\Models\Invoice; use Illuminate\Foundation\Events\Dispatchable; use Illuminate\Queue\SerializesModels; class InvoiceSentToAeat { use Dispatchable, SerializesModels; public function __construct(public Invoice $invoice, public array $aeatResponse) {} }
Despacha el evento tras el envío:
use App\Events\InvoiceSentToAeat; // ... después de enviar a AEAT InvoiceSentToAeat::dispatch($invoice, $result);
Crea un listener para notificaciones o logging:
// app/Listeners/LogAeatResponse.php namespace App\Listeners; use App\Events\InvoiceSentToAeat; use Illuminate\Support\Facades\Log; class LogAeatResponse { public function handle(InvoiceSentToAeat $event) { Log::info('AEAT response', [ 'invoice_id' => $event->invoice->id, 'response' => $event->aeatResponse, ]); } }
Registra tu evento y listener en EventServiceProvider
:
protected $listen = [ \App\Events\InvoiceSentToAeat::class => [ \App\Listeners\LogAeatResponse::class, ], ];
🔐 Políticas de autorización
Puedes restringir el acceso a invoices usando policies de Laravel:
// app/Policies/InvoicePolicy.php namespace App\Policies; use App\Models\User; use Squareetlabs\VeriFactu\Models\Invoice; class InvoicePolicy { public function view(User $user, Invoice $invoice): bool { return $user->id === $invoice->user_id; } public function update(User $user, Invoice $invoice): bool { return $user->id === $invoice->user_id && $invoice->status === 'draft'; } }
Registra la policy en AuthServiceProvider
:
protected $policies = [ \Squareetlabs\VeriFactu\Models\Invoice::class => \App\Policies\InvoicePolicy::class, ];
Úsala en tu controller:
public function update(Request $request, Invoice $invoice) { $this->authorize('update', $invoice); // ... }
📣 Integración de notificaciones
Puedes notificar a usuarios o admins cuando una invoice se envía o falla:
// app/Notifications/InvoiceSentNotification.php namespace App\Notifications; use Illuminate\Bus\Queueable; use Illuminate\Notifications\Notification; use Squareetlabs\VeriFactu\Models\Invoice; class InvoiceSentNotification extends Notification { use Queueable; public function __construct(public Invoice $invoice) {} public function via($notifiable) { return ['mail']; } public function toMail($notifiable) { return (new \Illuminate\Notifications\Messages\MailMessage) ->subject('Invoice Sent to AEAT') ->line('Invoice #' . $this->invoice->number . ' was sent to AEAT successfully.'); } }
Despacha la notificación en tu job o listener:
$user->notify(new \App\Notifications\InvoiceSentNotification($invoice));
🕒 Integración con colas (queues)
Puedes enviar invoices a AEAT de forma asíncrona usando colas:
use Squareetlabs\VeriFactu\Models\Invoice; use App\Jobs\SendInvoiceToAeatJob; // Despacha el job a la cola SendInvoiceToAeatJob::dispatch($invoice->id);
En tu job, implementa ShouldQueue
:
use Illuminate\Contracts\Queue\ShouldQueue; class SendInvoiceToAeatJob implements ShouldQueue { // ... }
Configura tu conexión de cola en .env
y ejecuta el worker:
php artisan queue:work
📝 Auditoría
Puedes usar paquetes como owen-it/laravel-auditing para auditar cambios en invoices:
- Instala el paquete:
composer require owen-it/laravel-auditing
- Añade el contrato
\OwenIt\Auditing\Contracts\Auditable
a tu modelo:use OwenIt\Auditing\Contracts\Auditable; class Invoice extends Model implements Auditable { use \OwenIt\Auditing\Auditable; // ... }
- Ahora todos los cambios en invoices serán auditados automáticamente. Puedes ver los logs:
$audits = $invoice->audits;
🧪 Testing
Ejecuta todos los tests unitarios:
php artisan test # o vendor/bin/phpunit
🤝 Contribuir
Las contribuciones son bienvenidas. Por favor:
- Fork el proyecto
- Crea una rama para tu feature
- Commit tus cambios
- Push a la rama
- Abre un Pull Request
📄 Licencia
Este paquete es open-source bajo la Licencia MIT.
🆘 Soporte
- Documentación técnica: https://sede.agenciatributaria.gob.es/Sede/iva/sistemas-informaticos-facturacion-verifactu/informacion-tecnica.html
- Issues: https://github.com/squareetlabs/LaravelVerifactu/issues
👥 Autores
- Alberto Rial Barreiro - SquareetLabs
- Jacobo Cantorna Cigarrán - SquareetLabs
⭐ Si este paquete te ha sido útil, ¡no olvides darle una estrella en GitHub!