PHP code example of squareetlabs / laravel-verifactu

1. Go to this page and download the library: Download squareetlabs/laravel-verifactu library. Choose the download type require.

2. Extract the ZIP file and open the index.php.

3. Add this code to the index.php.
    
        
<?php
require_once('vendor/autoload.php');

/* Start to develop here. Best regards https://php-download.com/ */

    

squareetlabs / laravel-verifactu example snippets


return [
    'enabled' => true,
    'system_id' => env('VERIFACTU_SYSTEM_ID', '01'),
    'default_currency' => 'EUR',
    'issuer' => [
        'name' => env('VERIFACTU_ISSUER_NAME', ''),
        'vat' => env('VERIFACTU_ISSUER_VAT', ''),
    ],
    
    // Modo Verifactu (true) o NO Verifactu/Requerimiento (false)
    'verifactu_mode' => env('VERIFACTU_MODE', true),
    
    // Parámetros del Sistema Informático
    'tipo_uso_posible_solo_verifactu' => env('VERIFACTU_TIPO_USO_SOLO_VF', 'N'),
    'tipo_uso_posible_multi_ot' => env('VERIFACTU_TIPO_USO_MULTI_OT', 'S'),
    'indicador_multiples_ot' => env('VERIFACTU_INDICADOR_MULTI_OT', 'N'),
    
    // Define si se cargan las migraciones del paquete (por defecto false)
    'load_migrations' => env('VERIFACTU_LOAD_MIGRATIONS', false),
];

use Squareetlabs\VeriFactu\Services\AeatClientFactory;
use Squareetlabs\VeriFactu\Services\AeatSubmissionResponse;

$client = app(AeatClientFactory::class)->make(
    issuer: ['name' => $company->legal_name, 'vat' => $company->tax_id],
    representative: config('verifactu.representative'),
);

// Huella y timestamp precalculados de tu propia cadena (ver $record):
$result = $client->sendInvoice($invoice, $previous, [
    'hash' => $record->hash,
    'generated_at' => $record->generated_at,
]);

// Respuesta tipada: EstadoEnvio, CSV y errores por línea ya parseados
$response = AeatSubmissionResponse::fromClientResult($result);

if ($response->accepted()) {
    // EstadoEnvio = Correcto; $response->csv disponible
} else {
    Log::warning('AEAT rechazo', ['error' => $response->firstError()]);
}

$this->app->bind(
    \Squareetlabs\VeriFactu\Contracts\CertificateProvider::class,
    \App\Services\MyCertificateStore::class,
);

return [
    'enabled' => env('VERIFACTU_ENABLED', true),
    'system_id' => env('VERIFACTU_SYSTEM_ID', '01'),
    'default_currency' => env('VERIFACTU_DEFAULT_CURRENCY', 'EUR'),
    
    'issuer' => [
        'name' => env('VERIFACTU_ISSUER_NAME', ''),
        'vat' => env('VERIFACTU_ISSUER_VAT', ''),
    ],
    
    'verifactu_mode' => env('VERIFACTU_MODE', true),
    
    'sistema_informatico' => [
        'tipo_uso_posible_solo_verifactu' => env('VERIFACTU_TIPO_USO_SOLO_VF', 'N'),
        'tipo_uso_posible_multi_ot' => env('VERIFACTU_TIPO_USO_MULTI_OT', 'S'),
        'indicador_multiples_ot' => env('VERIFACTU_INDICADOR_MULTI_OT', 'N'),
    ],
    
    'load_migrations' => env('VERIFACTU_LOAD_MIGRATIONS', false),
];

use Squareetlabs\VeriFactu\Contracts\VeriFactuInvoice;
use Illuminate\Database\Eloquent\Model;

class Invoice extends Model implements VeriFactuInvoice
{
    // Implementa los métodos requeridos por la interfaz
    public function getInvoiceNumber(): string
    {
        return $this->invoice_number; // Tu campo personalizado
    }
    
    // ... resto de métodos
}

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']));
}

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,
]);

$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,
]);

$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
]);

use Squareetlabs\VeriFactu\Enums\RectificativeType;

$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,
    'rectificative_type' => 'S', // S=Sustitución, I=Diferencia
    'rectified_invoices' => json_encode(['INV-001', 'INV-002']),
    'rectification_amount' => json_encode(['base' => -50.00, 'tax' => -10.50, 'total' => -60.50]),
]);

$invoice = Invoice::create([
    'number' => 'INV-EXP-001',
    'date' => '2024-07-01',
    'customer_name' => 'Foreign Customer Ltd',
    'customer_tax_id' => null,
    'customer_country' => 'GB',
    'issuer_name' => 'Issuer S.A.',
    'issuer_tax_id' => 'B87654321',
    'amount' => 500.00,
    'tax' => 0.00,  // Exportaciones exentas de IVA
    'total' => 500.00,
    'type' => InvoiceType::EXPORT,
]);

// Primera factura de la cadena
$firstInvoice = Invoice::create([
    'number' => 'INV-001',
    'date' => '2024-07-01',
    'is_first_invoice' => true,
    'amount' => 100.00,
    'tax' => 21.00,
    'total' => 121.00,
    // ... otros campos
]);

// Segunda factura enlazada
$secondInvoice = Invoice::create([
    'number' => 'INV-002',
    'date' => '2024-07-02',
    'is_first_invoice' => false,
    'previous_invoice_number' => 'INV-001',
    'previous_invoice_date' => '2024-07-01',
    'previous_invoice_hash' => $firstInvoice->hash,
    'amount' => 150.00,
    'tax' => 31.50,
    'total' => 181.50,
    // ... otros campos
]);

$invoice = Invoice::create([
    'number' => 'INV-SUB-001',
    'date' => '2024-07-01',
    'is_subsanacion' => true,
    'rejected_invoice_number' => 'INV-REJECTED-001',
    'rejection_date' => '2024-06-30',
    'amount' => 100.00,
    'tax' => 21.00,
    'total' => 121.00,
    // ... otros campos
]);

$invoice = Invoice::create([
    'number' => 'INV-002',
    'is_first_invoice' => false,                    // false si no es la primera
    'previous_invoice_number' => 'INV-001',         // Número de factura anterior
    'previous_invoice_date' => '2024-07-01',        // Fecha de factura anterior
    'previous_invoice_hash' => 'ABC123...',          // Hash de factura anterior
    // ... otros campos
]);

$invoice = Invoice::create([
    'number' => 'INV-RECT-001',
    'type' => InvoiceType::RECTIFICATIVE_R1,
    'rectificative_type' => 'I',                    // 'S' = Sustitución, 'I' = Diferencia
    'rectified_invoices' => [                       // Array de facturas rectificadas
        'INV-001',
        'INV-002'
    ],
    'rectification_amount' => [                     // Importes de rectificación
        'base' => -50.00,
        'tax' => -10.50,
        'total' => -60.50
    ],
    // ... otros campos
]);

$invoice = Invoice::create([
    'number' => 'INV-SUBS-001',
    'is_subsanacion' => true,                       // Indica que es subsanación
    'rejected_invoice_number' => 'INV-REJECTED-001', // Número de factura rechazada
    'rejection_date' => '2024-06-30',              // Fecha de rechazo
    // ... otros campos
]);

$invoice = Invoice::create([
    'number' => 'INV-001',
    'numero_instalacion' => 'INST-001',            // Identificador de instalación
    // ... otros campos
]);

$invoice->update([
    'aeat_status' => 'ACEPTADA',                   // Estado de respuesta AEAT
    'aeat_response_code' => '0',                   // Código de respuesta
    'aeat_response_message' => 'Registro aceptado', // Mensaje de respuesta
    'aeat_registration_date' => now(),            // Fecha de registro en AEAT
    'aeat_csv' => 'CSV-123456789',                // CSV de registro
    'has_aeat_warnings' => false,                 // Si tiene advertencias
]);

$invoice = Invoice::create([
    'number' => 'INV-001',
    'operation_date' => '2024-07-01',              // Fecha de operación (si difiere de date)
    'tax_period' => '01',                         // Período fiscal (01, 02, 0A, etc.)
    'external_reference' => 'REF-EXT-123',        // Referencia externa opcional
    'description' => 'Descripción de la operación',
    'status' => 'draft',                          // Estado interno: draft, sent, accepted, rejected
    // ... otros campos
]);

use Squareetlabs\VeriFactu\Models\Invoice;
use Squareetlabs\VeriFactu\Models\Recipient;
use Squareetlabs\VeriFactu\Enums\ForeignIdType;

$invoice = Invoice::create([
    'number' => 'INV-INT-001',
    'date' => '2024-07-01',
    'customer_name' => 'Foreign Customer Ltd',
    'customer_tax_id' => null,                     // Sin NIF para extranjeros
    'customer_country' => 'GB',                    // Código ISO del país
    // ... otros campos
]);

// Crear recipient con identificador extranjero
$recipient = Recipient::create([
    'invoice_id' => $invoice->id,
    'name' => 'Foreign Customer Ltd',
    'tax_id' => 'GB123456789',                     // VAT number del país
    'country' => 'GB',
    'id_type' => ForeignIdType::VAT_NUMBER,       // Tipo de identificador
]);

use Squareetlabs\VeriFactu\Models\Invoice;
use Squareetlabs\VeriFactu\Enums\InvoiceType;

// Factura original
$originalInvoice = Invoice::create([
    'number' => 'INV-001',
    'date' => '2024-07-01',
    'amount' => 100.00,
    'tax' => 21.00,
    'total' => 121.00,
    'type' => InvoiceType::STANDARD,
    // ... otros campos
]);

// Factura rectificativa por diferencia
$rectificativeInvoice = Invoice::create([
    'number' => 'INV-RECT-001',
    'date' => '2024-07-15',
    'type' => InvoiceType::RECTIFICATIVE_R1,
    'rectificative_type' => 'I',                   // I = Diferencia
    'rectified_invoices' => ['INV-001'],          // Facturas rectificadas
    'rectification_amount' => [
        'base' => -20.00,                          // Diferencia en base
        'tax' => -4.20,                            // Diferencia en impuesto
        'total' => -24.20                          // Diferencia total
    ],
    'amount' => -20.00,                            // Importe negativo
    'tax' => -4.20,
    'total' => -24.20,
    // ... otros campos
]);

$invoice = Invoice::create([
    'number' => 'INV-001',
    'date' => '2024-07-01',
    'numero_instalacion' => 'INST-001',            // Identificador de instalación
    'issuer_name' => 'Empresa Principal S.A.',
    'issuer_tax_id' => 'B12345678',
    // ... otros campos
]);

// Otra factura de diferente instalación pero mismo emisor
$invoice2 = Invoice::create([
    'number' => 'INV-002',
    'date' => '2024-07-01',
    'numero_instalacion' => 'INST-002',           // Diferente instalación
    'issuer_name' => 'Empresa Principal S.A.',
    'issuer_tax_id' => 'B12345678',                // Mismo NIF emisor
    // ... otros campos
]);

// Primera factura de la cadena
$first = Invoice::create([
    'number' => 'INV-001',
    'date' => '2024-07-01',
    'is_first_invoice' => true,
    'amount' => 100.00,
    'tax' => 21.00,
    'total' => 121.00,
    // ... otros campos
]);

// Calcular hash (se hace automáticamente si está configurado)
$first->hash = HashHelper::generateInvoiceHash([...])['hash'];
$first->save();

// Segunda factura enlazada
$second = Invoice::create([
    'number' => 'INV-002',
    'date' => '2024-07-02',
    'is_first_invoice' => false,
    'previous_invoice_number' => $first->number,
    'previous_invoice_date' => $first->date,
    'previous_invoice_hash' => $first->hash,
    'amount' => 150.00,
    'tax' => 31.50,
    'total' => 181.50,
    // ... otros campos
]);

// Tercera factura enlazada
$third = Invoice::create([
    'number' => 'INV-003',
    'date' => '2024-07-03',
    'is_first_invoice' => false,
    'previous_invoice_number' => $second->number,
    'previous_invoice_date' => $second->date,
    'previous_invoice_hash' => $second->hash,
    'amount' => 200.00,
    'tax' => 42.00,
    'total' => 242.00,
    // ... otros campos
]);

// Factura original que fue rechazada
$rejectedInvoice = Invoice::create([
    'number' => 'INV-REJECTED-001',
    'date' => '2024-06-30',
    'aeat_status' => 'RECHAZADA',
    'aeat_response_code' => '1',
    'aeat_response_message' => 'Error en validación',
    // ... otros campos
]);

// Factura de subsanación
$subsanacionInvoice = Invoice::create([
    'number' => 'INV-SUBS-001',
    'date' => '2024-07-01',
    'is_subsanacion' => true,
    'rejected_invoice_number' => 'INV-REJECTED-001',
    'rejection_date' => '2024-06-30',
    // Corregir los datos que causaron el rechazo
    'amount' => 100.00,                             // Corregido
    'tax' => 21.00,
    'total' => 121.00,
    // ... otros campos corregidos
]);

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);
        
        // Actualizar campos de estado si el envío fue exitoso
        if ($result['status'] === 'success') {
            $invoice->update([
                'aeat_status' => $result['aeat_status'] ?? 'ENVIADA',
                'aeat_response_code' => $result['code'] ?? null,
                'aeat_response_message' => $result['message'] ?? null,
                'aeat_registration_date' => $result['registration_date'] ?? null,
                'aeat_csv' => $result['csv'] ?? null,
                'has_aeat_warnings' => $result['has_warnings'] ?? false,
            ]);
        }
        
        return response()->json($result, $result['status'] === 'success' ? 200 : 422);
    }
}

use Squareetlabs\VeriFactu\Models\Invoice;

// Buscar facturas por estado
$acceptedInvoices = Invoice::where('aeat_status', 'ACEPTADA')->get();
$rejectedInvoices = Invoice::where('aeat_status', 'RECHAZADA')->get();
$pendingInvoices = Invoice::whereNull('aeat_status')->get();

// Buscar facturas con advertencias
$invoicesWithWarnings = Invoice::where('has_aeat_warnings', true)->get();

// Buscar facturas por CSV
$invoice = Invoice::where('aeat_csv', 'CSV-123456789')->first();

// Obtener información completa de una factura
$invoice = Invoice::find(1);
echo "Estado: " . $invoice->aeat_status;
echo "Código: " . $invoice->aeat_response_code;
echo "Mensaje: " . $invoice->aeat_response_message;
echo "CSV: " . $invoice->aeat_csv;
echo "Fecha registro: " . $invoice->aeat_registration_date;

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);
}

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',
]);

use Squareetlabs\VeriFactu\Helpers\QrUrlHelper;
use Squareetlabs\VeriFactu\Models\Invoice;

// Obtener la factura
$invoice = Invoice::find(1);

// Generar URL para QR en modo VERIFACTU (producción)
$qrUrl = QrUrlHelper::build(
    $invoice, 
    'B12345678',  // NIF del emisor
    true,         // true = producción, false = pruebas
    true          // true = VERIFACTU, false = NO VERIFACTU
);

// Resultado: https://www2.agenciatributaria.gob.es/wlpl/TIKE-CONT/ValidarQR?nif=B12345678&numserie=FAC-001&fecha=15-01-2024&importe=121.00

// Usa la configuración por defecto del modo
$qrUrl = QrUrlHelper::build($invoice, 'B12345678', true);

use Squareetlabs\VeriFactu\Helpers\QrUrlHelper;
use Squareetlabs\VeriFactu\Models\Invoice;

class InvoiceQrController extends Controller
{
    public function generateQr($invoiceId)
    {
        $invoice = Invoice::findOrFail($invoiceId);
        $issuerVat = config('verifactu.issuer.vat');
        $isProduction = app()->environment('production');
        
        $qrUrl = QrUrlHelper::build($invoice, $issuerVat, $isProduction);
        
        // Generar el código QR usando alguna librería (ej: SimpleSoftwareIO/simple-qrcode)
        // $qrCode = QrCode::size(300)->generate($qrUrl);
        
        return response()->json([
            'url' => $qrUrl,
            // 'qr_image' => $qrCode
        ]);
    }
}

// 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) {}
}

use App\Events\InvoiceSentToAeat;

// ... después de enviar a AEAT
InvoiceSentToAeat::dispatch($invoice, $result);

// 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,
        ]);
    }
}

protected $listen = [
    \App\Events\InvoiceSentToAeat::class => [
        \App\Listeners\LogAeatResponse::class,
    ],
];

// 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';
    }
}

protected $policies = [
    \Squareetlabs\VeriFactu\Models\Invoice::class => \App\Policies\InvoicePolicy::class,
];

public function update(Request $request, Invoice $invoice)
{
    $this->authorize('update', $invoice);
    // ...
}

// 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.');
    }
}

$user->notify(new \App\Notifications\InvoiceSentNotification($invoice));

use Squareetlabs\VeriFactu\Models\Invoice;
use App\Jobs\SendInvoiceToAeatJob;

// Despacha el job a la cola
SendInvoiceToAeatJob::dispatch($invoice->id);

use Illuminate\Contracts\Queue\ShouldQueue;

class SendInvoiceToAeatJob implements ShouldQueue
{
    // ...
}

   use OwenIt\Auditing\Contracts\Auditable;

   class Invoice extends Model implements Auditable
   {
       use \OwenIt\Auditing\Auditable;
       // ...
   }
   

   $audits = $invoice->audits;
   
bash
php artisan vendor:publish --provider="Squareetlabs\VeriFactu\Providers\VeriFactuServiceProvider"
php artisan migrate
bash
   php artisan migrate
   
bash
   php artisan verifactu:make-adapter Invoice
   
bash
php artisan queue:work
bash
php artisan test
# o
vendor/bin/phpunit