Browse Source

resolvendo um conflito aqui

tags/0.1
Italo Maia 16 years ago
parent
commit
40e3cce4e1
  1. 11
      pynfe/processamento/assinatura.py
  2. 84
      pynfe/processamento/comunicacao.py
  3. 4
      pynfe/processamento/serializacao.py
  4. 4
      pynfe/utils/__init__.py
  5. 103
      run_fake_soap_server.py
  6. 2
      tests/03-processamento-01-serializacao-xml.txt
  7. 58
      tests/03-processamento-04-comunicacao.txt
  8. 27
      tests/certificado.pem
  9. 15
      tests/key.pem

11
pynfe/processamento/assinatura.py

@ -2,9 +2,7 @@
import xmlsec, libxml2 # FIXME: verificar ambiguidade de dependencias: lxml e libxml2 import xmlsec, libxml2 # FIXME: verificar ambiguidade de dependencias: lxml e libxml2
from geraldo.utils import memoize
from pynfe.utils import etree, StringIO
from pynfe.utils import etree, StringIO, extrair_tag
from pynfe.utils.flags import NAMESPACE_NFE, NAMESPACE_SIG from pynfe.utils.flags import NAMESPACE_NFE, NAMESPACE_SIG
class Assinatura(object): class Assinatura(object):
@ -28,9 +26,11 @@ de assinatura digital."""
def assinar_etree(self, raiz): def assinar_etree(self, raiz):
u"""Efetua a assinatura numa instancia da biblioteca lxml.etree. u"""Efetua a assinatura numa instancia da biblioteca lxml.etree.
Este metodo de assinatura será utilizado internamente pelos demais, Este metodo de assinatura será utilizado internamente pelos demais,
sendo que eles convertem para uma instancia lxml.etree para somente sendo que eles convertem para uma instancia lxml.etree para somente
depois efetivar a assinatura. depois efetivar a assinatura.
TODO: Verificar o funcionamento da PyXMLSec antes de efetivar isso.""" TODO: Verificar o funcionamento da PyXMLSec antes de efetivar isso."""
pass pass
@ -50,11 +50,6 @@ TODO: Verificar o funcionamento da PyXMLSec antes de efetivar isso."""
def verificar_objetos(self, objetos): def verificar_objetos(self, objetos):
pass pass
@memoize
def extrair_tag(root):
return root.tag.split('}')[-1]
class AssinaturaA1(Assinatura): class AssinaturaA1(Assinatura):
"""Classe abstrata responsavel por efetuar a assinatura do certificado """Classe abstrata responsavel por efetuar a assinatura do certificado
digital no XML informado.""" digital no XML informado."""

84
pynfe/processamento/comunicacao.py

@ -1,27 +1,34 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import datetime
from httplib import HTTPSConnection, HTTPResponse 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 NAMESPACE_NFE, NAMESPACE_SOAP, VERSAO_PADRAO
from pynfe.utils.flags import CODIGOS_ESTADOS, VERSAO_PADRAO
from assinatura import AssinaturaA1
class Comunicacao(object): class Comunicacao(object):
u"""Classe abstrata responsavel por definir os metodos e logica das classes u"""Classe abstrata responsavel por definir os metodos e logica das classes
de comunicação com os webservices da NF-e.""" de comunicação com os webservices da NF-e."""
_ambiente = 1 # 1 = Produção, 2 = Homologação
servidor = None servidor = None
porta = 80
certificado = None certificado = None
certificado_senha = 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.servidor = servidor
self.porta = porta
self.certificado = certificado self.certificado = certificado
self.certificado_senha = certificado_senha self.certificado_senha = certificado_senha
self._ambiente = homologacao and 2 or 1
class ComunicacaoSefaz(Comunicacao): class ComunicacaoSefaz(Comunicacao):
u"""Classe de comunicação que segue o padrão definido para as SEFAZ dos Estados.""" u"""Classe de comunicação que segue o padrão definido para as SEFAZ dos Estados."""
_versao = VERSAO_PADRAO _versao = VERSAO_PADRAO
_assinatura = AssinaturaA1
def transmitir(self, nota_fiscal): def transmitir(self, nota_fiscal):
pass pass
@ -41,8 +48,8 @@ class ComunicacaoSefaz(Comunicacao):
# Monta XML para envio da requisição # Monta XML para envio da requisição
xml = self._construir_xml_soap( xml = self._construir_xml_soap(
metodo='CadConsultaCadastro',
tag_metodo='consultaCadastro',
metodo='nfeRecepcao2', # FIXME
tag_metodo='nfeStatusServicoNF2', # FIXME
cabecalho=self._cabecalho_soap(), cabecalho=self._cabecalho_soap(),
dados=dados, dados=dados,
) )
@ -51,18 +58,69 @@ class ComunicacaoSefaz(Comunicacao):
retorno = self._post(post, xml, self._post_header()) retorno = self._post(post, xml, self._post_header())
# Transforma o retorno em etree # 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): def consultar_cadastro(self, instancia):
#post = '/nfeweb/services/cadconsultacadastro.asmx' #post = '/nfeweb/services/cadconsultacadastro.asmx'
post = '/nfeweb/services/nfeconsulta.asmx' post = '/nfeweb/services/nfeconsulta.asmx'
def inutilizar_faixa_numeracao(self, faixa):
pass
def inutilizar_faixa_numeracao(self, numero_inicial, numero_final, emitente, certificado,
senha, 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)
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
assinatura = self._assinatura(certificado, senha)
dados = assinatura.assinar_etree(etree.ElementTree(raiz), retorna_xml=True)
# Monta XML para envio da requisição
xml = self._construir_xml_soap(
metodo='nfeRecepcao2', # XXX
tag_metodo='nfeInutilizacaoNF', # XXX
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): def _cabecalho_soap(self):
u"""Monta o XML do cabeçalho da requisição SOAP""" u"""Monta o XML do cabeçalho da requisição SOAP"""
@ -99,7 +157,7 @@ class ComunicacaoSefaz(Comunicacao):
caminho_chave, caminho_cert = self.certificado.separar_arquivo(senha=self.certificado_senha) caminho_chave, caminho_cert = self.certificado.separar_arquivo(senha=self.certificado_senha)
# Abre a conexão HTTPS # 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: try:
#con.set_debuglevel(100) #con.set_debuglevel(100)

4
pynfe/processamento/serializacao.py

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

4
pynfe/utils/__init__.py

@ -65,3 +65,7 @@ def obter_municipio_por_codigo(codigo, uf):
return municipios[codigo] return municipios[codigo]
@memoize
def extrair_tag(root):
return root.tag.split('}')[-1]

103
run_fake_soap_server.py

@ -1,24 +1,95 @@
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"""
if __name__ == '__main__':
porta = 8080
import os, datetime
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
# Via Tornado
import tornado.wsgi
#import tornado.wsgi
import tornado.httpserver import tornado.httpserver
import tornado.ioloop import tornado.ioloop
application = ServidorNFEFalso()
container = tornado.wsgi.WSGIContainer(application)
http_server = tornado.httpserver.HTTPServer(container)
import tornado.web
import tornado.options
from pynfe.utils import etree, StringIO, extrair_tag
from pynfe.utils.flags import CODIGOS_ESTADOS
#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):
sigla_servidor = 'GO'
def post(self):
# Obtem o body da request
xml = self.request.body
# Transforma em etree
raiz = etree.parse(StringIO(xml))
# Extrai a tag do método da request
tag = extrair_tag(raiz.getroot().getchildren()[0].getchildren()[0])
# Chama o método respectivo para a tag
print 'Metodo:', tag
getattr(self, tag)(raiz)
def nfeStatusServicoNF2(self, raiz):
data_hora = datetime.datetime.now().strftime('%Y-%m-%dT%H:%M:%S')
ret = etree.Element('retConsStatServ')
etree.SubElement(ret, 'versao').text = '1.00' # FIXME
etree.SubElement(ret, 'tbAmb').text = '2' # Homologação
etree.SubElement(ret, 'verAplic').text = self.sigla_servidor
etree.SubElement(ret, 'cStat').text = '1' # FIXME
etree.SubElement(ret, 'xMotivo').text = 'Servico em funcionamento normal' # FIXME
etree.SubElement(ret, 'cUF').text = CODIGOS_ESTADOS[self.sigla_servidor]
etree.SubElement(ret, 'dhRecbto').text = data_hora
etree.SubElement(ret, 'tMed').text = '10'
etree.SubElement(ret, 'dhRetorno').text = data_hora
etree.SubElement(ret, 'xObs').text = 'Nenhuma informacao adicional'
xml = etree.tostring(ret, encoding='utf-8', xml_declaration=True)
self.write(xml)
def nfeInutilizacaoNF(self, raiz):
data_hora = datetime.datetime.now().strftime('%Y-%m-%dT%H:%M:%S')
ret = etree.Element('retInutNFe')
etree.SubElement(ret, 'versao').text = '1.00' # FIXME
xml_dados = raiz.getroot().getchildren()[0].getchildren()[0].getchildren()[1].text
xml = etree.tostring(ret, encoding='utf-8', xml_declaration=True)
self.write(xml)
if __name__ == '__main__':
porta = 8080
# 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) http_server.listen(porta)
tornado.ioloop.IOLoop.instance().start() tornado.ioloop.IOLoop.instance().start()

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

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

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

@ -11,13 +11,67 @@ Carregando certificado digital tipo A1
Instancia de comunicacao Instancia de comunicacao
>>> comunicacao = ComunicacaoSefaz( >>> 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=certificado,
... certificado_senha='associacao', ... 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',
... )
Instancia do certificado
>>> from pynfe.entidades import CertificadoA1
>>> certificado = CertificadoA1(caminho_arquivo='tests/certificado.pfx')
Verifica o status do servico Verifica o status do servico
>>> comunicacao.status_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(
... certificado=certificado,
... senha='associacao',
... 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