En este post voy a detallar algo que a muchos desarrolladores preocupa en el Perú, el envío de los casos de prueba al servicio Web de Facturación Electrónica de SUNAT.

Primero que nada, andan rondando por la Web muchos post y alguno que otro consejo de como invocar al servicio con el siguiente código:

  <system.serviceModel>
    <bindings>
      <basicHttpBinding>
        <binding name="BillServicePortBinding" closeTimeout="00:01:00"
openTimeout="00:01:00" receiveTimeout="00:10:00" sendTimeout="00:01:00"
allowCookies="false" bypassProxyOnLocal="false" hostNameComparisonMode="StrongWildcard"
maxBufferSize="65536" maxBufferPoolSize="524288" maxReceivedMessageSize="65536"
messageEncoding="Text" textEncoding="utf-8" transferMode="Buffered"
useDefaultWebProxy="true">
          <readerQuotas maxDepth="32" maxStringContentLength="8192" maxArrayLength="16384"
maxBytesPerRead="4096" maxNameTableCharCount="16384" />
          <security mode="Transport">
            <transport clientCredentialType="None" proxyCredentialType="None" realm="" />
            <message clientCredentialType="UserName" algorithmSuite="Default" />
          </security>
        </binding>
        <binding name="secured">
          <security mode="TransportWithMessageCredential">
            <message clientCredentialType="UserName" />
          </security>
        </binding>
      </basicHttpBinding>
    </bindings>
    <client>
      <endpoint address="https://www.sunat.gob.pe/ol-ti-itcpgem-sqa/billService"
binding="basicHttpBinding" bindingConfiguration="BillServicePortBinding"
contract="DocumentosSunat.billService" name="BillServicePort" >
        <headers>
          <wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
            <wsse:UsernameToken >
              <wsse:Username>20123456789MODDATOS</wsse:Username>
              <wsse:Password Type='http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText'>MODDATOS</wsse:Password>
            </wsse:UsernameToken>
          </wsse:Security>
        </headers>
      </endpoint>
    </client>
  </system.serviceModel>

Hasta aquí todo esta correcto, pero, ¿Qué pasa si necesitamos que estas credenciales se coloquen de manera dinámica en tiempo de ejecución? Puede ser por un tema de seguridad, personalización, etc.

Este post es una guía para aquellos que no les gusta “harcodear” en el Config, anteriomente ya había escrito un post que ayudaba a usar el protocolo WS-Security al momento de llamar al Servicio de SUNAT, pero recientemente descubrí con horror que si quitas el elemento Headers del Endpoint SUNAT te devolverá el siguiente error:

0101 El encabezado de seguridad es incorrecto

o

0154 El RUC del archivo no corresponde al RUC del usuario

Entonces pensé que si esto sucede a pesar de proporcionarle las credenciales en  tiempo de ejecución daba igual porque el servicio toma en cuenta siempre la sección Headers en el archivo de configuración.

La solución

Para poder resolver esto es necesario crear dos clases como en el post anterior que ya les había explicado, y específicamente colocar este código en la clase que implementa la interfaz IClientMessageInspector en la función BeforeSendRequest:

public class PasswordDigestMessageInspector : IClientMessageInspector
{
    public string Username { getset; }
    public string Password { getset; }
 
    public PasswordDigestMessageInspector(string username, string password)
    {
        Username = username;
        Password = password;
    }
 
    #region IClientMessageInspector Members
 
    public void AfterReceiveReply(ref Message reply, object correlationState)
    {
        return;
    }
 
    public object BeforeSendRequest(ref Message request, System.ServiceModel.IClientChannel channel)
    {
        UsernameToken token = new UsernameToken(Username, Password, PasswordOption.SendPlainText);
 
        XmlElement securityToken = token.GetXml(new XmlDocument());
 
        // Modificamos el XML Generado.
        var nodo = securityToken.GetElementsByTagName("wsse:Nonce").Item(0);
        nodo?.RemoveAll();
 
        MessageHeader securityHeader = MessageHeader.CreateHeader("Security",
            "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd",
            securityToken, false);
        request.Headers.Add(securityHeader);
 
        return Convert.DBNull;
    }
 
    #endregion
}

Luego, procedemos a dejar la sección del Endpoint así:

    <client>
      <endpoint address="https://www.sunat.gob.pe/ol-ti-itcpgem-sqa/billService"
binding="basicHttpBinding" bindingConfiguration="BillServicePortBinding"
contract="DocumentosSunat.billService" name="BillServicePort" />
    </client>

El código para invocar el servicio sería el siguiente:

public Connect(string ruc, string username, string password)
       {
           System.Net.ServicePointManager.UseNagleAlgorithm = true;
           System.Net.ServicePointManager.Expect100Continue = false;
           System.Net.ServicePointManager.CheckCertificateRevocationList = true;
 
           _proxy = new Sunat.billServiceClient();
 
           // Agregamos el behavior configurado para soportar WS-Security.
           var behavior = new PasswordDigestBehavior(string.Concat(ruc, username), password);
           _proxy.Endpoint.EndpointBehaviors.Add(behavior);
 
       }

Esto deberá servirles para todos los métodos del billService.

Si tienen dudas y/o sugerencias son bienvenidos en la zona de comentarios.

Anuncios