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
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
class Assinatura(object):
@ -28,9 +26,11 @@ de assinatura digital."""
def assinar_etree(self, raiz):
u"""Efetua a assinatura numa instancia da biblioteca lxml.etree.
Este metodo de assinatura será utilizado internamente pelos demais,
sendo que eles convertem para uma instancia lxml.etree para somente
depois efetivar a assinatura.
TODO: Verificar o funcionamento da PyXMLSec antes de efetivar isso."""
pass
@ -50,11 +50,6 @@ TODO: Verificar o funcionamento da PyXMLSec antes de efetivar isso."""
def verificar_objetos(self, objetos):
pass
@memoize
def extrair_tag(root):
return root.tag.split('}')[-1]
class AssinaturaA1(Assinatura):
"""Classe abstrata responsavel por efetuar a assinatura do certificado
digital no XML informado."""

84
pynfe/processamento/comunicacao.py

@ -1,27 +1,34 @@
# -*- 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
from assinatura import AssinaturaA1
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."""
_versao = VERSAO_PADRAO
_assinatura = AssinaturaA1
def transmitir(self, nota_fiscal):
pass
@ -41,8 +48,8 @@ class ComunicacaoSefaz(Comunicacao):
# Monta XML para envio da requisição
xml = self._construir_xml_soap(
metodo='CadConsultaCadastro',
tag_metodo='consultaCadastro',
metodo='nfeRecepcao2', # FIXME
tag_metodo='nfeStatusServicoNF2', # FIXME
cabecalho=self._cabecalho_soap(),
dados=dados,
)
@ -51,18 +58,69 @@ 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, 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):
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)
# 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)

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)

4
pynfe/utils/__init__.py

@ -65,3 +65,7 @@ def obter_municipio_por_codigo(codigo, uf):
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.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)
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',

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

@ -11,13 +11,67 @@ 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',
... )
Instancia do certificado
>>> from pynfe.entidades import CertificadoA1
>>> certificado = CertificadoA1(caminho_arquivo='tests/certificado.pfx')
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(
... 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