Acessando o web service de Nota Fiscal de Servicos Eletronica (NFS-e Nota Carioca) em Java

Depois de bater bastante cabeça para conseguir dar um primeiro passo na integração com o sistema de Nota Fiscal de Serviços Eletrônica (NFS-e) do município do Rio de Janeiro (Nota Carioca), vou colocar aqui o passo a passo que eu segui para contribuir com outras pessoas que possam estar passando pelo mesmo martírio.

Não encontramos muita informação detalhada por aí. A conclusão que cheguei é que quem conseguiu fazer a integração não quer divulgar como fez, pois provavelmente a implementação faz parte de algum produto comercial sendo vendido, e portanto fornecer estas informações gratuitamente vai contra a empresa.

Como este não é o meu caso, pois eu não vendo este serviço, estou apenas integrando para poder emitir NFS-e automaticamente com o Peladeiro.com, então resolvi contribuir. Aí vai.....


1- Gerar as classes Java que representam os XML consumidos pelo Web Service da prefeitura:

Meu primeiro passo foi gerar as classes Java que representam os arquivos XML definidos pela prefeitura. Para isso, baixei o XML Schema (XSD) da versao que a prefeitura usa. Baixei do site da ABRASF em http://www.abrasf.org.br/

Neste site, procurar na página de "Nota Fiscal de Servicos Eletronica" o XSD correspondente. No meu caso foi o tipos_nfse_v01.xsd

Gerar as classes Java a partir do XSD: (http://docs.oracle.com/javase/6/docs/technotes/tools/share/xjc.html)

Para isso, executei o comando:

 xjc -p com.meusistema.nfse.binding -d C:\Users\Eu\projetos\meunfse\src\java\com\meusistema\nfse\binding C:\Users\Eu\projetos\meunfse\web-app\WEB-INF\tipos_nfse_v01.xsd  

Isso gerou um monte de classes Java, cada uma representando os tipos definidos no XSD. Essas classes serão usadas para preencher com os dados a serem passados para o web service (e retornados tbem). Após usar as classes para preencher com os dados, por exemplo setCpf("34234234");, usei o JAXB para gerar a string XML a ser passada para o web service.... mas vamos chegar lá...

2- Gerar as classes Java de Cliente do Web Service através do WSDL da prefeitura.

O passo seguinte foi gerar as classes Java que representam o Client do web service. A prefeitura fornece o WSDL. No meu caso ficava aqui: https://notacarioca.rio.gov.br/WSNacional/nfse.asmx?wsdl

Só que para conseguir acessar este WSDL, foi preciso eu ter o certificado digital. Sem o certificado, nada feito. Então, comprei um certificado da Certisign, do tipo NFS-e A1. É um processo chato, pois você compra no site, mas depois tem que marcar um dia para ir até lá para entregar a documentação da empresa, dos sócios, do representante, etc.... No final, você sai com um número, acesso um site, e com este número faz o download do certificado meucert.pfx (é um certificado com extensão PFX).

Instalei o certificado na minha máquina (windows 7), e através do browser eu consegui acessar o link acima do WSDL. Salvei o WSDL na minha máquina, e gerei as classes Java a partir dele. Para isso, usei o projeto CXF (versão 2.7.8), e com isso usei wsdl2java, No meu DOS executei:

 wsdl2java -p com.meusistema.nfse -frontend jaxws21 -client "C:\Users\Eu\projetos\meunfse\web-app\WEB-INF\notacarioca-nfse.wsdl"  

3- Como conseguir acessar o Web Service através do seu sistema, sua aplicação Java cliente, usando o certificado digital PFX.

Bom, neste ponto eu já conseguia acessar o web service (na verdade o WSDL) pelo browser, mas a minha aplicação sempre recebia um erro 403 - Forbidden: Access is denied.

a) Depois de muito bater cabeça, vi que o meu certificado PFX não possuía toda a cadeia de certificados, ele possuía apenas o certificado da minha empresa. Mas era preciso ter toda a cadeia:
A cadeia na verdade é minha empresa :: autoridade certificadora :: autoridade raiz -> etc

Então, segui um passo a passo que me salvou. Obrigado ao alexegidio em http://www.guj.com.br/java/148620-nfe-erro-4037---forbidden-ao-acessar-webservices-asmx-resolvido

O passo a passo é:


       1- Importe o certificado .PFX para o IE >>> FERRAMENTAS/OPÇÕES DA INTERNET/ ABA CONTEÚDO / BOTÃO CERTIFICADOS.   
      2- Selecione a aba "Pessoal".   
      3- Clique em importar.   
      4- O Sistema vai abrir um assistente. Selecione o Certificado e clique em avançar.   
      5- Na próxima tela MARQUE O CHECBOX "Marcar esta chave como exportável. Isso possibilitará o backup e transporte das chaves posteriormente". Como o texto diz se você não marcar esta opção o certificado não poderá ser exportado   
      6- Após ser importado seu certificado vai para a aba Pessoal. Clique sobre ele e clique em "exportar"   
      7- Clique em avançar   
      8- Na próxima tela marque a opção: "Sim, exportar a chave privada". Clique em avançar.   
      9- Na janela seguinte marque o checkbox "Incluir todos os certificados no caminho de certificação, se possível". <<ESTA OPÇÃO É CRUCIAL NÃO ESQUEÇA DELA !!!   
      10- Informe a senha e a seguir o local onde o arquivo será salvo.   
      PRONTO Feito isso está pronto você já pode utilizar o certificado no seu projeto.  


b) Teve um outro pulo do gato que foi juntar este meu certificado, agora completo com toda a cadeia de certificados, com os demais certificados aceitos para acesso HTTPS. O java da sua máquina vem com um arquivo cacerts que dentro possui as autoridades certificadoras confiáveis. É assim que sua aplicação java pode acessar um web service em HTTPS. Mas agora, além de aceitar os certificados do HTTPS, nossa aplicação tem que passar um certificado privado, como forma de autenticação. Então, eu fiz o import do meu certificado PFX pra dentro de um keystore existente do java instalado na minha máquina. Para isso, copiei o cacerts original da Sun em C:\Program Files\Java\jdk1.6.0_30\jre\lib\security\cacerts para outro diretório para não quebrar o arquivo original. E então executei o seguinte comando neste arquivo cópia:

 keytool -importkeystore -srckeystore meu-certificado.pfx -destkeystore cacerts -srcstoretype PKCS12 -deststoretype JKS  

A senha do cacerts original da Sun é "changeit".

Com isso, agora eu tenho um keystore que é o arquivo cacerts, que possui também o meu próprio certificado, além dos certificados aceitos para HTTPS.

Pronto. Agora é só executar....


package com.peladeiro.nfse;

import java.io.File;
import java.io.StringWriter;

import java.net.MalformedURLException;
import java.net.URL;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.namespace.QName;

import com.peladeiro.factory.DadosNfseFactory;
import com.peladeiro.nfse.binding.EnviarLoteRpsEnvio;


public class ClienteTeste {
  
  private static final QName SERVICE_NAME = new QName("http://notacarioca.rio.gov.br/", "Nfse");

  public static void main(String[] args) throws JAXBException {
    URL wsdlURL = Nfse.WSDL_LOCATION;
        if (args.length > 0 && args[0] != null && !"".equals(args[0])) { 
            File wsdlFile = new File(args[0]);
            try {
                if (wsdlFile.exists()) {
                    wsdlURL = wsdlFile.toURI().toURL();
                } else {
                    wsdlURL = new URL(args[0]);
                }
            } catch (MalformedURLException e) {
                e.printStackTrace();
            }
        }
        
    ClienteTeste c = new ClienteTeste();
    c.autentica();
    c.chamaWebService(wsdlURL);

  }
  
  private void autentica() {

    String caminhoDoCertificadoDoCliente = "C:\\Users\\Eu\\meu-certificado-final.pfx";
    String senhaDoCertificadoDoCliente = "minhaSenhaPrivada";
    String caminhoDoKeyStore = "C:\\Users\\Eu\\cacerts";
    String senhaDoKeyStore = "changeit";

    System.setProperty("java.protocol.handler.pkgs", "com.sun.net.ssl.internal.www.protocol");
    
    System.setProperty("javax.net.ssl.keyStoreType", "PKCS12");
    System.setProperty("javax.net.ssl.keyStore", caminhoDoCertificadoDoCliente);
    System.setProperty("javax.net.ssl.keyStorePassword", senhaDoCertificadoDoCliente);

    System.setProperty("javax.net.ssl.trustStoreType", "JKS");
    System.setProperty("javax.net.ssl.trustStore", caminhoDoKeyStore);
    System.setProperty("javax.net.ssl.trustStorePassword", senhaDoKeyStore);
    
  }

  public void chamaWebService(URL wsdlURL ) throws JAXBException{
    
      
        Nfse ss = new Nfse(wsdlURL, SERVICE_NAME);
        NfseSoap port = ss.getNfseSoap();  
        
        // Esta Factory instancia os objetos com os dados a serem passados para o web service
        /*
        Exemplo:
        TcIdentificacaoRps idrps = new TcIdentificacaoRps();
    idrps.setNumero(new BigInteger("4321"));
    idrps.setSerie("1");
    idrps.setTipo(TIPO_RPS);
         * */
        EnviarLoteRpsEnvio envio = DadosNfseFactory.criaEnviarLoteRpsEnvio();
        
        JAXBContext jc = JAXBContext.newInstance(EnviarLoteRpsEnvio.class);
    Marshaller m = jc.createMarshaller();
    m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
    m.setProperty(Marshaller.JAXB_ENCODING, "UTF-8");
        
        StringWriter sw = new StringWriter();
    m.marshal(envio, sw);
        
        RecepcionarLoteRpsRequest recepcionarLoteRpsRequest = new RecepcionarLoteRpsRequest();
    recepcionarLoteRpsRequest.setInputXML(sw.toString());
    RecepcionarLoteRpsResponse recepcionarLoteRpsResponse = port.recepcionarLoteRps(recepcionarLoteRpsRequest);
    System.out.println("recepcionarLoteRps.result=" + recepcionarLoteRpsResponse.outputXML);
  }

}


E é isso!
Abcs



Artigo interessante do pessoal do Financial Times mostrando detalhes da Web App para dispositivos móveis:
http://coding.smashingmagazine.com/2013/05/23/building-the-new-financial-times-web-app/

Algumas bibliotecas que eu não conhecia, ou que eles criaram:
https://github.com/ftlabs/ftellipsis
https://github.com/ftlabs/fruitmachine
https://github.com/ftlabs/ftscroller
https://github.com/davidaurelio/TouchScroll

Além disso, usaram LocalStorage para guardar JavaScript e CSS para a aplicação poder funcionar Offline, e guardarm as imagens em IndexedDB ou WebSQL.

Cada um atacando os desafios mobile de formas diferentes....