Implementação PHP - João Pessoa/PB

Bom dia, Luiz

Você já chegou a implementar algo para a prefeitura de João Pessoa?

Estou com problemas para obter os métodos de wsdl através do endpoint de homologação.

Estou utilizando a classe ‘SoapClient’ do php para fazer isso.

Já sim.

O problema é que para acessar o endereço do webservice, você precisa de um Certificado ICP válido e que corresponda a uma empresa autorizada na prefeitura.
Isso vale tanto para o browser, quanto para o sistema que você está desenvolvendo.

Segue aqui o endereços:

Prod: https://sispmjp.joaopessoa.pb.gov.br:8443/sispmjp/NfseWSService
Homo: https://nfsehomolog.joaopessoa.pb.gov.br:8443/sispmjp/NfseWSService

Seguem os arquivos:

NfseWSService.xml (12,2 KB)
nfse-v2-02.xsd (36,9 KB)
xmldsig-core-schema20020212.xsd (10,2 KB)
schemas_ginfes.rar (18,9 KB)

Em PHP você precisa enviar o cerificado junto com a requisição.
O SOAPCliente faz isso, assim como o cURL.

Bom dia, Luiz

Desde já agradeço pela ajuda.

Estou tentando fazer a consulta dos métodos da seguinte forma abaixo:

					$client = new SoapClient($wsdl, array(
						'trace'         => 1,
						'exceptions'    => 0,
						'local_cert'	=> $certificado
						'soap_version'=> SOAP_1_2,
						'ssl' => array(
							'verify_peer'   => false,
							'verify_peer_name' => false,
							'allow_self_signed' => true
						),
					));

Mas retorna o seguinte erro:
Warning: SoapClient::SoapClient(): Unable to set local cert chain file
Check that your cafile/capath settings include details of your certificate and its issuer

Estou usando o certificado no formato ‘pfx’.

No navegador instalando consegui o acesso, mas pelo php não.

É possível você me enviar o trecho da chamada SOAPda sua implementação?

Ah sim!

Você tem que converter o certificado de .PFX para .PEM.
Pelo próprio PHP dá para fazer, mas pode usar um site para fazer isso:
https://www.sslshopper.com/ssl-converter.html

O PHP na verdade usa uma cadeia de certificados para fazer o handshake do SSL.

O problema do SoapClient é que ele perde muito tempo interpretando o WSDL para saber que método está sendo chamado e etc.
E no final das contas, ele usa o próprio cURL ou outra função interna semelhante.

Você pode fazer tudo diretamente no cURL, inclusive passar o certificado e a cadeia de certificado.

Separe em arquivos diferentes o conteúdo do .PEM salvo e mantenha os 3 arquivos.
E informe nas variáveis abaixo. Lembre-se de colocar no mesmo diretório do .php ou então coloque o caminho absoluto para os arquivos.

        try{
            $keyFile = "key.pem";
            $caFile = "ca.pem";
            $certFile = "client.pem";
            $certPass = "xxxxxx";
            $timeout = 100;

            //SOAP Headers
            $headers = array(
                "Content-type: text/xml;charset=\"utf-8\"",
                "Accept: text/xml",
                "Cache-Control: no-cache",
                "Pragma: no-cache",
                "SOAPAction: ".$action,
                "Content-length: ".strlen($request)
            );

            // PHP cURL for http connection
            $ch = curl_init();
            curl_setopt($ch, CURLOPT_URL, $location);
            curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
            curl_setopt($ch, CURLOPT_HTTPAUTH, CURLAUTH_ANY);
            curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
            curl_setopt($ch, CURLOPT_POST, true);
            curl_setopt($ch, CURLOPT_POSTFIELDS, $request); //SOAP request
            curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);

            curl_setopt($ch, CURLOPT_CAINFO, $caFile);
            curl_setopt($ch, CURLOPT_SSLCERT, $certFile);
            curl_setopt($ch, CURLOPT_SSLCERTPASSWD, $certPass);
            curl_setopt($ch, CURLOPT_SSLKEY, $keyFile);

            // converting
            $response = curl_exec($ch);
            curl_close($ch);
        } catch (Exception $e){
            $response = 'Error';
        }

        return $response;

A variável $action é o método do SOAP a ser chamado, que você encontra no WSDL.
Exemplo:

<soap:operation soapAction="http://nfse.abrasf.org.br/GerarNfse" ...

O valor da $action é no caso:

http://nfse.abrasf.org.br/GerarNfse

A variável $location é o caminho completo do webservice:

https://sispmjp.joaopessoa.pb.gov.br:8443/sispmjp/NfseWSService

A variável $request é o conteúdo com o envelope SOAP e o arquivo XML assinado e convertido para STRING LITERAL.
Segue o exemplo de um arquivo já pronto para envio:

content.soap.txt (7,3 KB)

Exemplo de XML antes de assinar:
content.rps.xml (2,0 KB)

Você pode usar o artigo abaixo para retirar o arquivos .PEM necessários diretamente do seu .PFX:

https://www.vivaolinux.com.br/artigo/Nota-Fiscal-Eletronica-2.0-em-linha-de-comando-(parte-1)?pagina=2

Que se resume nos comandos abaixo:

# Extrair chave.pem
openssl pkcs12 -in certificado.pfx -out key.pem -nocerts -nodes

# Extrair cliente.pem
openssl pkcs12 -in certificado.pfx -out client.pem -clcerts -nokeys -nodes

# Extrair ca.pem
openssl pkcs12 -in certificado.pfx -out ca.pem -cacerts -nokeys -nodes

Você pode rodar isso inclusive no Windows.

Bom dia, Luiz

Muito obrigado mesmo pela ajuda.

Fico muito feliz e grato em saber que existem canais assim e que estão dispostos a ajudar, pois se dependêssemos somente do suporte destes serviços estaríamos perdidos.

Obrigado.

Bom dia, Luiz

Desculpe estar aqui te enchendo tanto de perguntas, é que realmente nunca passei por tanta dificuldade de integração como essa de João Pessoa, e olha que já sofri e ainda sofro com o GINFES kkkk.

Extrai as chaves e segui o script conforme o exemplo, mais quando faço a chamada o cURL não da erro mais retorna sempre ‘false’ para a chamada. Será que estou fazendo alguma coisa errada?

Este é o trecho do código que estou fazendo:

    $keyFile = $_SERVER['DOCUMENT_ROOT']."/diretorio/certificado_nota/key.pem";
    $caFile = $_SERVER['DOCUMENT_ROOT']."/diretorio/certificado_nota/ca.pem";
    $certFile = $_SERVER['DOCUMENT_ROOT']."/diretorio/certificado_nota/client.pem";
    $certPass = trim($dados_config['senha']);
    $timeout = 100;
	
	$location = $wsdl;
	$action = 'http://nfse.abrasf.org.br/GerarNfse';
	$request = $envelope;

	try{
        //SOAP Headers
        $headers = array(
            "Content-type: text/xml;charset=\"utf-8\"",
            "Accept: text/xml",
            "Cache-Control: no-cache",
            "Pragma: no-cache",
            "SOAPAction: ".$action,
            "Content-length: ".strlen($request)
        );

        // PHP cURL for http connection
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $location);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_HTTPAUTH, CURLAUTH_ANY);
        curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
        curl_setopt($ch, CURLOPT_POST, true);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $request); //SOAP request
        curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);

        curl_setopt($ch, CURLOPT_CAINFO, $caFile);
        curl_setopt($ch, CURLOPT_SSLCERT, $certFile);
        curl_setopt($ch, CURLOPT_SSLCERTPASSWD, $certPass);
        curl_setopt($ch, CURLOPT_SSLKEY, $keyFile);

        // converting
        $response = curl_exec($ch);
        curl_close($ch);
    } catch (Exception $e){
        $response = 'Error';
    }
	
    echo '<pre>';
    var_dump($response);
    echo '</pre>';

será que estou errando em algum detalhe?

Qual a mensagem de erro?

Isso é que é estranho, não gera nenhum erro e a variavel $response retorn bool(false)

Segura as pontas, estou com um probleminha técnico.
Assim que resolver, te dou retorno.

Bom dia, Luiz

Ok, desde já agradeço pela ajuda.

Boa tarde, Luiz

Consegui evoluir no processo, estabeleci uma conexão com o servidor da seguinte forma:

 $options = array(
 	'local_cert' => 'DIRETORIO_DO_CERTIFICADO.pem',
 	'cache_ws'   => WSDL_CACHE_NONE,
 	'encoding' => 'UTF-8',
 );  
 
 $client = new \SoapClient('https://sispmjp.joaopessoa.pb.gov.br:8443/sispmjp/NfseWSService?wsdl', $options);
 $metodos_ws = $client->__getFunctions();
 
 print_r($metodos_ws);

Funcionou, mais ainda não entendi algumas coisas, por exemplo:

  • A cadeia de certificado que estou passando para a função é a publica, sendo assim não informo a senha do certificado;

O mais estranho é que havia tentado desta forma anteriormente, e não havia funcionado.

Ainda não enviei nenhum RPS para a prefeitura, mas como agora esta conectando vou começar os testes.

A medida que eu for evoluindo no processo vou alimentando o post.

O certificado SSL é assim mesmo.
Ele apenas verifica se o CNPJ da sua empresa está na base da Prefeitura.
Não precisa de senha, pois ele acredita que esteja instalado no Sistema Operacional.

O problema do SoapClient é a demora na requisição.
Porque ele vai baixar o WSDL toda vez, fazer o parse e criar virtualmente os métodos.

Bom dia, Luiz

Consegui conexão com o servidor implementando o script da maneira abaixo e de forma estável:

$client = new SoapClient($wsdl, array(
	'local_cert' => 'CERTIFICADO.pem',
	'passphrase' => 'SENHA_DO_CERTIFICADO',
	'cache_wsdl' => WSDL_CACHE_NONE,
));

$metodos_ws = $client->__getFunctions();

print_r($metodos_ws);

$param = array(
	'nfseCabecMsg' => $xmlHeader,
	'nfseDadosMsg' => $xmlMain,
);

$retorno = $client->RecepcionarLoteRpsSincrono($param);

Com uma particularidade com relação ao certificado, converti o mesmo de ‘.pfx’ para ‘.pem’ utilizando o seguinte comando:

# openssl pkcs12 -in filename.pfx -out cert.pem -nodes

Referência: https://www.xolphin.com/support/Certificate_conversions/Convert_pfx_file_to_pem_file

Agora quando envio o RPS o servidor retorna a seguinte mensagem:
E232 - Ocorreu um erro no processamento do arquivo

Segundo o manual de erros e alertas a mensagem é:
Procure a Prefeitura para regularização

Vou entrar em contato com a Prefeitura e verificar.

Outra coisa, gostaria de saber se você possui o exemplo de xml dos seguintes métodos:

  • CancelarNfse
  • ConsultarLoteRps
  • ConsultarNfsePorRps
  • RecepcionarLoteRpsSincrono