Crónicas de una facturación electrónica desde PHP anunciada

Posted by

¡Al fin!

Sólo me tomó 7 semanas sin dormir para poder concluir un sistema básico de emisión de documentos electrónicos de la SUNAT para IcaServer 😑

Una buena parte del tiempo se me fue aprendiendo más a fondo XML, desde cómo crearlo con PHP hasta firmarlo con el certificado digital (y entender todo el proceso). Otra parte se me fue (como a muchos contadores) entendiendo las inconsistencias de la SUNAT…

Y estas son algunas notas de las vicisitudes que encontré en el camino.

XML es horrible

No saben cuánto extrañé JSON con REST 😛 Fue tentador para mi crear el XML a mano en un fichero, y luego reemplazar su contenido, como si fuera una plantilla. Pero en el proceso de desarrollo me topé con varios errores que requerían mover nodos, así que acabé con un híbrido: parte de nodos generados con código, y algunos segmentos con ficheros tipo plantillas.

El resultado es monstruoso. Funciona, pero es inmantenible. Decidí que al reescribirlo crearía una librería para hacer XML facilito.

Y ya existe 😀 Le presento a Semeele, XML sin dolor de cabeza 😁

Por ejemplo, para generar un XHTML, puedes usar este código:

$xml = new drmad\semeele\Document('html');
$xml->child('head')
    ->add('title', 'An XHTML')
    ->add('meta', ['charset' => 'utf-8'])
    ->parent()
->child('body')
    ->add('h1', 'An XHTML')
    ->add('p', 'This is a XML-valid HTML. Yay!')
;

echo $xml->getXML();

Así de sencillo. Ese código genera este XML:

<?xml version="1.0" encoding="utf-8"?><html><head><title>An XHTML</title><meta charset="utf-8"/></head><body><h1>An XHTML</h1><p>This is a XML-valid HTML. Yay!</p></body></html>

Hay más ejemplos y algo de documentación en la página de GitHub de Semeele.

La firma y el firmado

El certificado digital lo adquirí en llama.pe, fue uno de los pocos que muestran el precio del certificado en su web, sin solicitar una cotización ni otras trabas. Y no me equivoqué, me lo dieron en prácticamente horas.

Me lo entregaron en formato PKCS#12, heredero del peor formato criptográfico de la historia. Para aumentar el estrés, SUNAT requiere el certificado en formato distinto, ‘CER’  Para nuestra salvación, Linux tiene el fabuloso comando openssl, que hace todo el trabajo feo por nosotros.

Primero, obtenemos los certificados desde el fichero PKCS#12 (en formato PEM, que es ASCII plano)

openssl pkcs12 -in CERTIFICADO_PKCS.pfx -nokeys -out solo_certificados.crt

Luego lo recodificamos a DER (¿no era CER? Puedes leer aquí para confundirte un poco más…):

openssl x509 -in solo_certificados.crt -outform der -out certificado.cer

Y listo.

A parte de convertir de formato, con openssl puedes sacar cada certificado y la llave privada del PKCS#12, y guardar cada uno en el formato PEM (entre otras docenas de cosas más. openssl es muy paja 😛)

En vez de usar las librerías de OpenSSL de PHP para firmar el documento (y ver otros temas relacionados, como la canonicalización), preferí usar XMLSec, y funcionó a la ferpección.

ZIP de la discordia

La documentación del ZIP para el envío del XML a la SUNAT dice:

En caso de las facturas y sus correspondientes notas de crédito y débito, se enviará un único comprobante, razón por la que se espera recibir un único archivo ZIP y dentro de este, una carpeta de nombre dummy (vacio) y un documento XML. Los nombres de los archivos deben coincidir a excepción de la extensión.

Los dummies son los que escribieron eso 😒 Perdí buena cantidad de horas para darme cuenta que no debe existir esa carpeta en el ZIP,  únicamente el XML.

WS-Security

La librería de SOAP de PHP no tiene soporte nativo para WS-Security, requerido por la SUNAT. Pero implementarlo es “””sencillo””” (entre múltiples comillas). Usando el mismo ejemplo del Manual del Programador de la SUNAT, y con mucha ayuda de Google, llegué a este código:

$WSHeader = '<wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
    <wsse:UsernameToken>
        <wsse:Username>' . $RUC . $USUARIO_SOL . '</wsse:Username>
        <wsse:Password>' . $CONTRASEÑA_SOL . '</wsse:Password>
    </wsse:UsernameToken>
</wsse:Security>';
$headers = new SoapHeader('http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd', 'Security', new SoapVar($WSHeader, XSD_ANYXML));
$soap = new SoapClient('ruta/al/wsdl')
$soap->__soapCall('sendBill', $argumentos, null, $headers);

sendBill y $argumentos dependen de la acción que quieras realizar. OJO que $argumentos, por alguna razón esotérica, debe ser un array asociativo dentro de otro array 😝 algo como [['filename' => '123abc.zip', 'contentFile' => $zipfile]]. Bendito PHP…

El parámetro de new SoapClient es una ruta a un fichero, que explico en el siguiente apartado.

PHP y su bug de 9 años

Las pruebas de envío usando la librería de SOAP de PHP fueron bien. Los envíos de los comprobantes para la homologación fueron bien. Pero cuando intenté enviar mi primera factura en modo producción, ¡BOOM! Excepción del SOAP 😠

Parsing WSDL: <binding> 'BillServicePortBinding' already defined

Resulta que la librería de SOAP de PHP tiene un problema con WSDLs que usan namespaces (que es el caso del WSDL del SOAP de la SUNAT para producción), reportado el 16 de junio del 2008,  y nunca fue reparado…

No hay otras librerías decentes de SOAP para PHP. Tampoco hay para Python, pues pensé en usarlo para el envío. Nada. Pensé en hacer mi propia librería de SOAP… pero ya estaba muy abrumado por no acabar este tema.

Al final, leyendo sobre WSDL, se me ocurrió un hack: El WSDL de la SUNAT tiene definiciones de funciones para dos versiones  de SOAP,  1.1 y 1.2, usando los namespaces. El bendito BillServicePortBinding se define primero en en la URL relativa importada billService?ns2.wsdl (con bindings para SOAP 1.2), y luego se vuelve a definir en WSDL principal.

ASI QUE simplemente removí la línea donde importa el billService?ns2.wsdl 😁 Ahí se define el SOAP 1.2, pero PHP y la SUNAT trabajan bien con la versión 1.1, y problema resuelto. Para remover la línea tuve que descargar el WSDL con sus dos ficheros relacionados: billService?ns1.wsdlbillService.xsd2.xsd, y crear el objeto SoapClient pasándole la ruta del fichero principal, y no la URL del WSDL público.

Claro está que si a la SUNAT se le ocurre cambiar el WSDL, todo el código se romperá, hasta actualizar la copia local. Espero que eso no pase pronto.

Bienvenidos al futuro

Ahora IcaServer es un emisor de facturas electrónicas 😁 Y, obviamente, estoy brindando asesoría y herramientas para las empresas que quieran (o deban) hacer lo mismo. Ponte en contacto conmigo para brindarte mayor información.

123 comments

      1. me he bajado
        e-factura.sunat.gob.pe/ol-ti-itcpfegem/billService?wsdl
        e-factura.sunat.gob.pe/ol-ti-itcpfegem/billService?ns1.wsdl
        e-factura.sunat.gob.pe/ol-ti-itcpfegem/billService?ns2.wsdl
        e-factura.sunat.gob.pe/ol-ti-itcpfegem/billService.xsd2.xsd

        Que archivo modifico. Y Que le modifico
        todos esta dentro de una carpeta en mi ser local.

        1. El chiste está en el fichero billService?wsdl. Dentro hay una línea que dice <wsdl:import location="billService?ns2.wsdl".... Borra esa línea, y se feliz 😀

          1. Hola drmad, eliminé la linea que dices, para crear el soapcliente lo hago asi:
            $soap = new \SoapClient(‘wsdl-sunat/billService.xml?wsdl’, [
            ‘cache_wsdl’ => WSDL_CACHE_NONE,
            ‘trace’ => TRUE ,
            ‘soap_version’ => SOAP_1_1 ]
            );

            ahora me sale este error:
            SOAP-ERROR: Parsing WSDL: Couldn’t load from ‘wsdl-sunat/billService.xml?wsdl’ : failed to load external entity “wsdl-sunat/billService.xml?wsdl”

            NOTA: dentro de la carpeta “wsdl-sunat” tengo los archivos que corresponde a los archivos que menciona katty y Giova :

            billService.xml
            billService-ns1.xml
            billService-ns2.xml
            billService.xsd2.xsd

          2. Hi Jorge.

            Ojo que el fichero debe llamarse billService.xml?wsdl, así tal cual. El error de SOAP indica que no puede ‘cargar la entidad externa’, debe deberse a que estás intentando cargar un fichero con un nombre que no corresponde al fichero que tienes en el sistema de ficheros.

            También podrías cambiar el parámetro de SoapClient a “billService.xml“, debería de funcionar sin problemas.

          3. No logre hacer funcionar hacia con los cambios que me dices, primero: en Windows no acepta ese tipo de nombres para los archivos, Segundo: lo pase a Linux y tampoco funcionó; finalmente tuve que crear un ejecutable que solamente se encargue del envío de los comprobantes al servidor de la sunat y lo ejecuto desde PHP (en windows lo hice en .NET), no es una solución limpia pero por ahora me da tiempo a seguir viendo como solucionar este tema… Gracias a todos.

          4. Windows cochino 😀 Realmente no importa el nombre del fichero, con tal que sea consistente con el nombre que usas en el programa (y en los demás ficheros relacionados) para llamarlo. E.g puedes usar $soap = new \SoapCliente('c:\bendito.wsdl', ..., y renombrar/movier el fichero billService.xml?wsdl con dicho nombre. Este fichero, dentro, tiene referencias relativas a otros ficheros más. Nuevamente: funciona si renombras el fichero en ámbos lugares: el sistema de ficheros y el código/XML que lo referencia.

            Es cierto, el tema de SOAP en PHP es un gran problema, y esto que he hecho también es un hack feo. Espero y pronto se pongan las pilas con el bug de ya pronto 9 años.

      2. Hola drmad

        Tengo el mismo problema que indicas arriba cuando apunto a producción pero estoy usando python con suds.
        Puedes ayudarme con consultoria?

        Me comentas

          1. Hola, tengo una consulta, en windows no me permite utilizar ? en el nombre de un archivo, por ende no me permite usar billservice?ns1.wsdl, funcionaría igual si es que se cambia el nombre por ejemplo a ns1.wsdl y en el archivo billservice.wsdl, en la parte que dice <wsdl:import location="billService?ns1.wsdl" cambiarla a <wsdl:import location="ns.wsdl"?.

            Además de ello quisiera saber donde descargan esos arcihvos ns1.wsdl, ya que solo encontré los de prueba.

            Gracias de antemano y saludos.

          2. Hola. El nombre realmente no importa, con tal que uses el mismo nombre en el fichero, y dentro del fichero WDSL que has descargado.

            En la URL http://orientacion.sunat.gob.pe/index.php/empresas-menu/comprobantes-de-pago-empresas/comprobantes-de-pago-electronicos-empresas/see-desde-los-sistemas-del-contribuyente/988-guias-manuales-y-servicios-web están todos las direcciones para acceso via SOAP, incluyendo la de la emisión de comprobantes electrónicos (ahi le llaman ‘Servicio Factura Electrónica). Los ficheros importados tienen rutas relativas, asi que solo basta con cambiar el nombre del fichero de la URL principal por uno de los ns1 o ns2.

    1. En el WSDL de la sunat, todas las referencias a otros ficheros son relativas. Así que si copias los ficheros a tu computadora con el mismo nombre, cuando cargues el fichero billService?wsdl (o el nombre que quieras ponerle, en ese fichero el nombre no es relevante), cargará del mismo directorio los demás ficheros (siempre y cuando tengan el mismo nombre).

      Saludos!

  1. Hola drmad, si me pudieras ayudar porque estoy tratando de enviar mi archivo zip, pero no tengo respuesta del web service SUNAT, este es el codigo:
    $service = ‘https://e-beta.sunat.gob.pe/ol-ti-itcpfegem-beta/billService?wsdl’;
    $NomArch = “20100073723-01-F607-00000420″;
    $fileName=”20100073723-01-F607-00000420.”.zip;
    $archivo = base64_encode(file_get_contents($NomArch.”.zip”));
    $RUC = “20100073723”;
    $USUARIO_SOL = “MODDATOS”;
    $CONTRASENA_SOL = “MODDATOS”;

    $soap = new SoapClient($service, [
    ‘cache_wsdl’ => WSDL_CACHE_NONE,
    ‘trace’ => TRUE ,
    ‘soap_version’ => SOAP_1_1 ]
    );

    $argumentos = array( ‘fileName’ => $fileName, ‘contentFile’ => base64_encode(file_get_contents($fileName)) );

    $WSHeader = ‘

    ‘.$RUC.$USUARIO_SOL.’
    ‘.$CONTRASENA_SOL.’

    ‘;
    $headers = new SoapHeader(‘http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd’, ‘Security’, new SoapVar($WSHeader, XSD_ANYXML));
    // $soap = new SoapClient(‘ruta/al/wsdl’)
    $result = $soap->__soapCall(‘sendBill’, $argumentos, null, $headers);
    echo $result;

    1. Puse un gran “OJO” debajo del código del ws-security, la librería de SOAP del PHP espera un array dentro de un array para los argumentos (usando la función retro array() que usas, debería ser un array(array('fileName'=>...).

      A parte, la librería de SOAP va a encodear el zip, no uses el base64_encode.

      A parte, en la 3ra línea, donde defines $fileName hay un .zip fuera del string. Verifica que eso no esté así en tu código.

  2. Hola drmad, hize los cambios y me sale el siguiente error:

    Fatal error: Uncaught SoapFault exception: [soap-env:Client.1036] Número de documento en el nombre del archivo no coincide con el consignado en el contenido del XMLDetalle: xxx.xxx.xxx value=’ticket: 1505145143245 error: numero de comprobante del xml diferente al numero del archivo 420 diff 00000420′ in C:\AppServ\www\ws\soapphp4.php:30 Stack trace: #0 C:\AppServ\www\ws\soapphp4.php(30): SoapClient->__soapCall(‘sendBill’, Array, NULL, Object(SoapHeader)) #1 {main} thrown in C:\AppServ\www\ws\soapphp4.php on line 30

    El codigo es:
    $service = ‘https://e-beta.sunat.gob.pe/ol-ti-itcpfegem-beta/billService?wsdl’;
    $fileName=”20100073723-01-F607-00000420.zip”;

    $RUC = “20100073723”;
    $USUARIO_SOL = “MODDATOS”;
    $CONTRASENA_SOL = “MODDATOS”;

    $soap = new SoapClient($service, [
    ‘cache_wsdl’ => WSDL_CACHE_NONE,
    ‘trace’ => TRUE ,
    ‘soap_version’ => SOAP_1_1 ]
    );

    $argumentos = array( array(‘fileName’ => $fileName, ‘contentFile’ => (file_get_contents($fileName))) );

    $WSHeader = ‘

    ‘.$RUC.$USUARIO_SOL.’
    ‘.$CONTRASENA_SOL.’

    ‘;
    $headers = new SoapHeader(‘http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd’, ‘Security’, new SoapVar($WSHeader, XSD_ANYXML));
    // $soap = new SoapClient(‘ruta/al/wsdl’)
    $result = $soap->__soapCall(‘sendBill’, $argumentos, null, $headers);
    echo $result;

    1. La excepción del SOAP dice la respuesta de la SUNAT: “Número de documento en el nombre del archivo no coincide con el consignado en el contenido del XML”.

  3. drmad, estoy utilizando para convertir la respuesta SUNAT, $result = $soap->__soapCall(‘sendBill’, $argumentos, null, $headers);
    print_r($result);
    $archivo = fopen(‘X’.$NomArch.’.xml’,’w+’);
    fputs($archivo, $result);
    fclose($archivo);
    pero sale un error: Warning: fputs() expects parameter 2 to be string, object given

    Habra otra manera de capturar la respuesta de SUNAT:
    stdClass Object ( [applicationResponse] => PK�+Kdummy/PK�+K0�����R-20515290142-03-BT10-1899.XML�VMs�8�ϯp��TM֑m0�]�)��Y6Y6�@6s�0Jl�% ��W��&N L�>ȭׯ[��eܯ�8R��qL�@5.uUAħ&�@]�o�k����3J��PH�� %)ҙp���aġ�c�#���xU���2r��F1tv�y&�?P�����J���s�6�|��#bFuKJ|>}�7��yg�x���l �z�2]pPTjt���;��|@�%�ng� K���eۖ�wAa�w�t�TLS7�4]>��Н��t�ZAs,�u'{j�̞�ʯS���#�66���4��v\pCߩ�S�%�<.f�y�t���=db�۲�4�2Vߛ�F�ٕ?Ӷ��ث���%u�V�L�p�%'�U �u���I��T�4�7�Bd&�*U�ߔ5T8�}�����]����L��y�}%Y�G�N���X����~�߳�甤�����PK�+Kdummy/PK�+K0�����&R-20515290142-03-BT10-1899.XMLPK�0 )

  4. Hola drmad, espero me ayudes con lo de firma digital, lo que he realizado es convertir el archivo .pfx en .pem los cuales al abrirlo el .pem con texto hay dos certificados: BEGIN CERTIFICATE con datos y localKeyID=.. y BEGIN PRIVATE KEY con localKeyID= .., quería saber con cual se firma, y dentro del UBL para firmar comienzo con (ext:UBLExtension) .. pero en ds:DigestValue que contenido es? ds:SignatureValue que contenido es? y en ds:X509Certificate que contenido es?, creo que en ds:X509Certificate va el contenido del archivo .pem los datos de BEGIN CERTIFICATE .. END CERTIFICATE, por favor espero me ayudes a ubicar los contenidos de la firma digital gracias.

    1. Si realmente quieres saber lo que va en cada elemento, tienes harto por leer 😀 Empezando por este post 😛 donde he detallado que usé XMLSec, un programa externo, para que haga el firmado del XML. Dale una leída a este enlace, que me sirvió para entender todo.

      En teoría podrías usar las librerías de PHP OpenSSL y DOM para hacer la C14N (“canonicalización“. me gusta esa palabra 😀 ), digest y firmado del XML usando el certificado X509. A mi me dio flojera ir por ese camino 😀

  5. Hola drmad, espero me ayudes con lo de firma digital, estoy utilizando xmlsec para firmar pero me sale error, te envio el codigo que estoy utilizando para firmar el XML:

    loadXML($xmlstr);

    $objSign = new XMLSecurityDSig($ruc);
    // Use the c14n exclusive canonicalization
    $objSign->setCanonicalMethod(XMLSecurityDSig::C14N);
    // Sign using SHA-256
    $objSign->addReference(
    $domDocument,
    XMLSecurityDSig::SHA1,
    array(‘http://www.w3.org/2000/09/xmldsig#enveloped-signature’),
    $options = array(‘force_uri’ => true)
    );

    $objKey = new XMLSecurityKey(XMLSecurityKey::RSA_SHA1, array(‘type’=>’private’));
    // Load the private key
    $objKey->loadKey($privateKey);
    /*
    If key has a passphrase, set it using
    $objKey->passphrase = ”;
    */
    // Sign the XML file
    $objSign->sign($objKey, $domDocument->getElementsByTagName($ReferenceNodeName)->item(1));
    // Add the associated public key to the signature
    $objSign->add509Cert($publicKey);

    // Append the signature to the XML
    //$objSign->appendSignature($ReferenceNodeName);

    $content = $domDocument->saveXML();
    ?>

  6. Los errores que me arroja el codigo son:

    Warning: DOMDocument::loadXML(): StartTag: invalid element name in Entity, line: 1 in C:\AppServ\www\xmlseclibs-master\src\XMLSecurityDSig.php on line 119

    Warning: DOMDocument::loadXML(): Extra content at the end of the document in Entity, line: 1 in C:\AppServ\www\xmlseclibs-master\src\XMLSecurityDSig.php on line 119

    Fatal error: Call to undefined function RobRichards\XMLSecLibs\openssl_get_privatekey() in C:\AppServ\www\xmlseclibs-master\src\XMLSecurityKey.php on line 345

  7. Nuevamente, yo no uso esa librería, ni habia oido de ella.

    Pero tienes un error Fatal, “RobRichards\XMLSecLibs\openssl_get_privatekey()” no existe. Lee su documentación, ahi debe decir cómo incluir la librería donde se define dicha función.

  8. Hola drmad, no se si me faltan inluir algo mas estoy utilizando lo siguiente:
    $xmlseclibs_srcdir = dirname(__FILE__) . ‘/src/’;
    require $xmlseclibs_srcdir . ‘/XMLSecurityKey.php’;
    require $xmlseclibs_srcdir . ‘/XMLSecurityDSig.php’;
    require $xmlseclibs_srcdir . ‘/XMLSecEnc.php’;

    tengo el siguiente error:
    Call to a member function getElementsByTagName() on string in C:\AppServ\www\xmlseclibs-master\firmar2.php on line 43

    y la linea 43 tiene el siguiente codigo:
    $objSign->sign($objKey, $domDocument->getElementsByTagName($ReferenceNodeName)->item(1));

    donde en lineas anteriores de codigo menciono lo siguiente para llamar al objeto:

    $domDocument = new DOMDocument();

    y otro dos errores:

    Warning: DOMDocument::loadXML(): StartTag: invalid element name in Entity, line: 1 in C:\AppServ\www\xmlseclibs-master\src\XMLSecurityDSig.php on line 119

    Warning: DOMDocument::loadXML(): Extra content at the end of the document in Entity, line: 1 in C:\AppServ\www\xmlseclibs-master\src\XMLSecurityDSig.php on line 119

    gracias por la ayuda

    1. Nuevamente, no conozco esa librería 😛 Lee su documentación sobre cómo debes de usarla. Prueba usar var_dump ($domDocument); para que veas qué es lo que contiene. Te apuesto que tiene un string, y no un objeto DOMDocument 😀

      Esos dos errores del loadXML() es probable que sea por que tu XML está mal formado.

  9. Hola, Oliver.

    La próxima semana empiezo mi proyecto de facturación con Python y el framework Django, pero comentas que no hay librerías decentes de SOAP en Python, además no se si encontraré alguna librería para firmar el XML.

    Qué problemas crees que me pueda encontrar si desarrollo el proyecto en Python? Algún consejo?

    1. Hola Victor

      Paja que uses Django 😀 Yo hice una búsqueda rápida (por desesperación para acabar el proyecto), pero sí existen varias librerías para SOAP en Python. Es cuestión que pruebes. También hay librerías para firmar XML. Hay de todo en Python 😀 Yo me rendí a la flojera, y usé el libxml2, que es un programa externo 🙂

      Ya estoy a finales de acabar mi API de facturación, por si quieres evitar la fatiga de desarrollar todo 😉 publicaré más detalles aquí, en este blog.

  10. Esta semana voy a probar librerías de Python para firmar los XML y enviarlo al servicio SOAP de Sunat, después de haber investigado un poco creo que esta es la principal barrera.
    Si no logro hacerlo, mi segunda alternativa era justamente trabajar con una API, para no reinventar la rueda. Espero noticias de tu API.

    1. Hola pudiste encontrar una manera de realizar el proceso de SOAP con python. Actualmente estoy usando la librería de suds para conectarme por SOAP y signxml para la firma del XML.
      El problema es que a veces con suds no se conecta a la primera y tengo que ponerlo en un for para que se conecte varias veces hasta que agarre, quería saber si te ha pasado algo similar y como lo arreglaste.
      Estos son los 2 errores que me sale

      Intento 1, obtuvo el error: HTTP Error 503: Service Temporarily Unavailable al conectarse a https://e-beta.sunat.gob.pe/ol-ti-itcpfegem-beta/billService?wsdl

      Intento 2, obtuvo el error: import schema (http://service.sunat.gob.pe) at (https://e-beta.sunat.gob.pe/ol-ti-itcpfegem-beta/billService.xsd2.xsd), failed al conectarse a https://e-beta.sunat.gob.pe/ol-ti-itcpfegem-beta/billService?wsdl

  11. Yo tengo el siguiente error al enviar una factura electrónica, el xml está firmado con un certificado de pruebas, y lo envio al web service beta.

    “Uncaught SoapFault exception: [soap-env:Client.2335] El documento electrónico ingresado ha sido alterado – Detalle: Incorrect reference digest value “

    1. Hola Alfredo, estás firmando mal el certificado 🙂 Usa alguna herramientas para comprobar el firmado de un XML, hay varias por ahi. Yo uso para firmar el XML xmlsec, el que también tiene una función para comprobar: xmlsec1 --verify --trusted-pem ruta/al/certificado/ca.pem ruta/al/comprobante.xml

  12. Hola, cuando firmo con el .p12 me salen 4 firmas en el archivo. También ya no estoy usando el sha1, llama.pe me dijo que ahora es sha256. Las preguntas son:

    1. Uso xmlsec pero me sale 4 firmas en X509Certificate
    2. Que tan cierto es eso? De no usar el SHa1

    1. ¡Hola!

      El PKCS puede contener varios certificados. En los certificados que he visto venían 2: el certificado en si, y su certificado ‘papá’ (que le llaman una “cadena de confianza“). Solo es necesario el último certificado (el tuyo) es el necesario. No recuerdo ahorita, pero con el comando openssl puedes extraerlo. Vale la pena una googleadita 🙂

      Sobre el SHA1, es cierto. El SHA1 ya ha caído en desuso por que han encontrado formas de romperlo (Google logró construir dos PDFs distintos con el mismo hash SHA1. Más info aquí). Tienes razón, debería yo también usar el SHA256, gracias por el dato 👍

      1. Tenías razón, había más certificados. Saque mi certificado y mi llave pública y con eso los firme.

        Ahora tengo un problema con el servidor de producción, me sale este error
        IndexError: No definition ‘{http://service.sunat.gob.pe}billService’ in ‘port_types’ found.

        He mirado que el xml de producción varia con el beta, hay algo para tener en cuenta antes del envío?

        1. Recuerda que php tiene un bug que no permite usar namespaces, que usa el wsdl del server de produccion de la sunat. Por eso hice el hack que describí en este blog post

        2. Yo lo resolví haciendo un programa externo (C#)que se encarga solamente del envío a Producción de la SUNAT, el cual lo ejecuto desde PHP….

  13. Hola al momento de hacer esto:
    $ruc = ‘20563050501’;
    $usuarioSol = ‘MODDATOS’;
    $claveSol = ‘moddatos’;

    $WSHeader = ‘

    ‘ . $ruc. $usuarioSol. ‘
    ‘ . $claveSol. ‘

    ‘;

    $fileName = ‘20563050501-03-B002-00000001.zip’;
    $contentFile = (file_exists(‘../documentos/pagosxml/mayo/20180514/20563050501-03-B002-00000001.zip’) ? base64_encode(file_get_contents(‘../documentos/pagosxml/mayo/20180514/20563050501-03-B002-00000001.zip’)) : ‘no existe el archivo’);
    $array = array(‘filename’ => $fileName, ‘contentFile’ => $contentFile);
    $headers = new SoapHeader(‘http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd’, ‘Security’, new SoapVar($WSHeader, XSD_ANYXML));
    $soap = new SoapClient(‘https://e-beta.sunat.gob.pe/ol-ti-itcpfegem-beta/billService?wsdl’);
    $soap->__soapCall(‘sendBill’, $array, null, $headers);

    No me retorna nada, queda en vacío, me ayudas con esto por favor.

    1. Hola! Recuerda que el 2do parámetro de __soapCall es un array dentro de un array, por alguna razón. Esa línea debería quedar algo como $soap->__soapCall(‘sendBill’, [$array], null, $headers);.

      1. Hola DrMad que tal, me muestra este error: Fatal error: Uncaught SoapFault exception: [soap-env:Client.0151] El nombre del archivo ZIP es incorrecto – Detalle: xxx.xxx.xxx value=’ticket: error: Error de nombre archivo ” codigo cpe: ” no es un cpe valido’ in C:\ubicacion\test.php:45 Stack trace: #0 C:\ubicacion\test.php(45): SoapClient->__soapCall(‘sendBill’, Array, NULL, Object(SoapHeader)) #1 [internal function]: Test->index() #2 C:\ubicacion\core\CodeIgniter.php(359): call_user_func_array(Array, Array) #3 C:\ubicacion\index.php(215): require_once(‘C:\\ubicacion..’) #4 {main} thrown in C:\ubicacion\test.php on line 45.

        quería saber si estoy por buen camino, entiendo que debo cambiar el nombre del zip y xml segun el formato de la sunat y crei que debia poner una carpeta dummy dentro del zip por lo que decia la documentacion de la sunat. Saludos

        1. Hola de nuevo.

          No, no va la carpeta ‘dummy’, tal como lo describo en este mismo post. Sería bueno que me comentes qué nombre estás colocando al ZIP y al XML dentro, pues la SUNAT te informa que estas colocándolo mal.

          1. Hola Drmad, el zip y xml tienen el mismo nombre (obviamente diferente extension :B), que es así
            RUC-TIPOcomprobante-[(B)o(F) depende si es fact o bol]serie-correlativo(numeracion de la boleta) y finalmente la extensión. solo es el zip y xsml ya saque la carpeta dummy y a pesar de eso me muestra el mensaje de error de nombre. Help me!!! Saludos hermano y gracias por la ayuda que me brindas 😀

          2. Hey Alejandro.

            Obviamente, estás formando mal el nombre, por eso te pedí que me enviaras exactamente lo que estás consignando ahi.

            Si quieres, puedes enviarlo directo a mi al correo “yo” en este dominio 🙂

  14. Hola drMad, el nombre de los archivos esta así: 20563050501-03-B002-00023275(.zip)(.xml). lo que no entiendo es que se pone en el digestValue, lo que vi en la doc de la sunat es el valor hash codificado en Base64, no entiendo esa parte(que es lo que se debe codificar en base64? que valor? ) .

    1. El nombre está bien, es probable que tu programa lo esté creando mal 😀 El digestValue se obtiene después de firmar el documento. Es un procedimiento complejo, yo opté por usar un programa externo para ello. También lo podrías hacer tu, PHP tiene librerías de OpenSSL para ello.

        1. ¿Creo que te refieres a la linea que remuevo para que funcione el SOAP de PHP? Esa solo está presente en el servidor de producción de la SUNAT, no en la beta.

    1. Hola.

      Como digo en el post, yo no uso PHP para el firmado del XML, uso un programa externo. Luego leo el XML firmado, y obtengo esos valores. Es posible firmar el XML con PHP, usando OpenSSL o alguna clase que sé que existe en las interwebs 🙂 pero conmigo, la flojera pudo más 😀

  15. Hola drmad, puedes por favor compartirnos el comando exacto del xmlsec1 para firmar un xml? O explicar un poco mas acerca del proceso de firma de un xml? (si es que hay que manipular la estructura del xml previamente al firmado). Gracias!!

    1. Hola, este sería el comando con xmlsec1 :

      $xmlsec1 –sign –output documento_firmado.xml –pkcs12 certificado.p12 –pwd clave_certificado documento_a_firmar.xml

      El archivo con extension p12 contiene un certificado y su clave privada respectiva. Eso lo puedes armar con la libreria OpenSSL.

      Saludos.

  16. Hola, tu post me alivia mucho. Ya que yo estoy intentando hacer lo mismo, pero en mi caso necesito hacerlo en Python. Tengo el armado de trama y el envío (el cuale estoy haciendo mediante la librería Zeep). Sin embargo no logro que el ws de la sunat me de una respuesta de que la factura se ha ingresado, al menos no por código. Sin embargo, si es que mi xml, dentro de mi .zip lo introduzco en SoapUI directamente junto al resto de la trama requerida. El documento ingresa.
    Regresando a lo aliviado que me siento por tu post, la razón es que indica que no utilicemos la librería base64. ¿Por qué lo dices? ¿Sabes de otra librería que podría codificar en base64 mi .zip para realizar el envío de la trama utilizando Python?
    Gracias de antemano.

    1. 😃 Me alegra saber tu alivio, y que usas python 😄

      Creo que te refieres a un comentario donde indico que no use la función base64_encode en el zip antes de enviarlo por SOAP. En ese caso, es por que la librería de SOAP de PHP va a codificar el adjunto automáticamente. Si usas el base64_encode, el fichero acabará sobre-codificado, y enviará el base64 del base64 del zip.

      1. Ese error lo cometía yo también en Python con la libreria respectiva.

        Tu post también me fue de mucha ayuda, Oliver, quería hacerte una consulta aprovechando la duda del amigo. ¿Lograste desarrollar las salidas en XML para la versión 2.1 de ubl?

        Gracias de antemano.

        1. Hola Marco, me alegra también leer eso 😁

          Y no, aun no he acabado de implementar el 2.1 😟 Problemas, problemas everywhere…

          Aunque hoy recibí una buena noticia: La SUNAT va a empezar a dar gratis los certificados digitales a las empresas con ingresos anuales que no pasen los 300 UIT 😋

          Eso se merece un blogpost 😁

          1. Jaja, sí, esa es una buena noticia! Aparte lo de los OSE, no sé cómo van a hacer con ese tema, hasta hay una demanda de por medio a la SUNAT. Lo de los certificados gratis creo que se dará hasta el otro año, pero bueno es un avance… Lo malo de la versión 2.1 es la cantidad de tipo de factura o notas de crédito/débito que hay, por ejemplo o los pocos modelos de ejemplo que publica SUNAT para los diferentes casos. Esperemos que mejoren esa parte para implementarlo.

            Saludos!

        2. Oof!
          Eso era lo único que estaba haciendo mal. Lo estaba codificando a base64 dos veces. Yo utilizo Zeep y Zeep también lo codifica en base64 automáticamente antes de enviarlo. Que gran alivio. Ahora solo falta esperar a que me den mi firma digital gratis :v Por si acaso esperaré sentado :v

          1. Hola, yo tengo un error con la URL de producción de SUNAT (https://e-factura.sunat.gob.pe/ol-ti-itcpfegem/billService?wsdl). Al intentar conectarme me manda el siguiente error:
            IndexError: No definition ‘{http://service.sunat.gob.pe}billService’ in ‘port_types’ found

            Actualmente estoy usando python y el paquete de suds.
            Como lo solucionaste en ese caso?
            También quise usar Zeep pero no me dejaba conectarme al servidor de prueba y pase a usar suds.

    2. Hola yo uso Python 3.6 para enviar mis documentos electrónicos, tuve algunos problemas, pero ya están solucionados. Si deseas ayuda puedes hablarme. Saludos!

  17. Hola @drmad muy bueno el artículo. Pero actualmente tengo problemas con la verificación del RSA . Estoy usando UBL 2.1. Tienes un correo para ponernos en contacto ?

  18. Hola este es mi código no me devuelve ningún resultado, sólo un par de veces me devolvió que no podía cargar el archivo xsd2.xsd:

    /* INICIO */

    $WSHeader = ‘

    USUARIO
    CLAVE

    ‘;
    $headers = new SoapHeader(‘http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd’, ‘Security’, new SoapVar($WSHeader, XSD_ANYXML));

    $soap = new SoapClient(‘https://e-beta.sunat.gob.pe:443/ol-ti-itcpfegem-beta/billService?wsdl’,
    [
    ‘cache_wsdl’ => WSDL_CACHE_NONE,
    ‘trace’ => TRUE ,
    ‘soap_version’ => SOAP_1_1
    ]
    );

    $argumentos=[‘filename’ => $sNombre.’.zip’, ‘contentFile’ => base64_encode(file_get_contents(PATH_SESSION_SAVE.$sNombreZip))];

    unlink(PATH_SESSION_SAVE.$sNombreXml);
    unlink(PATH_SESSION_SAVE.$sNombreZip);

    $result=$soap->__soapCall(‘sendBill’, $argumentos, null, $headers,$output);

    if (is_soap_fault($result)) {
    trigger_error(“SOAP Fault: (faultcode: {$result->faultcode}, faultstring: {$result->faultstring})”, E_USER_ERROR);
    }
    else
    {
    echo $result;
    }

    /* FIN */

    Si pudieras ayudarme por favor

  19. Hola, que tal. Tengo un problema al usar SoapClient y el wsdl de prueba de la Sunat.
    Este es mi codigo.

    $service = “https://e-beta.sunat.gob.pe/ol-ti-itcpfegem-beta/billService?wsdl”;
    $fileName=’../../FE/ENVIADOS/10450775482-01-F002-40.ZIP’;

    $RUC = “10486573212”;
    $USUARIO_SOL = “MODDATOS”;
    $CONTRASEÑA_SOL = “MODDATOS”;

    $WSHeader = ‘
    ‘ . $RUC . $USUARIO_SOL . ‘
    ‘ . $CONTRASEÑA_SOL . ‘
    ‘;

    $soap = new SoapClient($service, [
    ‘cache_wsdl’ => WSDL_CACHE_NONE,
    ‘trace’ => TRUE ,
    ‘soap_version’ => SOAP_1_1 ]
    );

    $argumentos = array( array(‘fileName’ => $fileName, ‘contentFile’ => base64_encode(file_get_contents($fileName))) );

    $headers = new SoapHeader(‘http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd’, ‘Security’, new SoapVar($WSHeader, XSD_ANYXML));

    $result = $soap->__soapCall(‘sendBill’, $argumentos, null, $headers);

    Cuando ejecuto mi codigo y luego hago un var_dump a $result, me devuelve NULL. Luego al usar $soap->__getLastResponseHeaders() me devuelve un error 500. Que estoy haciendo mal?

  20. Hola tengo el siguiente mensaje de excepción:

    faultcode = Client.0151
    faultstring = El nombre del archivo ZIP es incorrecto – Detalle: xxx.xxx.xxx value=’ticket: error: Error de nombre archivo ” codigo cpe: no es un cpe valido’

    El nombre del archivo es 20600450906-01-F001-9999, factura firmada. Si se hace la prueba con el SOAPUI el ws de la SUNAT me devuelve la respuesta correcta “La factura ha sido aceptada”. Pero si lo hago desde mi aplicación PHP obtengo el mensaje de excepción de lineas arriba. Leí los comentarios al respecto en este post, pero no queda claro como se soluciona, ya que la excepción esta rechazando por el nombre mal formado del archivo xml y zip.

    Alguna idea?? Dejo mi codigo. Gracias de antemano.

    <?php

    // Get the zip file and convert into string
    $fileName = '20600450906-01-F001-9999.zip';
    $file = file_get_contents($fileName);

    // Encode the image string data into base64
    $dataBase64 = base64_encode($file);

    // Display the output
    // echo $dataBase64;

    echo 'Creando variables… .’;
    $wsdlURL = ‘https://e-beta.sunat.gob.pe:443/ol-ti-itcpfegem-beta/billService?wsdl’;
    $username = ‘20600450906MODDATOS’;
    $password = ‘moddatos’;

    echo ‘Creando variable de Auth…’;
    $xmlAuth = ‘

    ‘ . $username . ‘
    ‘ . $password . ‘

    ‘;
    $varAuth = new SoapVar($xmlAuth, XSD_ANYXML);

    $options = [
    ‘uri’ => ‘http://schemas.xmlsoap.org/soap/envelope/’,
    ‘style’ => SOAP_RPC,
    ‘use’ => SOAP_ENCODED,
    ‘soap_version’ => SOAP_1_1,
    ‘cache_wsdl’ => WSDL_CACHE_NONE,
    ‘connection_timeout’ => 0,
    ‘trace’ => true,
    ‘encoding’ => ‘UTF-8’,
    ‘exceptions’ => false
    ];

    echo ‘Iniciando SOAP Client…’;
    $client = new SoapClient($wsdlURL, $options);

    // $headers = array();
    // $headers[] = new SoapHeader(“http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd”, ‘UsernameToken’, new wsHeader(‘20600450906MODDATOS’, ‘moddatos’));
    $header = new SoapHeader(‘http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd’, ‘Security’, $varAuth, false);
    $client->__setSoapHeaders($header);

    $parametros = [
    [
    ‘fileName’ => $fileName,
    ‘contentFile’ => $dataBase64
    // ‘partyType’ => ‘?’
    ]
    ];

    echo ‘Cargando parametros…’;
    $response = $client->sendBill($parametros);
    // $response = $client->__soapCall(‘sendBill’, $parametros, null, $headers);

    print_r($response);

    if (!empty($response->faultcode)) {
    echo ‘Fault code: ‘ . $response->faultcode . ”;
    echo ‘Message fault: ‘ . $response->faultstring . ”;
    exit;
    }

    echo ‘Resultado : ‘;

    if (!empty($response->sendBillResponse)) {
    echo ‘Response : ‘. $response->applicationResponse . “\n”;
    }

    ?>

    1. Para empezar, no ejecutes base64_encode en el fichero, por que la librería de SOAP también hará lo mismo, y enviarás un fichero doblemente codificado. Segundo, el segundo parámetro de __soapCall es un array. Si intentas pasar como contenido del parámetro un array, entonces tienes que colocar el array dentro de otro array… Asumo que lo mismo se aplica cuando llamas directo al método WSDL. Prueba usando $response = $client->sendBill([$parametros]);

      Todo esto ya está escrito en este blogpost 😛

  21. Gracias por la respuesta. Te comento que al invocar el método sendBill no hay necesidad de enviar la matriz $parametros en otra matriz:

    $parametros = array(
    ‘fileName’ => $filename,
    ‘content’ => $file
    );
    $response = $client->sendBill($parametros);

    En cuanto al resto de sugerencias, me sirvio de mucho. Una vez mas gracias.

  22. Buen día, tengo un inconveniente. Lo que pasa es que tenía desarrolada la conexión de otra forma distinta a la que usted ha realizado y me estuvo funcionando correctamente hasta hace unos días que me empezó a mostrar el error “perfil no autorizado para emitir comprobantes electrónicos”. Después de “pelear” unos días con la Sunat me indicaron que mis usuarios secundario estaban correctos y que el error estaba en que mi conexión estaba incompleta y debía guiarme de la conexión tal cual está en su manual, googleando llegué hasta aquí y estuve tomando tu ejemplo pero me muestra el error “An uncaught Exception was encountered Type: SoapFault Message: Internal Error” y me señala la línea $client->__soapCall(‘sendSummary’, $params, null, $headers);. Mi código es el siguiente:

    $zipXml = $filename.’.zip’;
    $params = array(
    ‘fileName’ => $zipXml,
    ‘contentFile’ => file_get_contents(‘adjunto/xml/boletas/’.$fileBoleta.’/’.$zipXml)
    );

    $service = ‘adjunto/wsdl/billService.wsdl’;
    $WSHeader = ‘

    20600258894MODDATOS
    MODDATOS

    ‘;

    $headers = new SoapHeader(‘http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd’, ‘Security’, new SoapVar($WSHeader, XSD_ANYXML));
    $client = new SoapClient($service, array(
    ‘cache_wsdl’ => WSDL_CACHE_NONE,
    ‘trace’ => TRUE
    ));

    $client->__soapCall(‘sendSummary’, $params, null, $headers);
    $status = $client->__getLastResponse();

    Cabe resaltar que aquí he colocado el usuario de prueba pero el error me sale con el usuario del usuario secundario creado en la clave sol. Si tiene alguna sugerencia, comentario o algo que me podría ayudar se lo agradecería mucho. Saludos.

      1. Hola, hace unos días sunat está presentando problemas con su web services y eso incluye los archivos .wsdl que se descargan para poder conectarse con el servicio, cuando me comuniqué con ellos me dijeron que podía emitir los resumenes diarios a través de https://www.sunat.gob.pe/ol-ti-itcpfegem/billService?wsdl, que según ellos es su servicio antiguo, el problema es que no se como descargar el .wsdl de ese web service o como poder modificar el que ya tengo para que apunte a ese link. Si usted sabe como solucionar eso agradecería su ayuda.

        Saludos.

  23. Oliver gracias por compartir los conocimientos. Una consulta, estoy obteniendo este error y no doy con la solución tu ayuda por favor.
    Quiero consumir el servicio sendBill, este es el request:
    20519019168MODDATOS MODDATOS 20519019168-01-F001-00000042.zipVUVz.. he cortado este dato UFBQQ==

    Nombre del archivo zip: 20519019168-01-F001-00000042.zip
    Nombre del archivo xml: 20519019168-01-F001-00000042.xml
    Contenido del archivo 20519019168-01-F001-00000042.xml:

    8x recorté este dato uI=

    jA7CVB recorté este dato BG0IgZ8YCQ==

    MIIKNjCCC recorté este dato aXyrJQuMY=

    2.12.0F001-000000422019-05-0100:00:0001PEN1
    obInvoiceSign

    20519019168

    #obInvoiceSign

    20519019168

    0001

    20600881851

    46.98
    261.0246.98
    S
    1000IGVVAT
    261.02308.00308.00
    122308.00

    14.000000001
    46.98
    261.0246.98
    S18.0010
    1000IGVVAT

    SE-999
    11.8644068

  24. Oliver ordené la consulta y coloqué el código en dos pastebin.

    * Error que devuelve servicio sendBill: [soap-env:Client.0157] El archivo ZIP no contiene comprobantes

    1. Request: https://justpaste.it/2rd8m
    2. Nombre de archivo zip: 20519019168-01-F001-00000042.zip
    3. Nombre de archivo xml: 20519019168-01-F001-00000042.xml
    4. Contenido de archivo 20519019168-01-F001-00000042.xml: https://justpaste.it/2dbvt

    Muchas gracias de antemano por la ayuda

    1. Todo se ve bien. Por el error podemos deducir que hay un problema dentro del zip. ¿Estás seguro que estas zipeando correctamente el XML? Graba el ZIP por ahi, y ábrelo 😃 Quizas estás grabándolo mal. Ojo que también esa carpeta ‘dummy’ que indica la documentación de la sunat no debe de ir, como indiqué en el apartado ‘ZIP de la discordia’.

      Saludos!

  25. Hola amigo una consulta con respecto al error del doble nameespace ;
    atal error: Uncaught SoapFault exception: [WSDL] SOAP-ERROR: Parsing WSDL: ‘BillServicePortBinding’ already defined in

    estoy intentando hacer pasar mi comprobante s ya a produccion,
    mi linea de codigo es esta:
    //Llamada al WebService=======================================================================
    $service = $webservice;
    $headers = new CustomHeaders($nruc.$usuarioSol, $claveSol);
    $client = new SoapClient(‘https://e-factura.sunat.gob.pe/ol-ti-itcpfegem/billService?wsdl’, [‘cache_wsdl’ => WSDL_CACHE_NONE, ‘trace’ => TRUE , ‘soap_version’ => SOAP_1_1 ] );

    segun leyendo tus primeras sugerenecias es borrar la fila:

    y guarde el xml en un archivo dentro de la carpeta donde corre mi php pero al ponerlo en la posicion:
    //Llamada al WebService=======================================================================
    $service = $webservice;
    $headers = new CustomHeaders($nruc.$usuarioSol, $claveSol);
    $client = new SoapClient(‘archivomodificado.xml’, [‘cache_wsdl’ => WSDL_CACHE_NONE, ‘trace’ => TRUE , ‘soap_version’ => SOAP_1_1 ] );

    tampoco me envia quizás este haciendo la referencia mal, si podrías indicarme por favor te lo agradeceria.

    1. Asegúrate de que ‘archivomodificado.xml’ exista en la carpeta donde tu script de PHP se está ejecutando. Es mejor si colocas la ruta absoluta (i.e. “/home/eduardo/wsdl_de_la_sunat/archivomodificado.xml”), y también debes de descargar los demás ficheros que el WSDL principal incluye: billService?ns1.wsdl y billService.xsd2.xsd

  26. esta linea de mi archivo principal quedal igual

    o en el location= debe ir el mismo nombre del archivo principal que en este caso es archivomodificado.xml

  27. Ya me salio dr. solo que lo puse esos tres en mi unidad d:
    y salio ahora si quiero ponerlo en mi proyecto de xampp ejemplo:
    xampp/httpdocs/sistema/modelos/

    tengo que poner c:/xampp/httpdocs/sistema/modelos/
    o
    sistema/modelos/

    gracias de antemano.

  28. DrMad por favor podrías compartir el código de cómo conviertes el archivo.zip a una cadena para enviarlo.

    Es decir cómo obtienes lo que va en contentFile a partir del archivo zipeado:
    UEsDBBQAAgAIANQ6…….

    graciassss. Bendiciones

    1. Hola! Solo añade el contenido del zip al array de datos, PHP convertirá la información binaria del ZIP en el base64 requerido por el SOAP, algo como $soap->__soapCall('sendBill', [['filename' => 'elnombredelficherosegunlasunat.zip', 'contentFile' => file_get_contents('ruta/al/fichero.zip')]]); .

  29. Dmard cómo estásss help en cómo esto: Consumo correctamente el servicio pero no sé cómo “traducir” la respuesta que envía Sunat:
    applicationResponse = “UEsDBBQAAgAIAKKUMU8…………”

    Entiendo que es un archivo xml zipeado y convertido a una cadena base64 pero en PHP como transaformo eso a un texto “entendible” muchisimasss gracias

  30. drmad,

    Una consulta. Yo he obtenido un certificado de prueba (LLAMA-PE-CERTIFICADO-DEMO-20519019168.pfx) de la página llama.pe y luego con el utilitario que ellos brindan obtuve el .key y .cer a partir del pfx. Sin embargo me da error. Comparto el request:

    https://justpaste.it/1ywnn

    Por favor alguna idea por la que me da el error. La respuesta que obtengo es:
    Fatal error: Uncaught SoapFault exception: [soap-env:Client.2335] El documento electrónico ingresado ha sido alterado – Detalle: *RSA signature did not verify*

      1. drmad,

        Este es el código que estoy utilizando para firmar el documento xml. Te comparto en el Sgte. Link:
        https://justpaste.it/21ogr

        Un favor tendrás un ejemplo de un documento xml para utilizarlo como ejemplo y compararlo con uno que yo estoy generando para ver que me falta. Gracias

  31. Mi estimado, quisiera que me eche una mano, tengo un inconveniente a la hora de recibir la respuesta de sunat, al ejecutar este codigo:
    echo $response->applicationResponse;
    y lo muestro por consola a través de Ajax:
    $.ajax({
    url:’include/ctlConvertZip.php’,
    type:’POST’,
    data: {‘pushSunat’:pushSunat},
    dataType: ‘json’,
    success:function(r){
    console.log(r);
    }
    });
    Obtengo esto en consola:
    PKd�*Pdummy/PKd�*P�=kP��R-20487913791-01-F001-28.xml�V]��:}﯈�C��f���$
    T�-�-�ᶻo��@n��k;�v�HhV�J 9�3g�gf���]��e!�ո�Ua�!�ԕ7�,�����3��(�!�%b1�)�3ҁ�P��B�`�C�a1��u�w���a���sdA�f�9:�+�n�nG�#GXC}%�% Bp��; 7��+���:�<��(AC}$���뽇�%��,���[_�����|J�lz���\��m.�F��e�D��z�U;”�����^L���*LjÅQ1ވ�
    &\I�OM�˽G�)������a��^٬��@�O��?(*���)ی��GDCU-��z��oʕ�Γ��׳ռ��t�IP�u�Q�������(qw;���������[V�� �f�2����
    [3L�l{F�����CK����@���
    ,�����s�����ck�5xJ`���n��97���>�Y��q5y�ӕ@B_���̖.g���|ߔ4B����v�”x߫���E:��J&�8C���cr�q��.��ܗťF��M��;v�:�@ڲ@
    N�W�������?#%h�D>
    �W��X}�h���1BL��H��+r)�Ҧ̵!��~�̲[O�ξ%��zK��5A̧a�&��)��P�”J�<�_�*,�}s����\�����@��9O�T� �Iơ�_X��&
    ҷ�=�o���4�4���PKd�*Pdummy/PKd�*P�=kP��&R-20487913791-01-F001-28.xmlPK~/.
    Quisiera saber como solucionarlo, porque pienso que de esta forma no podre decodificarlo. Espero su respuesta muchas gracias.

  32. Hola, gracias por compartir tu experiencia, pero tengo una duda, realicé todo tal cual como indicaste, y SUNAT recibió mi archivo ZIP y me envió la respuesta, todo bien, pero al revisar el XML de respuesta, me di cuenta que el mensaje no era de aceptación, sino de información de un error, y el error es “el certificado usado no es el mismo informado a SUNAT”, es el mismo PFX usado para su facturador, con el CER informado a SUNAT, pero me resulta en ése error ahora que lo uso desde mi sistema.
    Mi sospecha es que como ahora se uso la extención PEM para firmar con Openssl, pues la SUNAT no reconoce la firma, o a lo mejor me podrías orientar mejor? quisá esté haciendo algo mal?
    Previamente usaba el facturador 1.3 con un certificado PFX y a SUNAT le informé del archivo CER, y todo funcionaba correctamente con su facturador.

  33. hola, hice todos los pasos de arriba, que se indican… pasando de las excepciones de wdsl en php.
    pero no se conecta al servicio de sunat.
    solo me sale : Bad Request
    o Bad Gateway

    alguien sabe en que estoy fallando.
    he descargado billService.wsdl
    y en el import apunta a billService.ns1.wsdl
    y luego apunta a billService.xsd2.xsd

      1. Entonces no hay solucion alguna para bad request.. y ah veces ingresa al servicio de sunat y dice,
        El sistema no puede responder a su solicitud…
        Sabes si es problema de sunat o codigo php

        1. Ese error “El sistema no puede responder a su solicitud” lo da la SUNAT (está en español, para empezar). Esos errores son ‘excepciones’ (código menor a 2000) y con la mayoría, solo tienes que reintentar hasta que salga. Para el ‘bad request’ quizas también sea de la SUNAT, la única forma de estar seguro es que me muestres el request HTTP del SOAP. Dale una mirada a https://www.php.net/manual/en/soapclient.getlastrequestheaders.php

          1. bueno tambien me sale
            Error Fetching http headers

            ahorita mi codigo es ese y me responde : Internal Server Error

            $service = ‘billService.wsdl’;
            $soap = new SoapClient($service, [
            ‘cache_wsdl’ => WSDL_CACHE_NONE,
            ‘trace’ => TRUE ,
            ‘soap_version’ => SOAP_1_1
            ]
            );

            $ruc = ‘ruc’;
            $usuarioSol = ‘usuario’;
            $claveSol=’clave’;

            $WSHeader = ”
            ‘.$ruc.$usuarioSol.’
            ‘.$claveSol.’
            “;
            $headers = new SoapHeader(‘http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd’, ‘Security’, new SoapVar($WSHeader, XSD_ANYXML));

            try {
            $params = array(array(‘fileName’ => $fileNam, ‘contentFile’ => file_get_contents($filezip)));
            $status = $soap->__soapCall(‘sendBill’, $params, null, $headers);
            echo $status;
            } catch(Exception $e) {
            $men = $e->getMessage();
            echo json_encode($data);
            }

Agregue un comentario a Alfredo Cancelar respuesta

Su dirección de correo no se hará público. Los campos requeridos están marcados *