Created
October 9, 2021 22:29
-
-
Save AngelAlexQC/a0bd54d257f05451c55c03ed3c8b2336 to your computer and use it in GitHub Desktop.
Factura SRI Ecuador para firmar
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace App\Models; | |
use Carbon\Carbon; | |
use DOMDocument; | |
use Illuminate\Database\Eloquent\Factories\HasFactory; | |
use Illuminate\Database\Eloquent\Model; | |
use RobRichards\XMLSecLibs\XMLSecurityDSig; | |
use RobRichards\XMLSecLibs\XMLSecurityKey; | |
class Factura extends Model | |
{ | |
use HasFactory; | |
/** | |
* Retornar la factura en formato XML | |
* @return string | |
*/ | |
public function getXML() | |
{ | |
$xml = new DOMDocument('1.0', 'UTF-8'); | |
$xml->formatOutput = true; | |
$factura = $xml->createElement('factura'); | |
$factura->setAttribute('id', 'comprobante'); | |
$factura->setAttribute('version', '2.1.0'); | |
// Información Tributaria | |
$infoTributaria = $xml->createElement('infoTributaria'); | |
$infoTributaria->appendChild($xml->createElement('ambiente', $this->ambiente)); | |
$infoTributaria->appendChild($xml->createElement('tipoEmision', 1)); | |
$infoTributaria->appendChild($xml->createElement('razonSocial', $this->razon_social)); | |
$infoTributaria->appendChild($xml->createElement('nombreComercial', $this->nombre_comercial)); | |
$infoTributaria->appendChild($xml->createElement('ruc', $this->ruc)); | |
$infoTributaria->appendChild($xml->createElement('claveAcceso', $this->getClaveAcceso())); | |
$infoTributaria->appendChild($xml->createElement('codDoc', '01')); | |
$infoTributaria->appendChild($xml->createElement('estab', $this->establecimiento)); | |
$infoTributaria->appendChild($xml->createElement('ptoEmi', $this->punto_emision)); | |
$infoTributaria->appendChild($xml->createElement('secuencial', $this->secuencial ? $this->secuencial : '000000001')); | |
$infoTributaria->appendChild($xml->createElement('dirMatriz', $this->direccion_matriz)); | |
// Información de la factura | |
$infoFactura = $xml->createElement('infoFactura'); | |
$infoFactura->appendChild($xml->createElement( | |
'fechaEmision', | |
$this->fecha_emision ? $this->fecha_emision : now()->format('d/m/Y') | |
)); | |
$infoFactura->appendChild($xml->createElement('dirEstablecimiento', $this->dir_establecimiento)); | |
$infoFactura->appendChild($xml->createElement('contribuyenteEspecial', $this->contribuyente_especial)); | |
$infoFactura->appendChild($xml->createElement('obligadoContabilidad', $this->obligado_contabilidad)); | |
$infoFactura->appendChild($xml->createElement('tipoIdentificacionComprador', $this->tipo_identificacion_comprador)); | |
$infoFactura->appendChild($xml->createElement('razonSocialComprador', $this->razon_social_comprador)); | |
$infoFactura->appendChild($xml->createElement('identificacionComprador', $this->identificacion_comprador)); | |
$infoFactura->appendChild($xml->createElement('totalSinImpuestos', $this->total_sin_impuestos)); | |
$infoFactura->appendChild($xml->createElement('totalDescuento', $this->total_descuento)); | |
$totalConImpuestos = $xml->createElement('totalConImpuestos'); | |
$totalImpuesto = $xml->createElement('totalImpuesto'); | |
$totalImpuesto->appendChild($xml->createElement('codigo', 2)); | |
$totalImpuesto->appendChild($xml->createElement('codigoPorcentaje', 2)); | |
$totalImpuesto->appendChild($xml->createElement('baseImponible', '123.45')); | |
$totalImpuesto->appendChild($xml->createElement('valor', '14.81')); | |
$totalConImpuestos->appendChild($totalImpuesto); | |
$infoFactura->appendChild($totalConImpuestos); | |
$infoFactura->appendChild($xml->createElement('propina', $this->propina)); | |
$infoFactura->appendChild($xml->createElement('importeTotal', $this->importe_total)); | |
$infoFactura->appendChild($xml->createElement('moneda', $this->moneda)); | |
// Pagos | |
$pagos = $xml->createElement('pagos'); | |
$pago = $xml->createElement('pago'); | |
$pago->appendChild($xml->createElement('formaPago', '01')); | |
$pago->appendChild($xml->createElement('total', $this->total)); | |
$pago->appendChild($xml->createElement('plazo', '30')); | |
$pago->appendChild($xml->createElement('unidadTiempo', 'dias')); | |
$pagos->appendChild($pago); | |
$infoFactura->appendChild($pagos); | |
// Detalles | |
$detalles = $xml->createElement('detalles'); | |
$arrayDetalles = [ | |
[ | |
'codigoPrincipal' => 'COD001', | |
'descripcion' => 'PROD001', | |
'cantidad' => '1', | |
'precioUnitario' => '123.45', | |
'descuento' => '0.00', | |
'precioTotalSinImpuesto' => '123.45', | |
'impuestos' => [ | |
[ | |
'codigo' => '2', | |
'codigoPorcentaje' => '2', | |
'tarifa' => '12', | |
'baseImponible' => '123.45', | |
'valor' => '14.81', | |
], | |
], | |
], | |
]; | |
foreach ($arrayDetalles as $detalle) { | |
$detalleXML = $xml->createElement('detalle'); | |
$detalleXML->appendChild($xml->createElement('codigoPrincipal', $detalle['codigoPrincipal'])); | |
$detalleXML->appendChild($xml->createElement('descripcion', $detalle['descripcion'])); | |
$detalleXML->appendChild($xml->createElement('cantidad', $detalle['cantidad'])); | |
$detalleXML->appendChild($xml->createElement('precioUnitario', $detalle['precioUnitario'])); | |
$detalleXML->appendChild($xml->createElement('descuento', $detalle['descuento'])); | |
$detalleXML->appendChild($xml->createElement('precioTotalSinImpuesto', $detalle['precioTotalSinImpuesto'])); | |
$impuestos = $xml->createElement('impuestos'); | |
foreach ($detalle['impuestos'] as $impuesto) { | |
$impuestoXML = $xml->createElement('impuesto'); | |
$impuestoXML->appendChild($xml->createElement('codigo', $impuesto['codigo'])); | |
$impuestoXML->appendChild($xml->createElement('codigoPorcentaje', $impuesto['codigoPorcentaje'])); | |
$impuestoXML->appendChild($xml->createElement('tarifa', $impuesto['tarifa'])); | |
$impuestoXML->appendChild($xml->createElement('baseImponible', $impuesto['baseImponible'])); | |
$impuestoXML->appendChild($xml->createElement('valor', $impuesto['valor'])); | |
$impuestos->appendChild($impuestoXML); | |
} | |
$detalleXML->appendChild($impuestos); | |
$detalles->appendChild($detalleXML); | |
} | |
// Info Adicional | |
$infoAdicional = $xml->createElement('infoAdicional'); | |
$vendedor = $xml->createElement('campoAdicional', 'Ángel Quiroz'); | |
$vendedor->setAttribute('nombre', 'Vendedor'); | |
$caja = $xml->createElement('campoAdicional', 'Caja 1'); | |
$caja->setAttribute('nombre', 'Caja'); | |
$infoAdicional->appendChild($vendedor); | |
$infoAdicional->appendChild($caja); | |
$factura->appendChild($infoTributaria); | |
$factura->appendChild($infoFactura); | |
$factura->appendChild($detalles); | |
$factura->appendChild($infoAdicional); | |
$xml->appendChild($factura); | |
return $xml->saveXML(); | |
} | |
public function getEstructura() | |
{ | |
$fecha = now()->format('dmY'); | |
$tipo_comprobante = '01'; | |
$ruc = $this->ruc; | |
$tipo_ambiente = '1'; | |
$serie = $this->establecimiento . $this->punto_emision; | |
$secuencial = $this->secuencial; | |
$codigo_numerico = '12345678'; | |
$tipo_emision = '1'; | |
return $fecha . $tipo_comprobante . $ruc . $tipo_ambiente . $serie . $secuencial . $codigo_numerico . $tipo_emision; | |
} | |
/** | |
* Generar Clave de Acceso | |
* @return string | |
*/ | |
public function getClaveAcceso() | |
{ | |
return $this->getEstructura() . $this->getDigitoVerificador(); | |
} | |
public function getDigitoVerificador() | |
{ | |
$estructura = $this->getEstructura(); | |
if (strlen($estructura) == 48) { | |
$digits = str_replace(array('.', ','), array('' . ''), strrev($estructura)); | |
if (!ctype_digit($digits)) { | |
return false; | |
} | |
$sum = 0; | |
$factor = 2; | |
for ($i = 0; $i < strlen($digits); $i++) { | |
$sum += substr($digits, $i, 1) * $factor; | |
if ($factor == 7) { | |
$factor = 2; | |
} else { | |
$factor++; | |
} | |
} | |
$dv = 11 - ($sum % 11); | |
if ($dv == 10) { | |
return 1; | |
} | |
if ($dv == 11) { | |
return 0; | |
} | |
return $dv; | |
} | |
} | |
/** | |
* Guarda el XML en un archivo | |
* @return string | |
*/ | |
public function saveXMLToFile() | |
{ | |
$xml = $this->getXML(); | |
$file = fopen('xml' . DIRECTORY_SEPARATOR . $this->getClaveAcceso() . '.xml', 'w'); | |
fwrite($file, $xml); | |
fclose($file); | |
return 'xml' . DIRECTORY_SEPARATOR . $this->getClaveAcceso() . '.xml'; | |
} | |
/** | |
* Firma el archivo XML | |
* @param string $passPhrase | |
* @return void | |
*/ | |
public function signXML(string $passPhrase) | |
{ | |
$xml = new DOMDocument('1.0', 'UTF-8'); | |
$certificateNumber = rand(100000, 999999); | |
$signatureNumber = rand(100000, 999999); | |
$signedPropertiesNumber = rand(100000, 999999); | |
$signedInfoNumber = rand(100000, 999999); | |
$signedPropertiesIdNumber = rand(100000, 999999); | |
$referenceIDNumber = rand(100000, 999999); | |
$signatureValueNumber = rand(100000, 999999); | |
$objectNumber = rand(100000, 999999); | |
$certificatePath = base_path('certs' . DIRECTORY_SEPARATOR . $this->ruc . '.p12'); | |
$doc = new DOMDocument(); | |
$doc->load($this->saveXMLToFile()); | |
$objDSig = new XMLSecurityDSig(); | |
$objDSig->setCanonicalMethod(XMLSecurityDSig::C14N); | |
$objDSig->addReference( | |
$doc, | |
XMLSecurityDSig::SHA1 | |
); | |
$objKey = new XMLSecurityKey( | |
XMLSecurityKey::RSA_SHA1, | |
array('type' => 'private') | |
); | |
// Load PKCS12 certificate from file whit 'openssl_pkcs12_read' | |
$pkcs12 = file_get_contents($certificatePath); | |
$certs = array(); | |
if (!openssl_pkcs12_read($pkcs12, $certs, $passPhrase)) { | |
throw new \Exception('Error reading PKCS12 certificate'); | |
} | |
$objKey->loadKey($certs['pkey']); | |
$objKey->passphrase = $passPhrase; | |
$objDSig->sign($objKey); | |
$objDSig->add509Cert( | |
$certs['cert'], | |
true, | |
false, | |
[ | |
'issuerSerial' => true, | |
'subjectName' => true, | |
] | |
); | |
// Add modulus and exponent to the signature | |
$objDSig->addObject( | |
$xml->createElement('Modulus', 'hola'), | |
); | |
$objDSig->appendSignature($doc->documentElement); | |
// Edit XML | |
/* $signatureElement = $doc->documentElement->getElementsByTagNameNS( | |
'http://www.w3.org/2000/09/xmldsig#', | |
'Signature' | |
)->item(0); | |
$signatureElement->setAttribute('Id', 'Signature' . $signatureNumber); | |
$signatureElement->setAttribute('xmlns:etsi', 'http://uri.etsi.org/01903/v1.3.2#'); | |
$signedInfoElement = $doc->getElementsByTagName('SignedInfo')->item(0); | |
$signedInfoElement->setAttribute('Id', 'Signature-SignedInfo' . $signedInfoNumber); | |
$reference1 = $signedInfoElement->getElementsByTagName('Reference')->item(0); | |
$reference1->removeChild($reference1->getElementsByTagName('Transforms')->item(0)); | |
$reference1->setAttribute('Id', 'SignedPropertiesID' . $signedPropertiesIdNumber); | |
$reference1->setAttribute('Type', 'http://uri.etsi.org/01903#SignedProperties'); | |
$reference1->setAttribute('URI', '#Signature' . $signatureNumber . '-SignedProperties' . $signedPropertiesNumber); | |
$reference2 = $signedInfoElement->getElementsByTagName('Reference')->item(1); | |
$reference2->removeChild($reference2->getElementsByTagName('Transforms')->item(0)); | |
$reference2->setAttribute('URI', '#Certificate' . $certificateNumber); | |
$reference3 = $signedInfoElement->getElementsByTagName('Reference')->item(2); | |
$reference3->setAttribute('Id', 'Reference-ID-' . $referenceIDNumber); | |
$reference3->setAttribute('URI', '#comprobante'); | |
$reference3->getElementsByTagName('Transform')->item(0)->setAttribute('Algorithm', 'http://www.w3.org/2000/09/xmldsig#enveloped-signature'); | |
$signatureValueElement = $doc->getElementsByTagName('SignatureValue')->item(0); | |
$signatureValueElement->setAttribute('Id', 'SignatureValue' . $signatureValueNumber); | |
$keyInfoElement = $doc->getElementsByTagName('KeyInfo')->item(0); | |
$keyInfoElement->setAttribute('Id', 'Certificate' . $certificateNumber); */ | |
$doc->save( | |
'xml' . | |
DIRECTORY_SEPARATOR . | |
'signed' . | |
DIRECTORY_SEPARATOR . | |
$this->getClaveAcceso() . | |
'.xml' | |
); | |
} | |
/** | |
* Envia el XML al SRI | |
* @return string | |
*/ | |
public function sendXML() | |
{ | |
/* $url = 'https://celcer.sri.gob.ec/comprobantes-electronicos-ws/RecepcionComprobantesOffline?wsdl'; | |
$file = file_get_contents('xml/factura' . $this->secuencial . '.xml'); | |
$xml_envio = "<soapenv:Envelope xmlns:soapenv=\"http://schemas.xmlsoap.org/soap/envelope/\" xmlns:ec=\"http://ec.gob.sri.ws.recepcion\">"; | |
$xml_envio .= "<soapenv:Header/>"; | |
$xml_envio .= "<soapenv:Body>"; | |
$xml_envio .= "<ec:validarComprobante>"; | |
$xml_envio .= "<xml>" . base64_encode($file) . "</xml>"; | |
$xml_envio .= "</ec:validarComprobante>"; | |
$xml_envio .= "</soapenv:Body>"; | |
$xml_envio .= "</soapenv:Envelope>"; | |
$headers = array( | |
"Content-type: text/xml;charset=\"utf-8\"", | |
"Accept: text/xml", | |
); | |
// Envio de la petición | |
$ch = curl_init(); | |
curl_setopt($ch, CURLOPT_URL, $url); | |
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); | |
curl_setopt($ch, CURLOPT_POST, 1); | |
curl_setopt($ch, CURLOPT_POSTFIELDS, $xml_envio); | |
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); | |
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); | |
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); | |
$result = curl_exec($ch); | |
print_r($result); | |
curl_close($ch); | |
return $result; */ | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment