verifactu-laravel maintained by krato
Verifactu Laravel
SDK Laravel para cumplimiento Verifactu. Minimal. Desacoplado. Production-ready.
DX inspirada en Stripe PHP SDK: configuración mínima, contratos claros, funciona desde el primer composer require.
// Enviar una factura a AEAT en 3 líneas
$invoice = new InvoiceRecord($tuFactura);
$result = Verifactu::submit($invoice);
// → SubmissionResult { status: Accepted, csv: "CSV-001" }
Lo que hace
- Registros de facturación Verifactu (alta F1/F2)
- Hash SHA-256 encadenado según especificación AEAT
- Envío síncrono y asíncrono (queues) a AEAT
- Autenticación por certificado digital (PKCS#12)
- Persistencia de requests/responses y cadena de hashes
- Multi-tenant con
forTenant() - Testing helpers completos
Lo que NO hace (deliberadamente)
- NO genera facturas — solo registros de facturación Verifactu
- NO tiene UI/dashboard — backend puro
- NO impone modelos Eloquent — solo contratos que tú implementas
- NO almacena datos de facturas — solo hashes, envíos y respuestas AEAT
Requisitos
- PHP 8.1+
- Laravel 10, 11 o 12
- Extensiones:
curl,dom,openssl - Certificado digital (.p12/.pfx) para envíos a AEAT
Instalación
composer require krato/verifactu-laravel
php artisan verifactu:install
Esto publica la configuración, las migraciones y ejecuta las migraciones.
Añade a tu .env:
VERIFACTU_ENV=testing
VERIFACTU_SIF_NAME="Tu Software"
VERIFACTU_SIF_NIF=B12345678
VERIFACTU_SIF_VERSION=1.0.0
VERIFACTU_CERT_PATH=/path/to/certificate.p12
VERIFACTU_CERT_PASSWORD=tu-password
Uso rápido
1. Implementa el contrato InvoiceRecord
El paquete no depende de tus modelos. Tú implementas el contrato:
use Krato\Verifactu\Contracts\InvoiceRecord;
use Krato\Verifactu\DTOs\InvoiceIdentifier;
use Krato\Verifactu\DTOs\Issuer;
use Krato\Verifactu\DTOs\Recipient;
use Krato\Verifactu\DTOs\TaxBreakdown;
use Krato\Verifactu\Enums\InvoiceType;
use Krato\Verifactu\Enums\TaxRegime;
use Krato\Verifactu\Enums\TaxType;
class MyInvoiceRecord implements InvoiceRecord
{
public function __construct(private Invoice $invoice) {}
public function getIdentifier(): InvoiceIdentifier
{
return new InvoiceIdentifier(
series: $this->invoice->series,
number: $this->invoice->number,
issueDate: $this->invoice->issue_date,
);
}
public function getIssuer(): Issuer
{
return new Issuer(
nif: config('verifactu.sif.nif'),
name: config('verifactu.sif.name'),
);
}
public function getRecipient(): ?Recipient
{
return new Recipient(
nif: $this->invoice->customer->nif,
name: $this->invoice->customer->name,
);
}
public function getInvoiceType(): InvoiceType
{
return InvoiceType::F1;
}
public function getDescription(): string
{
return $this->invoice->description;
}
public function getTaxBreakdowns(): array
{
return [
new TaxBreakdown(
taxType: TaxType::IVA,
taxRegime: TaxRegime::General,
taxBase: 1000.00,
taxRate: 21.00,
taxAmount: 210.00,
),
];
}
public function getTotalAmount(): float
{
return $this->invoice->total;
}
public function getIssueDate(): \DateTimeInterface
{
return $this->invoice->issue_date;
}
}
2. Envía a AEAT
use Krato\Verifactu\Facades\Verifactu;
// Síncrono
$result = Verifactu::submit(new MyInvoiceRecord($invoice));
if ($result->isAccepted()) {
// $result->csv contiene el CSV de AEAT
}
// Asíncrono (via queue)
Verifactu::dispatch(new MyInvoiceRecord($invoice));
API
Verifactu::submit($invoice)
Envía un registro de facturación a AEAT de forma síncrona. Devuelve SubmissionResult.
$result = Verifactu::submit($invoice);
$result->status; // SubmissionStatus enum
$result->csv; // string|null — CSV de AEAT
$result->xmlResponse; // string|null — XML completo de respuesta
$result->errors; // SubmissionError[] — errores si los hay
$result->isAccepted(); // bool
$result->isRejected(); // bool
Verifactu::dispatch($invoice)
Envía de forma asíncrona vía queue. Usa la configuración de verifactu.queue y verifactu.retry.
Verifactu::dispatch($invoice);
Verifactu::forTenant($nif)
Para aplicaciones multi-tenant. Devuelve una instancia configurada para ese NIF.
Verifactu::forTenant('B12345678')->submit($invoice);
Verifactu::forTenant('A87654321')->dispatch($invoice);
Requiere implementar CertificateResolver para resolver el certificado por NIF:
use Krato\Verifactu\Contracts\CertificateResolver;
use Krato\Verifactu\DTOs\CertificateCredentials;
class MyCertificateResolver implements CertificateResolver
{
public function resolve(string $nif): CertificateCredentials
{
$tenant = Tenant::where('nif', $nif)->firstOrFail();
return new CertificateCredentials(
path: $tenant->certificate_path,
password: $tenant->certificate_password,
);
}
}
// En un ServiceProvider:
$this->app->bind(CertificateResolver::class, MyCertificateResolver::class);
Testing
El paquete incluye helpers para testing sin enviar nada a AEAT:
use Krato\Verifactu\Facades\Verifactu;
use Krato\Verifactu\Testing\InvoiceRecordFactory;
it('submits invoice to AEAT', function () {
// Activa el modo fake
$fake = Verifactu::fake();
// Envía con la factory incluida
$invoice = InvoiceRecordFactory::make()
->withIdentifier('FA', '001')
->withIssuer('B12345678', 'Mi Empresa S.L.')
->withTotalAmount(1210.00);
Verifactu::submit($invoice);
// Assertions
$fake->assertSubmitted('FA-001');
$fake->assertSubmittedCount(1);
});
InvoiceRecordFactory
Factory fluida para crear facturas de test:
$invoice = InvoiceRecordFactory::make()
->withIdentifier('FA', '001', new DateTime('2027-01-15'))
->withIssuer('B12345678', 'Mi Empresa S.L.')
->withRecipient('A87654321', 'Cliente S.A.')
->withInvoiceType(InvoiceType::F2)
->withDescription('Servicios de consultoría')
->withTotalAmount(1210.00)
->withTaxBreakdowns([
new TaxBreakdown(TaxType::IVA, TaxRegime::General, 1000.00, 21.00, 210.00),
])
->withIssueDate(new DateTime('2027-01-15'));
FakeTransport
$fake = Verifactu::fake();
// Assertions disponibles
$fake->assertSubmitted('FA-001');
$fake->assertSubmittedCount(2);
$fake->assertNothingSubmitted();
$fake->submissions(); // array de envíos registrados
$fake->reset();
// Respuesta personalizada
$fake->respondWith($customXmlResponse);
FakeHashChainStore
Para tests unitarios de la cadena de hashes:
use Krato\Verifactu\Testing\FakeHashChainStore;
$store = new FakeHashChainStore;
$store->getLastHash('B12345678', 'FA'); // null (cadena vacía)
$store->append('B12345678', 'FA', $link);
$store->getChain('B12345678', 'FA'); // [HashChainLink]
Configuración
Publica el fichero de configuración:
php artisan vendor:publish --tag=verifactu-config
Entorno
// config/verifactu.php
'environment' => env('VERIFACTU_ENV', 'testing'), // 'production' o 'testing'
testing: sandbox AEAT (prewww1.aeat.es)production: producción AEAT (www1.agenciatributaria.gob.es)
Software de facturación (SIF)
'sif' => [
'name' => env('VERIFACTU_SIF_NAME', 'Mi Software'),
'nif' => env('VERIFACTU_SIF_NIF'),
'version' => env('VERIFACTU_SIF_VERSION', '1.0.0'),
'id' => env('VERIFACTU_SIF_ID'),
],
Certificado
'certificate' => [
'path' => env('VERIFACTU_CERT_PATH'), // Ruta al .p12/.pfx
'password' => env('VERIFACTU_CERT_PASSWORD'),
],
Colas
'queue' => [
'enabled' => env('VERIFACTU_QUEUE_ENABLED', true),
'connection' => env('VERIFACTU_QUEUE_CONNECTION', 'default'),
'queue' => env('VERIFACTU_QUEUE_NAME', 'verifactu'),
],
Reintentos
'retry' => [
'max_attempts' => env('VERIFACTU_RETRY_MAX', 3),
'backoff' => [60, 300, 900], // segundos entre reintentos
],
Stores personalizados
'bindings' => [
'hash_chain_store' => DatabaseHashChainStore::class, // o FileHashChainStore::class
'submission_store' => DatabaseSubmissionStore::class,
],
Puedes implementar tus propios stores implementando HashChainStore y SubmissionStore.
Tipos de factura
| Enum | Valor | Descripción |
|---|---|---|
InvoiceType::F1 |
F1 |
Factura completa (art. 6, 7.2 y 7.3 del RD 1619/2012) |
InvoiceType::F2 |
F2 |
Factura simplificada (art. 6.1.d y 7.1 del RD 1619/2012) |
Tipos de impuesto
| Enum | Valor | Descripción |
|---|---|---|
TaxType::IVA |
01 |
Impuesto sobre el Valor Añadido |
TaxType::IGIC |
02 |
Impuesto General Indirecto Canario |
TaxType::IPSI |
03 |
Impuesto sobre la Producción, Servicios e Importación |
Regímenes fiscales
| Enum | Valor | Descripción |
|---|---|---|
TaxRegime::General |
01 |
Régimen general |
TaxRegime::Export |
02 |
Exportación |
TaxRegime::SpecialGoods |
03 |
Bienes usados |
TaxRegime::InvestmentGold |
04 |
Oro de inversión |
TaxRegime::TravelAgencies |
05 |
Agencias de viaje |
TaxRegime::EntityGroups |
06 |
Grupos de entidades en IVA |
TaxRegime::CashBasis |
07 |
Criterio de caja |
TaxRegime::SimplifiedRegime |
11 |
Recargo de equivalencia |
TaxRegime::EqualizationCharge |
12 |
Régimen simplificado |
Arquitectura
src/
├── Contracts/ ← Interfaces que tú implementas
│ ├── InvoiceRecord
│ ├── CertificateResolver
│ ├── HashChainStore
│ └── SubmissionStore
├── DTOs/ ← Value objects inmutables
├── Enums/ ← InvoiceType, TaxType, TaxRegime, etc.
├── Hash/ ← SHA-256 hash chaining (spec AEAT)
├── Xml/ ← XML builder, SOAP envelope, response parser
├── Transport/ ← cURL client, endpoints, certificados
├── Support/ ← Implementaciones por defecto (DB, ficheros)
├── Testing/ ← FakeTransport, InvoiceRecordFactory
├── Jobs/ ← SubmitInvoiceRecord (async)
├── Commands/ ← verifactu:install
├── Facades/ ← Verifactu
├── VerifactuManager ← Orquestador principal
└── VerifactuServiceProvider
Flujo de envío
Tu app → Verifactu::submit($invoice)
1. ChainManager genera hash SHA-256 encadenado
2. RecordBuilder genera XML del registro
3. XsdValidator valida el XML
4. SubmissionEnvelope envuelve en SOAP
5. SubmissionStore guarda el request (ANTES de enviar)
6. SoapClient envía a AEAT (HTTPS + certificado)
7. ResponseParser parsea la respuesta
8. SubmissionStore guarda la respuesta
9. HashChainStore persiste el hash (solo si aceptada)
→ SubmissionResult devuelto
Roadmap
v0.1 — Core de envío (actual)
Alta de facturas (F1, F2), hash chaining, envío AEAT, persistencia de respuestas, async con queues, testing helpers.
v0.2 — Casos fiscales
Rectificativas (R1-R5), anulaciones, QR, eventos Laravel, retry automático.
v0.3 — Consultas y auditoría
Query API contra AEAT, auditoría cruzada, comandos operativos, exportación.
v1.0 — Estable
Cuando haya 10+ empresas en producción y 4 semanas sin bugs críticos.
Testing del paquete
composer test
Changelog
Ver CHANGELOG.
Licencia
MIT. Ver LICENSE.