Browse Source

Trabalhando na comunicação

tags/0.1
Marinho Brandão 16 years ago
parent
commit
62ad9582c6
  1. 81
      pynfe/processamento/comunicacao.py
  2. 4
      pynfe/processamento/serializacao.py
  3. 58
      run_fake_soap_server.py
  4. 2
      tests/03-processamento-01-serializacao-xml.txt
  5. 51
      tests/03-processamento-04-comunicacao.txt
  6. 27
      tests/certificado.pem
  7. 15
      tests/key.pem

81
pynfe/processamento/comunicacao.py

@ -1,22 +1,27 @@
# -*- coding: utf-8 -*-
import datetime
from httplib import HTTPSConnection, HTTPResponse
from pynfe.utils import etree, StringIO
from pynfe.utils import etree, StringIO, so_numeros
from pynfe.utils.flags import NAMESPACE_NFE, NAMESPACE_SOAP, VERSAO_PADRAO
from pynfe.utils.flags import CODIGOS_ESTADOS, VERSAO_PADRAO
class Comunicacao(object):
u"""Classe abstrata responsavel por definir os metodos e logica das classes
de comunicação com os webservices da NF-e."""
_ambiente = 1 # 1 = Produção, 2 = Homologação
servidor = None
porta = 80
certificado = None
certificado_senha = None
def __init__(self, servidor, certificado, certificado_senha):
def __init__(self, servidor, porta, certificado, certificado_senha, homologacao=False):
self.servidor = servidor
self.porta = porta
self.certificado = certificado
self.certificado_senha = certificado_senha
self._ambiente = homologacao and 2 or 1
class ComunicacaoSefaz(Comunicacao):
u"""Classe de comunicação que segue o padrão definido para as SEFAZ dos Estados."""
@ -41,8 +46,8 @@ class ComunicacaoSefaz(Comunicacao):
# Monta XML para envio da requisição
xml = self._construir_xml_soap(
metodo='CadConsultaCadastro',
tag_metodo='consultaCadastro',
metodo='CadConsultaCadastro', # FIXME
tag_metodo='consultaCadastro', # FIXME
cabecalho=self._cabecalho_soap(),
dados=dados,
)
@ -51,18 +56,66 @@ class ComunicacaoSefaz(Comunicacao):
retorno = self._post(post, xml, self._post_header())
# Transforma o retorno em etree
try:
retorno = etree.parse(StringIO(retorno))
return retorno
except TypeError:
pass
#retorno = etree.parse(StringIO(retorno))
return bool(retorno)
def consultar_cadastro(self, instancia):
#post = '/nfeweb/services/cadconsultacadastro.asmx'
post = '/nfeweb/services/nfeconsulta.asmx'
def inutilizar_faixa_numeracao(self, faixa):
pass
def inutilizar_faixa_numeracao(self, numero_inicial, numero_final, emitente, ano=None,
serie='1', justificativa=''):
post = '/nfeweb/services/nfestatusservico.asmx'
# Valores default
ano = str(ano or datetime.date.today().year)[-2:]
uf = CODIGOS_ESTADOS[emitente.endereco_uf]
cnpj = so_numeros(emitente.cnpj)
# Identificador da TAG a ser assinada formada com Código da UF + Ano (2 posições) +
# CNPJ + modelo + série + nro inicial e nro final precedida do literal “ID”
id_unico = 'ID%(uf)s%(ano)s%(cnpj)s%(modelo)s%(serie)s%(num_ini)s%(num_fin)s'%{
'uf': uf,
'ano': ano,
'cnpj': cnpj,
'modelo': '55',
'serie': serie.zfill(3),
'num_ini': str(numero_inicial).zfill(9),
'num_fin': str(numero_final).zfill(9),
}
# Monta XML do corpo da requisição # FIXME
raiz = etree.Element('inutNFe', xmlns="http://www.portalfiscal.inf.br/nfe", versao="1.07")
inf_inut = etree.SubElement(raiz, 'infInut', Id=id_unico) # FIXME
etree.SubElement(inf_inut, 'tpAmb').text = str(self._ambiente)
etree.SubElement(inf_inut, 'xServ').text = 'INUTILIZAR'
etree.SubElement(inf_inut, 'cUF').text = uf
etree.SubElement(inf_inut, 'ano').text = ano
etree.SubElement(inf_inut, 'CNPJ').text = emitente.cnpj
etree.SubElement(inf_inut, 'mod').text = '55'
etree.SubElement(inf_inut, 'serie').text = serie
etree.SubElement(inf_inut, 'nNFIni').text = str(numero_inicial)
etree.SubElement(inf_inut, 'nNFFin').text = str(numero_final)
etree.SubElement(inf_inut, 'xJust').text = justificativa
dados = etree.tostring(raiz, encoding='utf-8', xml_declaration=True)
# Efetua assinatura
# Monta XML para envio da requisição
xml = self._construir_xml_soap(
metodo='CadConsultaCadastro', # FIXME
tag_metodo='consultaCadastro', # FIXME
cabecalho=self._cabecalho_soap(),
dados=dados,
)
# Chama método que efetua a requisição POST no servidor SOAP
retorno = self._post(post, xml, self._post_header())
# Transforma o retorno em etree # TODO
#retorno = etree.parse(StringIO(retorno))
return retorno
def _cabecalho_soap(self):
u"""Monta o XML do cabeçalho da requisição SOAP"""
@ -99,11 +152,11 @@ class ComunicacaoSefaz(Comunicacao):
caminho_chave, caminho_cert = self.certificado.separar_arquivo(senha=self.certificado_senha)
# Abre a conexão HTTPS
con = HTTPSConnection(self.servidor, key_file=caminho_chave, cert_file=caminho_cert)
con = HTTPSConnection(self.servidor, self.porta, key_file=caminho_chave, cert_file=caminho_cert)
try:
#con.set_debuglevel(100)
con.request(u'POST', post, xml, header)
resp = con.getresponse()

4
pynfe/processamento/serializacao.py

@ -17,7 +17,7 @@ class Serializacao(object):
Nao deve ser instanciada diretamente!"""
_fonte_dados = None
_ambiente = 1
_ambiente = 1 # 1 = Produção, 2 = Homologação
_nome_aplicacao = 'PyNFe'
def __new__(cls, *args, **kwargs):
@ -232,7 +232,7 @@ class SerializacaoXML(Serializacao):
etree.SubElement(ide, 'indPag').text = str(nota_fiscal.forma_pagamento)
etree.SubElement(ide, 'mod').text = str(nota_fiscal.modelo)
etree.SubElement(ide, 'serie').text = nota_fiscal.serie
etree.SubElement(ide, 'nNF').text = nota_fiscal.numero_nf
etree.SubElement(ide, 'nNF').text = str(nota_fiscal.numero_nf)
etree.SubElement(ide, 'dEmi').text = nota_fiscal.data_emissao.strftime('%Y-%m-%d')
etree.SubElement(ide, 'dSaiEnt').text = nota_fiscal.data_saida_entrada.strftime('%Y-%m-%d')
etree.SubElement(ide, 'tpNF').text = str(nota_fiscal.tipo_documento)

58
run_fake_soap_server.py

@ -1,24 +1,50 @@
from soaplib.wsgi_soap import SimpleWSGISoapApp
from soaplib.service import soapmethod
from soaplib.serializers.primitive import String, Integer, Array, Null
# -*- coding: utf-8 -*-
class ServidorNFEFalso(SimpleWSGISoapApp):
@soapmethod(String, Integer, _returns=Array(String))
def ping(self, nome, vezes):
ret = [nome for i in range(vezes)]
print ret
return ret
"""Este script deve ser executado com Python 2.6+ e OpenSSL"""
import os
CUR_DIR = os.path.dirname(os.path.abspath(__file__))
#from soaplib.wsgi_soap import SimpleWSGISoapApp
#from soaplib.service import soapmethod
#from soaplib.serializers.primitive import String, Integer, Array, Null
#import tornado.wsgi
import tornado.httpserver
import tornado.ioloop
import tornado.web
import tornado.options
#class ServidorNFEFalso(SimpleWSGISoapApp):
# @soapmethod(String, Integer, _returns=Array(String))
# def ping(self, nome, vezes):
# ret = [nome for i in range(vezes)]
# return ret
class HandlerStatusServico(tornado.web.RequestHandler):
def post(self):
self.write('<x/>')
if __name__ == '__main__':
porta = 8080
# Via Tornado
import tornado.wsgi
import tornado.httpserver
import tornado.ioloop
application = ServidorNFEFalso()
container = tornado.wsgi.WSGIContainer(application)
http_server = tornado.httpserver.HTTPServer(container)
# Codigo específico da soaplib
#application = ServidorNFEFalso()
#container = tornado.wsgi.WSGIContainer(application)
#http_server = tornado.httpserver.HTTPServer(container)
tornado.options.parse_command_line()
application = tornado.web.Application([
(r'^/nfeweb/services/nfestatusservico.asmx$', HandlerStatusServico), # Consulta de status do serviço
])
ssl_options = {
'certfile': os.path.join(CUR_DIR, 'tests', 'certificado.pem'),
'keyfile': os.path.join(CUR_DIR, 'tests', 'key.pem'),
}
http_server = tornado.httpserver.HTTPServer(application, ssl_options=ssl_options)
http_server.listen(porta)
tornado.ioloop.IOLoop.instance().start()

2
tests/03-processamento-01-serializacao-xml.txt

@ -63,7 +63,7 @@ Instancia a NF
... uf='GO',
... modelo=55,
... serie='1',
... numero_nf='1',
... numero_nf=1,
... data_emissao=datetime.date(2010,1,13),
... data_saida_entrada=datetime.date(2010,1,13),
... natureza_operacao='Venda a vista',

51
tests/03-processamento-04-comunicacao.txt

@ -11,13 +11,60 @@ Carregando certificado digital tipo A1
Instancia de comunicacao
>>> comunicacao = ComunicacaoSefaz(
... #servidor='localhost:8080',
... servidor='homologacao.nfe.fazenda.sp.gov.br',
... servidor='localhost',
... porta=8080,
... #servidor='homologacao.nfe.fazenda.sp.gov.br',
... certificado=certificado,
... certificado_senha='associacao',
... )
Instancia do emitente (auxiliar para este teste)
>>> from pynfe.entidades import Emitente
>>> emitente = Emitente(
... cnpj='12.345.678/0001-90',
... razao_social='Tarsila Calcados Ltda.',
... nome_fantasia='Tarsila Calcados Ltda.',
... inscricao_estadual='123456789012',
... endereco_logradouro='Rua 10',
... endereco_numero='15',
... endereco_complemento='qd 17, lt 10',
... endereco_bairro='Setor Oeste',
... endereco_municipio='5208806', # Goiania
... endereco_uf='GO',
... endereco_cep='75370-000',
... endereco_telefone='6242421212',
... )
Verifica o status do servico
>>> comunicacao.status_servico()
True
Transmissao de NF-e
>>> #comunicacao.transmitir(nota_fiscal)
Cancelamento de NF-e
>>> #comunicacao.cancelar(nota_fiscal)
Consulta situacao de NF-e
>>> #comunicacao.situacao_nfe(nota_fiscal)
Consulta de cadastro (???)
>>> #comunicacao.consultar_cadastro()
Inulitilizacao de faixa de numeracao
>>> comunicacao.inutilizar_faixa_numeracao(
... numero_inicial=10,
... numero_final=20,
... emitente=emitente,
... ano=2009,
... serie='1',
... justificativa='AJUSTE DA SEQUENCIA DE NUMERACAO',
... )

27
tests/certificado.pem

@ -0,0 +1,27 @@
-----BEGIN CERTIFICATE-----
MIIEqzCCA5OgAwIBAgIDMTg4MA0GCSqGSIb3DQEBBQUAMIGSMQswCQYDVQQGEwJC
UjELMAkGA1UECBMCUlMxFTATBgNVBAcTDFBvcnRvIEFsZWdyZTEdMBsGA1UEChMU
VGVzdGUgUHJvamV0byBORmUgUlMxHTAbBgNVBAsTFFRlc3RlIFByb2pldG8gTkZl
IFJTMSEwHwYDVQQDExhORmUgLSBBQyBJbnRlcm1lZGlhcmlhIDEwHhcNMDkwNTIy
MTcwNzAzWhcNMTAxMDAyMTcwNzAzWjCBnjELMAkGA1UECBMCUlMxHTAbBgNVBAsT
FFRlc3RlIFByb2pldG8gTkZlIFJTMR0wGwYDVQQKExRUZXN0ZSBQcm9qZXRvIE5G
ZSBSUzEVMBMGA1UEBxMMUE9SVE8gQUxFR1JFMQswCQYDVQQGEwJCUjEtMCsGA1UE
AxMkTkZlIC0gQXNzb2NpYWNhbyBORi1lOjk5OTk5MDkwOTEwMjcwMIGfMA0GCSqG
SIb3DQEBAQUAA4GNADCBiQKBgQCx1O/e1Q+xh+wCoxa4pr/5aEFt2dEX9iBJyYu/
2a78emtorZKbWeyK435SRTbHxHSjqe1sWtIhXBaFa2dHiukT1WJyoAcXwB1GtxjT
2VVESQGtRiujMa+opus6dufJJl7RslAjqN/ZPxcBXaezt0nHvnUB/uB1K8WT9G7E
S0V17wIDAQABo4IBfjCCAXowIgYDVR0jAQEABBgwFoAUPT5TqhNWAm+ZpcVsvB7m
alDBjEQwDwYDVR0TAQH/BAUwAwEBADAPBgNVHQ8BAf8EBQMDAOAAMAwGA1UdIAEB
AAQCMAAwgawGA1UdEQEBAASBoTCBnqA4BgVgTAEDBKAvBC0yMjA4MTk3Nzk5OTk5
OTk5OTk5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDCgEgYFYEwBAwKgCQQHREZU
LU5GZaAZBgVgTAEDA6AQBA45OTk5OTA5MDkxMDI3MKAXBgVgTAEDB6AOBAwwMDAw
MDAwMDAwMDCBGmRmdC1uZmVAcHJvY2VyZ3MucnMuZ292LmJyMCAGA1UdJQEB/wQW
MBQGCCsGAQUFBwMCBggrBgEFBQcDBDBTBgNVHR8BAQAESTBHMEWgQ6BBhj9odHRw
Oi8vbmZlY2VydGlmaWNhZG8uc2VmYXoucnMuZ292LmJyL0xDUi9BQ0ludGVybWVk
aWFyaWEzOC5jcmwwDQYJKoZIhvcNAQEFBQADggEBAJFytXuiS02eJO0iMQr/Hi+O
x7/vYiPewiDL7s5EwO8A9jKx9G2Baz0KEjcdaeZk9a2NzDEgX9zboPxhw0RkWahV
CP2xvRFWswDIa2WRUT/LHTEuTeKCJ0iF/um/kYM8PmWxPsDWzvsCCRp146lc0lz9
LGm5ruPVYPZ/7DAoimUk3bdCMW/rzkVYg7iitxHrhklxH7YWQHUwbcqPt7Jv0RJx
clc1MhQlV2eM2MO1iIlk8Eti86dRrJVoicR1bwc6/YDqDp4PFONTi1ddewRu6elG
S74AzCcNYRSVTINYiZLpBZO0uivrnTEnsFguVnNtWb9MAHGt3tkR0gAVs6S0fm8=
-----END CERTIFICATE-----

15
tests/key.pem

@ -0,0 +1,15 @@
-----BEGIN RSA PRIVATE KEY-----
MIICXQIBAAKBgQCx1O/e1Q+xh+wCoxa4pr/5aEFt2dEX9iBJyYu/2a78emtorZKb
WeyK435SRTbHxHSjqe1sWtIhXBaFa2dHiukT1WJyoAcXwB1GtxjT2VVESQGtRiuj
Ma+opus6dufJJl7RslAjqN/ZPxcBXaezt0nHvnUB/uB1K8WT9G7ES0V17wIDAQAB
AoGAAYOcnzsVLR/JJKSa1uukis0WcYb/PsL7t2Ud6X5C/SdVrsh7jRMQ7oXNV4n7
U2wayiHyQY/sZhhQoMYvVO6b2Wm/anq+F5L2QNT0FUxckRugDtH6tY6wqbs+AoTK
+eTBcSowB6HP6J9yZss02jSzh1xqX1t7q7dJEJjTGmoRczECQQDRrXux9N5Bdnzu
vTQFm7W1cYkxLrxPZ/gHFaHLAjd/h29cG1UTpkmUeKFDDPETE4q6Zmg3sWOAeMZB
tZQA7nf1AkEA2R5hS2Z+vvXLEGzxSTwYH2pRBoXUzuj801YqMhe4T/pJu4H3Bzab
4/SEEZdcFEa51HdOmqtOtQj1NDy/z3Lb0wJBAI1YaGEvU8BHcrKxgucg715QGg64
laLl0HJeJ8IlTWo/z1cE6dYkK8fVhcggakbUzpkXPbwFbbEGOYfEMvBp0R0CQQCr
G98vriIbWthjJIhv3/Ve5Mngax6QxltiLpjoi3sNRMJRDRbiz23CFBT1TCUcMbUI
NdJz4KgR0nJ0bZ/43JtTAkBLJwkUNQDLdxx2EMif8KMRfsTAqUQ0d4wWnUP0hPHH
4qjUm/un000TF7eB85tszXxGxWATaGx4OPT91dXwzDsh
-----END RSA PRIVATE KEY-----
Loading…
Cancel
Save