g-efac/laravel

Integracion de Laravel para el SDK de G-eFac (facturacion electronica e-CF)

Maintainers

Package info

github.com/Walewsky/efac-laravel

pkg:composer/g-efac/laravel

Transparency log

Statistics

Installs: 1

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

v0.2.0 2026-08-08 02:56 UTC

This package is auto-updated.

Last update: 2026-08-08 03:07:41 UTC


README

Puente de Laravel para g-efac/sdk: registra EfacClient en el contenedor, expone la fachada Efac y traduce la configuración de Laravel (.env, caché) al SDK. No agrega API propia — es cableado.

¿Vas a implementar la emisión? Este README es la instalación. La Guía de integración es el manual completo: qué se envía, qué se recibe, idempotencia, estados, errores, reintentos, pruebas y una implementación de referencia de punta a punta.

Estabilidad: versión 0.x

/v1 es provisional — todavía no está congelada. Mientras siga así, este paquete se publica en 0.x y las versiones menores pueden romper compatibilidad: no hay promesa de semver hasta que la API se congele. Cuando se active el disparador de congelación, el paquete pasa a 1.0.0 y adopta semver en serio. El detalle completo del disparador y del compromiso de compatibilidad está documentado en el README de g-efac/sdk.

Instalación

composer require g-efac/laravel

Requiere Laravel 12. Laravel 11 no está soportado: cada release 11.x depende de correcciones de seguridad que solo existen en 12.x, así que instalar este paquete sobre Laravel 11 dejaría la aplicación expuesta a advisories ya resueltos en la versión siguiente.

Gracias al auto-discovery de Composer, instalar el paquete ya registra el service provider y el alias Efac. No hay que tocar config/app.php ni ningún otro archivo de arranque.

Configuración

Publica el archivo de configuración:

php artisan vendor:publish --tag=efac-config

Esto crea config/efac.php. Las siete opciones, todas configurables por variable de entorno:

Variable Config key Valor por defecto Significado
EFAC_BASE_URL efac.base_url https://api.g-efac.com URL base de la API
EFAC_CLIENT_ID efac.client_id — (obligatoria) Client id OAuth2 del emisor
EFAC_CLIENT_SECRET efac.secret — (obligatoria) Client secret OAuth2
EFAC_SCOPE efac.scope efac.v1 Scope OAuth2 solicitado
EFAC_CACHE_STORE efac.cache_store null (store por defecto) Store de caché de Laravel donde se guarda el access token
EFAC_CONNECT_TIMEOUT efac.connect_timeout 5 Timeout de conexión de Guzzle, en segundos
EFAC_TIMEOUT efac.timeout 30 Timeout de la petición completa de Guzzle, en segundos

Solo EFAC_CLIENT_ID y EFAC_CLIENT_SECRET no tienen valor por defecto. Si alguna falta o queda en blanco, el paquete lanza una RuntimeException clara — pero no durante el boot() del framework, sino la primera vez que se resuelve EfacClient del contenedor. Esto es deliberado: un fallo duro en el arranque rompería artisan en cualquier entorno sin credenciales configuradas — CI, un clone recién hecho, un contenedor corriendo migraciones — donde de todas formas nadie está llamando a la API.

Uso

Con la fachada Efac

use Efac\Laravel\Efac;
use Efac\Sdk\Enum\EcfTipo;
use Efac\Sdk\Enum\TaxTreatment;
use Efac\Sdk\Invoice\Invoice;
use Efac\Sdk\Invoice\Item;

$factura = Invoice::of(EcfTipo::CreditoFiscal31)
    ->seller('101000001', 'ACME SRL', 'Av. Principal 1')
    ->buyer('130000002', 'Cliente SA', email: 'cxp@cliente.do')
    ->addItem(Item::goods('Widget', qty: 2, unitPrice: 500.00, tax: TaxTreatment::Itbis18))
    ->build();

$resultado = Efac::invoices()->emit($factura, 'orden-1042');

echo $resultado->encf;      // E310000000001
echo $resultado->qrUrl;     // URL del timbre para la Representación Impresa

Con inyección de dependencias

EfacClient está registrado como singleton, así que también se puede pedir por tipo en cualquier sitio que resuelva el contenedor — un controlador, un job, un comando:

namespace App\Http\Controllers;

use Efac\Sdk\EfacClient;
use Efac\Sdk\Enum\EcfTipo;
use Efac\Sdk\Enum\TaxTreatment;
use Efac\Sdk\Invoice\Invoice;
use Efac\Sdk\Invoice\Item;

final class EmitirFacturaController
{
    public function __invoke(EfacClient $efac)
    {
        $factura = Invoice::of(EcfTipo::CreditoFiscal31)
            ->seller('101000001', 'ACME SRL', 'Av. Principal 1')
            ->buyer('130000002', 'Cliente SA', email: 'cxp@cliente.do')
            ->addItem(Item::goods('Widget', qty: 2, unitPrice: 500.00, tax: TaxTreatment::Itbis18))
            ->build();

        $resultado = $efac->invoices()->emit($factura, 'orden-1042');

        return response()->json(['encf' => $resultado->encf]);
    }
}

La fachada y la inyección por tipo llegan al mismo EfacClient singleton — no hay diferencia de comportamiento entre una y otra, solo de estilo.

La llave de idempotencia es obligatoria

emit() exige una llave, tanto vía la fachada como vía inyección — el paquete no la genera por ti. La razón es fiscal, no estética: sin Idempotency-Key, reintentar una petición que expiró por timeout puede emitir un e-CF duplicado y consumir un segundo e-NCF, que es un recurso fiscal con seguimiento legal. La explicación completa, junto con las reglas de reintento y de reuso de la llave, está en el README de g-efac/sdk.

El resto

Este paquete no duplica documentación del SDK. EfacClient y Invoices, que se ven en los ejemplos de arriba, son g-efac/sdk; lo único que aporta g-efac/laravel es la fachada, el service provider y la traducción de config/efac.php a EfacClient::builder().

Qué buscas Dónde
Implementar la emisión en una app Laravel, de principio a fin Guía de integración
Referencia del SDK sin Laravel README de g-efac/sdk
Contrato HTTP /v1 docs/api/openapi-v1.json

La guía cubre en detalle lo que este README solo menciona: el cuerpo JSON exacto que se envía, cada campo de las respuestas, las reglas de idempotencia, la tabla de estados, el manejo de 202 con awaitTerminal() o con jobs reprogramados, la matriz de reintentos por excepción, la caché del token, el ambiente DGII, multi-emisor y cómo hacer pruebas.

Qué no incluye este paquete

Deliberadamente no hay comandos Artisan, jobs en cola ni modelos Eloquent. El bridge se limita a poner un EfacClient correctamente configurado en el contenedor; cómo lo llamas — sincrónico, desde un job propio, desde un comando propio — queda en tu aplicación.