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 hidden or 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