⭐ Found this useful? Give it a star on GitHub! It helps us maintain and improve the project.
Symfony bundle for Spanish Veri*Factu compliance (RD 1007/2023) — SHA-256 hash chains, billing record XML, AEAT QR codes, validation, and submission hooks for SIF-compatible invoicing systems.
- ✅ Hash chain (SHA-256): AEAT-compliant fingerprint calculation and encadenamiento
- ✅ Billing record XML: Registro de facturación generation (Alta / Anulación)
- ✅ QR codes: ISO/IEC 18004 QR with AEAT verification URL and mandatory legend
- ✅ Business rules validation: NIF/CIF/NIE, dates, amounts, invoice types
- ✅ Symfony events: Before/after generation and AEAT submission hooks
- ✅ Console commands: Validate records and verify hashes from CLI
- ✅ Twig helpers:
verifactu_qr_data_uri,verifactu_qr_url,verifactu_legend - ✅ Pluggable storage:
HashChainRepositoryInterfacewith in-memory and Doctrine backends - ✅ AEAT SOAP client: mTLS submission to sandbox/production with client certificate
- ✅ XAdES signing: No-Veri*Factu modalidad with PKCS#12/PEM certificates
- ✅ XSD validation: Official AEAT schemas shipped with the bundle
- ✅ Nowo integration:
InvoiceDraft+InvoiceToBillingRecordMapperfor ERP adoption - ✅ Sandbox CLI:
nowo:verifactu:submit-sandboxsmoke-test command - ✅ VeriFactu and No-VeriFactu modes: Configuration-driven behaviour
- ✅ i18n: Validation messages in 7 locales (en, es, it, fr, pt, de, nl)
composer require nowo-tech/verifactu-bundleRegister the bundle in config/bundles.php:
<?php
return [
// ...
Nowo\VerifactuBundle\NowoVerifactuBundle::class => ['all' => true],
];See docs/USAGE.md for the full API. Quick example:
use Nowo\VerifactuBundle\Model\BillingRecord;
use Nowo\VerifactuBundle\Model\RecordType;
use Nowo\VerifactuBundle\Service\BillingRecordProcessor;
/** @var BillingRecordProcessor $processor */
$processor = $container->get('nowo_verifactu.service.billing_record_processor');
$result = $processor->process(new BillingRecord(
RecordType::Alta,
issuerNif: '89890001K',
invoiceSeriesNumber: 'FAC-2026-001',
issueDate: '09-07-2026',
invoiceType: 'F1',
totalTaxAmount: '21.00',
totalAmount: '121.00',
generatedAt: '2026-07-09T16:00:00+02:00',
), submitToAeat: true);
if ([] === $result['errors']) {
echo $result['record']->hash;
echo $result['record']->xml;
}Symfony 8 demo with FrankenPHP:
make -C demo/symfony8 up
# Open http://localhost:8010See docs/DEMO-FRANKENPHP.md for worker mode and AEAT SOAP timeout hierarchy.
This bundle is FrankenPHP worker mode friendly when AEAT SOAP timeouts stay below PHP and reverse-proxy write deadlines (REQ-RUNTIME-001).
- Installation
- Configuration
- Usage
- Contributing
- Code of Conduct
- Changelog
- Upgrading
- Release process
- Security policy
- Engram (MCP memory)
- Spec-driven development
- GitHub Spec Kit
| Stack | Coverage | How to run |
|---|---|---|
| PHP | 100% lines (src/) |
make test-coverage / composer test-coverage |
| TypeScript / JavaScript | N/A | — |
| Python | N/A | — |
CI enforces PHP coverage at 100% (Clover elements / line gate via .scripts/coverage-check-100.php).
make up && make install && make test
make test-coverage
make release-checkThis bundle is released under the MIT License.
Created by Nowo.tech