Usamos cookies para medir audiência e melhorar sua experiência. Você pode aceitar ou recusar a qualquer momento. Veja sobre o iMasters.
Boa noite, estou tentando enviar com PHP SOAP o XML de um lote RPS assinado para o Web Service da Betha também conhecido como Fly e-nota.
No entanto, não tenho tido nenhum retorno quanto a se deu algum erro ou algo do tipo.
Documentação https://e-gov.betha.com.br/e-nota-test/ambienteteste.faces
Ambiente Teste Webservice
https://e-gov.betha.com.br/e-nota-contribuinte-test-ws/recepcionarLoteRps?wsdl
https://e-gov.betha.com.br/e-nota-contribuinte-test-ws/consultarLoteRps?wsdl
Layout https://e-gov.betha.com.br/e-nota/manuais/layout.pdf
Modelo Conceitual http://www.abrasf.org.br/arquivos/files/NFSE-NACIONAL_Modelo_Conceitual versao 2-02.pdf
Manual de Integração ABRASF http://www.abrasf.org.br/arquivos/files/NFSE-NACIONAL_Manual_De_Integracao versao 2-02.pdf
Tem uma opção na área administrativa Fly e-nota para solicitar a prefeitura autorização para impressão de RPS tanto manual como eletrônica.
Não sei se isso é obrigatório ou se é necessário para o meu objetivo.
Parte do código para o envio:
------------------------------
function transmiteRps($xmlAssinado,$certificadoPem,$senha){
$wsdl = 'https://e-gov.betha.com.br/e-nota-contribuinte-test-ws/recepcionarLoteRps?wsdl';
$endpoint = 'https://e-gov.betha.com.br/e-nota-contribuinte-test-ws/recepcionarLoteRps';
$certificate = $certificadoPem;
$password = $senha;
$options = array(
'location' => $endpoint,
'keep_alive' => true,
'trace' => true,
'local_cert' => $certificate,
'passphrase' => $password,
'cache_wsdl' => WSDL_CACHE_NONE
);
try {
$client = new SoapClient($wsdl, $options);
$function = 'EnviarLoteRpsEnvio';
$arguments = ['EnviarLoteRpsEnvio' => ['xml'=>$xmlAssinado]];
$options = [];
$result = $client -> __soapCall($function, $arguments, $options);
} catch(Exception $e){
$result = false;
}
if($result!==false){
return xml2array($result->return);
}else{
return false;
}
}//transmiteRps
----------------------------------------------------------
function xml2array($xmlstring){
$xml = simplexml_load_string($xmlstring);
$json = json_encode($xml);
$array = json_decode($json, TRUE);
return $array;
}
-----------------------------------------------
function consultaRps($cnpj, $inscricao, $protocolo, $certificadoPem, $senha){
$wsdl = 'https://e-gov.betha.com.br/e-nota-contribuinte-test-ws/consultarLoteRps?wsdl';
$endpoint = 'https://e-gov.betha.com.br/e-nota-contribuinte-test-ws/consultarLoteRps';
$certificate = $certificadoPem;
$password = $senha;
$xml = '<?xml version="1.0" encoding="utf-8"?>
<ConsultarLoteRpsEnvio xmlns="http://www.betha.com.br/e-nota-contribuinte-test-ws">
<Prestador>
<CpfCnpj>
<Cnpj>'.$cnpj.'</Cnpj>
</CpfCnpj>
<InscricaoMunicipal>'.$inscricao.'</InscricaoMunicipal>
</Prestador>
<Protocolo>'.$protocolo.'</Protocolo>
</ConsultarLoteRpsEnvio>';
$options = array(
'location' => $endpoint,
'keep_alive' => true,
'trace' => true,
'local_cert' => $certificate,
'passphrase' => $password,
'cache_wsdl' => WSDL_CACHE_NONE
);
try {
$client = new SoapClient($wsdl, $options);
$function = 'consultarLoteRps';
$arguments = ['consultarLoteRps' => ['xml'=>$xml]];
$options = [];
$result = $client -> __soapCall($function, $arguments, $options);
} catch(Exception $e){
$result = false;
}
if($result!==false){
return $result->return;
}else{
return false;
}
} //consultaRps
--------------------------------
$certificadoPem=dirname(__FILE__).'/certificado.pem';
$senha='xxxxx'; //senha do certificado
----------------------------------
//assina RPS
$xmlAssinado=assinaRps(dirname(__FILE__).'/only_rps.xml'); //usei para assinar só a RPS
file_put_contents(dirname(__FILE__).'/rps_temp/rps1.xml','<?xml version="1.0" encoding="utf-8"?>
<EnviarLoteRpsEnvio xmlns="https://e-gov.betha.com.br/e-nota-contribuinte-test-ws"><LoteRps Id="LOTE1"><NumeroLote>1</NumeroLote><Cnpj>xxxxxxxxxxxxxx</Cnpj><InscricaoMunicipal>xxxx</InscricaoMunicipal><QuantidadeRps>1</QuantidadeRps>
<ListaRps>'.str_replace('<?xml version="1.0" encoding="utf-8"?>','',$xmlAssinado).'</ListaRps></LoteRps></EnviarLoteRpsEnvio>');
$_SESSION['nome_uri']='LOTE1';
$xmlAssinado=assinaRps(dirname(__FILE__).'/rps_temp/rps1.xml'); //usei para assinar o Lote RPS
$resultado=transmiteRps($xmlAssinado,
$certificadoPem,
$senha);
if(!empty($resultado["Protocolo"])){//se tem protocolo e sucesso
//$resultado["NumeroLote"]
//$resultado["Protocolo"]
//consulta lote e pega nfse
$nfseResposta = consultaRps('xxxxxxxxxxxxx', //cnpj
'xxxx', //inscrição municipal
$resultado["Protocolo"],
$certificadoPem,
$senha);
if($nfseResposta!==false){
echo '<textarea cols="120" rows="70">';
echo $nfseResposta;
echo '</textarea>';
}else{
echo 'Erro ao consultar RPS';
} echo "erro no envio";
}
**Vídeo Tutorial** do código NFSe acima porém para outro webservice que não é da Betha.
[https://www.youtube.com/watch?v=v1rByVX1_FY](https://www.youtube.com/watch?v=v1rByVX1_FY)
Desde já agradeço a atenção e colaboração dos colegas.
Funcionando postarei aqui a solução com o código completo para quem precisar.Bom dia Gabriel, muito obrigado pela dica.
Gostei do conceito de criar e enviar o objeto.
Vou tentar fazer como você mencionou.
Bem, pelo que entendi o mesmo conceito deve ser aplicado a função transmiteRps.
Mas a parte que diz no manual que tanto a RPS como o Lote devem ser Assinados,
a assinatura também deve ser colocada no objeto da forma que mencionasse?
Em nenhuma parte enviarei um arquivo XML do Lote RPS, apenas o objeto? quanto a senha, assinatura,... e outras coisas para ter um minimo de segurança?
O certificado eu converti para .pem Nesse caso o processo de assinar o objeto seria o mesmo praticamente que eu havia usado?
Só lembrando a parte que receberá o RPS https://e-gov.betha.com.br/e-nota-contribuinte-test-ws/recepcionarLoteRps?wsdl
Obs. No caso que fiz eu gravei o XML assinado, mas na hora de enviar não tive o Protocolo para a parte da consulta. Ou seja, algo errado mas também não tive resposta de erro do webservice.
Quanto a essas situações do certificado, terei de pesquisar, nunca precisei enviar nada assinado digitalmente via SOAP.
Veja se isso pode te ajudar: https://stackoverflow.com/a/7162316/1628790
Obrigado, vou dar uma olhada.
Aproveitando, você sabe interpretar o WSDL?
https://e-gov.betha.com.br/e-nota-contribuinte-test-ws/recepcionarLoteRps?wsdl
é que na chamada
__soapCall($function, $arguments, $options);
Tenho duvida quanto a função e os argumentos usados para o envio dos dados.
$function = 'EnviarLoteRpsEnvio';
$arguments = ['EnviarLoteRpsEnvio' => ['xml'=>$xmlAssinado]];
--- **Dúvida**: EnviarLoteRpsEnvioResponse ou EnviarLoteRpsEnvio em ambos os casos (função e nos argumentos) ?
Outra questão: ao usar **stdClass **na real não estaria criando a mesma estrutura do XML manualmente e salvando em um objeto para enviá-la ao webservice?Caso você não conheça todas as partes do WSDL, use o SoapUI para interpretar o WSDL, ele lhe ajudar bastante.
>
6 minutos atrás, didio20ss disse:
[...] ao usar stdClass na real não estaria criando a mesma estrutura do XML manualmente e salvando em um objeto para enviá-la ao webservice?
Sim e não. A ideia é criar exatamente a estrutura do XML, entretanto, a sua estrutura de XML é apenas uma string, por isso elas possuem comportamentos diferentes.
Oi, tive mais uma dúvida, tenho dois modelos de lote RPS onde um baixei da Betha e outro fornecido por uma empresa de automação que usa o sistema da Betha.
No cabeçalho dessa outra empresa tem o "e:" e no na Betha não.
Empresa Midia
<e:EnviarLoteRpsEnvio xmlns:e="http://www.betha.com.br/e-nota-contribuinte-ws">
Betha
<EnviarLoteRpsEnvio xmlns = "http://www.betha.com.br/e-nota-contribuinte-ws">
Esse "**e:**" ou as vezes já notei outras expressões no lugar,----------------
Outra dúvida
Nos exemplos da Betha tem:
envioSoapUi.xml que tem o comentário "Quando utilizar, por exemplo, o Soap-UI para envio do XML deve ser utilizada a estrutura abaixo" que teria um cabeçalho <soapenv
E tem exemplos sem isso.
Na hora de montar a estrutura do XML devo usar isso no meu código php ou não?
Outros exemplos que tem:
RecepcionarLoteRps.xml
RecepcionarLoteRpsSincrono.xml
**GerarNfse.xml** <GerarNfseEnvio xmlns = "http://www.betha.com.br/e-nota-contribuinte-ws">
*Qual a diferença do Sincrono e do outro? Qual a melhor opção?*
E eu estava a usar o **RecepcionarLoteRps**.
Me passaram a informação que gerando e enviando o XML do lote RPS para o Webservice da Betha,
o sistema deles gera a DANFE e até envia a NFSe para o e-mail do cliente se o mesmo for informado no XML.
*Então devo continuar a usar apenas o RecepecionarLoteRps ou também devo usar o GerarNfse?*
Obrigado mais uma vez>
2 horas atrás, didio20ss disse:
Esse "e:" ou as vezes já notei outras expressões no lugar,
como se chama e para que serve?
Faz diferença usar ?
É um prefixo de namespace. Quando não utilizado, tudo que é global, cai no namespace sem prefixo. Já, caso você utilizar, você pode usar o prefixo para informar o namespace utilizado.
Para saber mais, de uma olhada aqui: https://msdn.microsoft.com/en-us/library/aa468565.aspx
>
2 horas atrás, didio20ss disse:
Nos exemplos da Betha tem:
envioSoapUi.xml que tem o comentário "Quando utilizar, por exemplo, o Soap-UI para envio do XML deve ser utilizada a estrutura abaixo" que teria um cabeçalho <soapenv
E tem exemplos sem isso.
Na hora de montar a estrutura do XML devo usar isso no meu código php ou não?
essa estrutura deve ser usada quando você quiser manipular completamente a chamada do SOAP (cabeçalho e corpo da requisição). Isso normalmente acontece quando utiliza-se a biblioteca cURL. Com a biblioteca SOAP do PHP, ele já faz isso por ti.
>
2 horas atrás, didio20ss disse:
Outros exemplos que tem:
RecepcionarLoteRps.xml
RecepcionarLoteRpsSincrono.xml
**GerarNfse.xml** <GerarNfseEnvio xmlns = "http://www.betha.com.br/e-nota-contribuinte-ws">
*Qual a diferença do Sincrono e do outro? Qual a melhor opção?*
Sincrono será feita a requisição e o sistema irá esperar o retorno dessa requisição. Assíncrono é o contrário, a requisição é efetuada, o script não esperar o retorno e continua executando. Qual é melhor? Nenhum. Cada um tem seu propósito.
>
2 horas atrás, didio20ss disse:
E eu estava a usar o RecepcionarLoteRps.
Me passaram a informação que gerando e enviando o XML do lote RPS para o Webservice da Betha,
o sistema deles gera a DANFE e até envia a NFSe para o e-mail do cliente se o mesmo for informado no XML.
Então devo continuar a usar apenas o RecepecionarLoteRps ou também devo usar o GerarNfse?
Não sei, isso foge da área de programação e entra na área de negócio da empresa Betha. Entre em contato com eles e verifique o que for melhor.
Agradeço a ajuda do Gabriel Heming e outras fontes sobre o assunto.
Para quem precisar assinar e enviar lote RPS para a Betha emitindo assim a NFSe seguem algumas dicas que me ajudaram a resolver o problema:
1º - Ao gerar o XML da RPS, deve-se assinar primeiro a RPS e depois o Lote.
2º - para assinar o XML converte-se o arquivo .pfx do certificado para .pem para assim poder trabalhar com assinatura no servidor PHP
Para a conversão basta procurar na internet por "convert pfx to pem" e terá como exemplo https://www.sslshopper.com/ssl-converter.html
O meu certificado baixei da Betha e ao converter o arquivo continua contendo tanto a chave privada quanto a publica.
Por isso, após a conversão para "pem" deve-se abrir o arquivo com Bloco de Notas, Notepad++ ou outro,
Verá vários blocos de código. O primeiro é a chave primaria. Deve-se copiar o segundo bloco que é a chave publica e salvar em um novo arquivo chavepublica.pem
Irá precisar dos dois arquivos .pem para a assinatura, o completo e o com apenas a chave pública.
Biblioteca para assinar o XML https://github.com/robrichards/xmlseclibs
Vídeo bem útil do Bill Barsch da Geranet https://www.youtube.com/watch?v=v1rByVX1_FY
Usei apenas a parte para assinar o documento.
* Importante: Usar o "force_uri". Não sei se porque usei o certificado da Betha no meu caso o atributo URI não apareceu no XML como no exemplo do vídeo, mas teve efeito na assinatura. Sem essa opção ela não é válida.
3º - Para o envio usei o cURL adicionando o cabeçalho SOAP por ele.
Segundo algumas fontes, usando o somente o SOAP pode ocorre de não trazer retorno em caso de erros e ai você não sabe o que fazer. Já com o cURL você recebe sempre as mensagens. Quanto usei só o SOAP tinha erro mas a página ficava em branco, de mogo que eu não sabia o que estava acontecendo.
Agradeço ao POST do Luiz Paulo Franz aqui do fórum sobre o envio por cURL.
* Tive que fazer algumas modificações no código dele pois tava dando erro de assinatura inválida.
Tirei toda a parte do código dele que fazia alterações no XML, onde ele usava str_replace
Motivo: após você assinar um documento, ele não deve ser alterado, pois assim a assinatura se tornaria inválida.
Observações finais:
Fazer os testes no ambiente de homologação.
cuidar de acentos ou caracteres especiais no XML.
Também é bom remover os espaços em branco e quebras de linha antes de se assinar o documento.
Nunca repetir o número do lote e usar em sequência.
Espero que estas informações possam ajudar a outros.
Um forte abraço a todos
Giovani Silva
Virthuz Anunciante Virtual
Boa tarde Amigo! como você assinou o xml, digo as tags, não consegui fazer isso, só assina tudo junto. você poderia passar sua função de assinatura?
Boa tarde Josemir Costa, você conseguiu resolver sua questão?
Chegou a olhar os links que mencionei?
Biblioteca para assinar o XML https://github.com/robrichards/xmlseclibs
Vídeo bem útil do Bill Barsch da Geranet https://www.youtube.com/watch?v=v1rByVX1_FY
Para assinar individual, colocamos a primeira parte do XML em uma string e então chamamos a função de assinar enviando o código. Após pegamos o retorno e concatenamos com o restante do XML de deveria estar antes e após a primeira string já assinada. Daí chamamos novamente a função de assinar passando a segunda string (XML completo) a ser assinada.
Não sei se ficou claro.
O único cuidado que tem que ter é não modificar nada no primeiro bloco após assinado, pois remover espaços após a assinatura por exemplo, a invalidaria.
Att.
Giovani Silva
Virthuz Anunciante Virtual
>
4 minutos atrás, Giovani Silva disse:
Boa tarde Josemir Costa, você conseguiu resolver sua questão?
Chegou a olhar os links que mencionei?
Biblioteca para assinar o XML https://github.com/robrichards/xmlseclibs
Vídeo bem útil do Bill Barsch da Geranet https://www.youtube.com/watch?v=v1rByVX1_FY
Para assinar individual, colocamos a primeira parte do XML em uma string e então chamamos a função de assinar enviando o código. Após pegamos o retorno e concatenamos com o restante do XML de deveria estar antes e após a primeira string já assinada. Daí chamamos novamente a função de assinar passando a segunda string (XML completo) a ser assinada.
Não sei se ficou claro.
O único cuidado que tem que ter é não modificar nada no primeiro bloco após assinado, pois remover espaços após a assinatura por exemplo, a invalidaria.
Att.
Giovani Silva
Virthuz Anunciante Virtual
Então! A questão é que quando assino o xml usando a biblioteca ele só assina a primeira tag, como faço p assinar a tag correta? tipo assinar a tag <rps> depois a tag <LoteRps> que são as que devem conter os ids e serem assinadas. Deu pra entender?
$rps="<Rps><InfRps Id="rps1"> ... </InfRps></Rps";
**$RpsAssinado**=assinaRps($rps,$chave_publica);
**$Lote**='<e:EnviarLoteRpsEnvio xmlns:e="http://www.betha.com.br/e-nota-contribuinte-ws"><LoteRps Id="LOTE1'"><NumeroLote>1</NumeroLote><Cnpj>xxxxxxx</Cnpj><InscricaoMunicipal>xxxx</InscricaoMunicipal><QuantidadeRps>1</QuantidadeRps><ListaRps>'.**$RpsAssinado**.'</ListaRps></LoteRps></e:EnviarLoteRpsEnvio>';
$LoteAssinado=assinaRps(**$Lote **, $chave_publica);Mais um detalhe Josemir, na função assinaRps não sei se você estava enviando um arquivo com o XML.
Caso era um arquivo, na função teria a opção $doc -> load($arquivo)
Nessa caso tem que substituir por
$doc -> loadXML($arquivo)
para funcionar com a string
$rps="<Rps><InfRps Id="rps1"> ... </InfRps></Rps";
OK, vou fazer os teste, muito obrigado!
Veja meu xml:
<Rps>
<InfDeclaracaoPrestacaoServico>
<Rps Id="1">
<IdentificacaoRps>
<Numero>254122</Numero>
<Serie>serie</Serie>
<Tipo>1</Tipo>
</IdentificacaoRps>
<DataEmissao>2018-04-10</DataEmissao>
<Status>1</Status>
</Rps>
<Competencia>2018-04-10</Competencia>
<Servico>
<Valores>
<ValorServicos>400.00</ValorServicos>
<ValorIss>8.00</ValorIss>
<Aliquota>0.0200</Aliquota>
</Valores>
<IssRetido>1</IssRetido>
<ItemListaServico>1401</ItemListaServico>
<Discriminacao>Descriminação do serviço</Discriminacao>
<CodigoMunicipio>3100401</CodigoMunicipio>
<ExigibilidadeISS>3</ExigibilidadeISS>
</Servico>
<Prestador>
<CpfCnpj>
<Cnpj>01139388000179</Cnpj>
</CpfCnpj>
<InscricaoMunicipal>1234</InscricaoMunicipal>
</Prestador>
<Tomador>
<IdentificacaoTomador>
<CpfCnpj>
<Cnpj>12345678912345</Cnpj>
</CpfCnpj>
<InscricaoMunicipal>1234</InscricaoMunicipal>
</IdentificacaoTomador>
<RazaoSocial>TOMADOR</RazaoSocial>
<Endereco>
<Endereco>teste</Endereco>
<Numero>123</Numero>
<Bairro>teste</Bairro>
<CodigoMunicipio>1234</CodigoMunicipio>
<Uf>SE</Uf>
<Cep>35438000</Cep>
</Endereco>
<Contato>
<Telefone>32554411</Telefone>
<Email>teste@dominiodoemail.com</Email>
</Contato>
</Tomador>
<RegimeEspecialTributacao>1</RegimeEspecialTributacao>
<OptanteSimplesNacional>2</OptanteSimplesNacional>
<IncentivoFiscal>2</IncentivoFiscal>
</InfDeclaracaoPrestacaoServico>
</Rps>';
quando eu coloco a opção do force_uri não aparece na assinatura, na verdade aparece um código aleatório, quando coloco o id na tag pai <rps> aí sim aparece mas na estrutura correta o id aparece na tag rps filho. Não consegui chegar a uma solução ainda.
Muito obrigado pela atenção e pasciência!Boa noite, em nossos testes não lembro se tentamos colocar um ID na tag Rps e sim na InfRps.
Conforme havia comentado na solução aqui neste post, é necessário deixar o force_uri como true mesmo que aparentemente não apareça na estrutura do seu xml ou que fique em branco. Funciona dessa maneira mesmo. Agora se desativar o force_uri da erro na assinatura.
Então mesmo que não apareça o uri na assinatura de seu xml, ele fez alguma coisa nela tornando-a válida.
Ok, vou tentar mesmo sem aparecer p ver, muito obrigado mais uma vez.
Um dos pontos que analizei, é que você está enviando os dados de forma errada. Apesar do WSDL definir os tipos como um XML, não é um XML que deve ser enviado. Você deve criar um objeto que corresponda ao tipo definido na mensageria.
Veja o seguinte XML:
//Cria o objeto nó raiz
$consultarLoteRpsEnvio = new stdClass();
//cria o objeto do nó Prestador
$prestador = new stdClass();
//Cria o objeto do nó CpfCnpj
//Adiciona o nó CpfCnpj ao nó Prestador
//Adiciona o nó Prestador ao nó Raiz