Funções SAT¶
O acesso às funções SAT se dá através de uma biblioteca que é fornecida pelo fabricante do equipamento SAT. Este projeto abstrai o acesso à essa biblioteca tornando possível acessar um equipamento SAT conectado no computador local ou compartilhar um equipamento SAT entre dois ou mais computadores, acessando-o remotamente via API RESTful.
Se você estiver acessando um equipamento SAT conectado ao computador local,
então deverá usar um ClienteSATLocal
, cuja
configuração já foi discutida em Configuração Básica.
Se estiver compartilhando um equipamento SAT, então deverá usar
um ClienteSATHub
, cuja configuração também já
foi demonstrada.
Nota
Instalar um servidor SATHub está fora do escopo desta documentação. Consulte a documentação do projeto SATHub para saber como instalar e configurar um servidor SATHub em desenvolvimento ou em produção.
Uma vez configurado o cliente SAT, basta invocar os métodos correspodentes às funções SAT, que serão demonstradas mais adiante nesta documentação.
Nota
Sobre os nomes dos métodos Os nomes das funções SAT neste projeto foram modificados dos nomes originais para ficarem compatíveis com o estilo de código Python para nomes de métodos, funções, etc. Mas a modificação é simples e segue uma regra fácil de converter de cabeça. Por exemplo:
ComunicarCertificadoICPBRASIL -> comunicar_certificado_icpbrasil
TesteFimAFim -> teste_fim_a_fim
As palavras são separadas por um caracter de sublinha e o nome é todo convertido para letras minúsculas.
Lidando com as Respostas¶
As respostas contém os atributos que são descritos na ER SAT com nomes que sejam
o mais próximo possível da descrição oficial. Por exemplo, a função
ConsultarSAT
está descrita na ER SAT no item 6.1.5 e os detalhes da resposta
à esta função estão descritos no item 6.1.5.2 e diz o seguinte:
Retorno"numeroSessao|EEEEE|mensagem|cod|mensagemSEFAZ"
Dessa forma, a resposta à função ConsultarSAT
deverá conter atributos com os
mesmos nomes descritos na ER SAT:
resposta = cliente.consultar_sat()
print(resposta.numeroSessao) # resulta em 'int'
print(resposta.EEEEE) # resulta em 'unicode'
print(resposta.mensagem)
print(resposta.cod)
print(resposta.mensagemSEFAZ)
Caso ocorra um erro ao invocar o método consultar_sat()
será lançada uma
exceção ExcecaoRespostaSAT
contendo os detalhes do problema.
Lidando com Exceções¶
Quando uma função é invocada, seja através de um
ClienteSATLocal
ou
ClienteSATHub
, existem duas exceções principais
que podem ocorrer: ErroRespostaSATInvalida
ou
ExcecaoRespostaSAT
.
Quando a exceção ErroRespostaSATInvalida
é levantada, significa que a
resposta retornada pelo equipamento SAT não está em conformidade com a ER SAT,
geralmente por que a biblioteca resultou em uma sequência que não possui os
elementos que deveriam estar presentes, seja uma resposta de sucesso na execução
da função ou não.
Por outro lado será comum lidar com ExcecaoRespostaSAT
. Esta exceção indica
que a comunicação entre a AC e o equipamento correu bem mas execução da função
não obteve êxito. É o caso quando invocar a função ConsultarSAT
e o
equipamento estiver ocupado processando uma outra coisa; a exceção poderá
indicar o erro, já que ela contém uma resposta:
>>> # suponha que o equipamento SAT está ocupado
>>> import sys
>>> resposta = cliente.consultar_sat()
Traceback (most recent call last):
...
ExcecaoRespostaSAT: ConsultarSAT, numeroSessao=567192, EEEEE='08098', mensagem="SAT em processamento. Tente novamente.", cod="", mensagemSEFAZ=""
>>> resposta = sys.last_value
>>> resposta.mensagem
'SAT em processamento. Tente novamente.'
>>> resposta.EEEEE
'08098'
>>> resposta.numeroSessao
567192
O truque acima foi obter o objeto da exceção levantada de sys.last_value
,
que é similar ao que deveria fazer no bloco de tratamento da exceção
ExcecaoRespostaSAT
, por exemplo:
try:
resposta = cliente.consultar_sat()
# faz algo com a resposta...
except ErroRespostaSATInvalida as ex_resp_invalida:
# exibe o erro para o operador...
break
except ExcecaoRespostaSAT as ex_resposta:
resposta = ex_resposta.resposta
if resposta.EEEEE == '08098':
# o equipamento SAT está ocupado
# pergunta ao operador de caixa se quer tentar novamente...
pass
Obviamente, muita coisa pode dar errado entre o aplicativo comercial e a SEFAZ,
então utilize a regra básica de tratamento de exceções recomendada, mantendo uma
cláusula except
de fallback, por exemplo:
try:
resposta = cliente.enviar_dados_venda(cfe)
# faz algo com a resposta aqui
except ErroRespostaSATInvalida as ex_sat_invalida:
# o equipamento retornou uma resposta que não faz sentido;
# loga, e lança novamente ou lida de alguma maneira
pass
except ExcecaoRespostaSAT as ex_resposta:
# o equipamento retornou mas a função não foi bem sucedida;
# analise 'EEEEE' para decidir o que pode ser feito
pass
except Exception as ex:
# uma outra coisa aconteceu
pass
Aviso
Evite silenciar (ignorar) exceções. Se não sabe o porquê, veja o tópico sobre Tratamento de Exceções no tutorial de Python.
Funções Básicas e de Consulta¶
Estas são provavelmente as funções mais básicas da biblioteca SAT. São aquelas funções que normalmente são as primeiras a serem invocadas quando se está iniciando o procedimento de integração do SAT com o aplicativo comercial. Os exemplos dizem respeito a qualquer cliente SAT, local ou via SATHub.
A maioria das funções SAT resulta em uma resposta padrão no estilo:
numeroSessao|EEEEE|mensagem|cod|mensagemSEFAZ
Portanto, os atributos numeroSessao
, EEEEE
, mensagem
, cod
e
mensagemSEFAZ
estarão disponíveis na maioria das respostas, conforme visto
em Lidando com as Respostas:
ConsultarSAT¶
A função ConsultarSAT
(ER item 6.1.5, método
consultar_sat()
) é usada para testar
a comunicação com o equipamento SAT. Seu uso é simples e direto e, se nenhuma
exceção for lançada, é seguro acessar os atributos da resposta conforme
esperado.
>>> resp = cliente.consultar_sat()
>>> resp.mensagem
'SAT em Operação'
ConsultarStatusOperacional¶
A função ConsultarStatusOperacional
(ER item 6.1.7, método
consultar_status_operacional()
)
retorna atributos que mostram diversas informações a respeito do equipamento
SAT. A resposta para esta função é direta e simples, mas se você verificar a
documentação da ER SAT pode ficar confuso quanto aos atributos da resposta. A ER
SAT diz que o retorno da função é:
numeroSessao|EEEEE|mensagem|cod|mensagemSEFAZ|ConteudoRetorno
Entretando, a resposta não possui um atributo ConteudoRetorno
, por que
ele se expande em outros atributos que são documentados na ER SAT em uma tabela
separada. É como se o retorno fosse:
numeroSessao|EEEEE|mensagem|cod|mensagemSEFAZ|NSERIE|TIPO_LAN|LAN_IP|...
Por exemplo:
>>> resp = cliente.consultar_status_operacional()
>>> resp.mensagem
'Resposta com Sucesso'
>>> resp.NSERIE
320008889
>>> resp.STATUS_LAN
'CONECTADO'
>>> resp.DH_ATUAL
datetime.datetime(2015, 6, 25, 15, 26, 37)
ConsultarNumeroSessao¶
A função ConsultarNumeroSessao
(ER item 6.1.8, método
consultar_numero_sessao()
) permite
consultar a resposta para sessão executada anteriormente. Essa função é especial
no sentido de que sua resposta será a resposta para a função executada na sessão
que está sendo consultada.
Por exemplo, suponha que a última sessão executada seja um cancelamento, com
número de sessão 555810
. Se este número de sessão for consultado, a resposta
será a resposta de um cancelamento, resultando em uma instância de
RespostaCancelarUltimaVenda
.
>>> resp = cliente.consultar_numero_sessao(555810)
>>> resp
<satcfe.resposta.cancelarultimavenda.RespostaCancelarUltimaVenda at 0x7ffb171e02d0>
Nota
A documentação não deixa claro, mas os testes executados contra três equipamentos SAT de fabricantes diferentes se comportaram da seguinte maneira:
- Apenas a sessão executada imediatamente antes é que será considerada, ou seja, não adianta especificar uma sessão que tenha sido processada há duas ou mais sessões anteriores;
- Se a última sessão executada for de uma função de consulta de número de sessão (algo como uma meta consulta), a função também irá falhar.
ConsultarUltimaSessaoFiscal¶
A função ConsultarUltimaSessaoFiscal
(ER item 6.1.16, método
consultar_ultima_sessao_fiscal()
),
como o nome sugere, resulta na resposta da última sessão fiscal executada pelo
equipamento SAT. É considerada uma “sessão fiscal” um comando de venda ou de
cancelamento de venda, respectivamente
enviar_dados_venda()
ou
cancelar_ultima_venda()
, e suas
respostas,
RespostaEnviarDadosVenda
ou
RespostaCancelarUltimaVenda
.
Por exemplo, suponha que a última sessão fiscal executada pelo equipamento SAT tenha sido um comando de venda:
>>> resp = cliente.consultar_ultima_sessao_fiscal()
>>> resp
<satcfe.resposta.enviardadosvenda.RespostaEnviarDadosVenda at 0x7f1817971950>
Conforme descrito na especificação de requisitos do SAT, se o equipamento ainda
não tiver executado nenhum comando fiscal (venda ou cancelamento), a resposta
deverá indicar o código de retorno EEEEE
igual a 19003
que significa
“Não existe sessão fiscal”.
>>> try:
... # suponha que o equipamento nunca tenha executado um comando fiscal
... resp = cliente.consultar_ultima_sessao_fiscal()
... except ExcecaoRespostaSAT as err:
... pass
...
>>> err.resposta.EEEEE
'19003'
>>> err.resposta.mensagem
'Não existe sessão fiscal'
Veja mais detalhes em Venda e Cancelamento.
Novo na versão 2.0.
ExtrairLogs¶
A função ExtrairLogs
(ER item 6.1.12, método
extrair_logs()
) retorna os registros
de log do equipamento SAT. A resposta para esta função possui duas
particularidades: primeiro que os registros de log podem ser automaticamente
decodificados através do método
conteudo()
; segundo que o
nome dado para este campo pela ER SAT fica muito longo e, portanto, foi chamado
apenas de arquivoLog
.
>>> resp = cliente.extrair_logs()
>>> resp.mensagem
'Transferência completa'
>>> resp.arquivoLog
'MjAxNTA2MTIxNTAzNTB...jaGF2ZXMgZW5jb250cmFkbyBubyB0b2tlbg=='
>>> print(resp.conteudo())
20150612150350|SAT|info|nvl 2:token inicializado
20150612150350|SAT|info|nvl 2:par de chaves encontrado no token
20150612150350|SAT|info|nvl 2:certificado encontrado no token
20150612150350|SAT-SEFAZ|info|nvl 2:(CFeStatus) acessado o webservice
20150612150351|SAT|erro|nvl 0:(no error) marca inicio dos logs (01.00.00:48)
20150612150351|SAT|info|nvl 1:Equipamento inicializado
20150612150352|SEFAZ-SAT|info|nvl 2:(CFeStatus) status do equipamento recebido pela SEFAZ
20150612150356|SAT|info|nvl 1:relogio sincronizado com sucesso
20150612150356|SAT-SEFAZ|info|nvl 2:(CFeComandos) acessado o webservice
20150612153407|SEFAZ-SAT|info|nvl 2:(CFeComandos) não existem comandos pendentes
20150612153544|AC-SAT|info|nvl 2:recebida mensagem referente a função ConsultarSAT
20150612153544|SAT-AC|info|nvl 2:enviando mensagem referente a função ConsultarSAT
20150612153544|AC-SAT|info|nvl 2:recebida mensagem referente a função ConsultarStatusOperacional
Também é possível salvar o conteúdo decodificado dos registros de log através do
método salvar()
:
>>> resp = cliente.extrair_logs()
>>> resp.salvar()
'/tmp/tmpNhVSHi-sat.log'
Funções de Configuração/Modificação¶
As funções a seguir são utilizadas para configurar o equipamento SAT ou acabam por modificar certos registros de informações que ficam permanentemente gravadas no equipamento.
AtivarSAT¶
A função AtivarSAT
(ER item 6.1.1, método
ativar_sat()
) é usada para realizar a
ativação do equipamento SAT tornando-o apto para realizar vendas e
cancelamentos. Para maiores detalhes consulte o item 2.1.1 da ER SAT.
>>> from satcomum import constantes
>>> from satcomum import br
>>> cnpj_contribuinte = '12345678000199'
>>> resp = cliente.ativar_sat(constantes.CERTIFICADO_ACSAT_SEFAZ,
... cnpj_contribuinte, br.codigo_ibge_uf('SP'))
...
>>> resp.csr()
'-----BEGIN CERTIFICATE REQUEST-----
MIIBnTCCAQYCAQAwXTELMAkGA1UEBhMCU0cxETAPBgNVBAoTCE0yQ3J5cHRvMRIw
...
9rsQkRc9Urv9mRBIsredGnYECNeRaK5R1yzpOowninXC
-----END CERTIFICATE REQUEST-----
Atenção
É nesta função que é definido o Código de Ativação do equipamento SAT.
Este código é uma senha que é enviada ao equipamento a cada função executada. Se o equipamento ainda não estiver ativo, esta deverá ser a primeira função a ser executada e o código de ativação informado ao instanciar o cliente SAT é o código que será usado para definir o código de ativação do equipamento.
Veja TrocarCodigoDeAtivacao para outros detalhes.
ComunicarCertificadoICPBRASIL¶
A função ComunicarCertificadoICPBRASIL
(ER item 6.1.2, método
comunicar_certificado_icpbrasil()
) é
complementar à função AtivarSAT
e é usada para enviar à SEFAZ o conteúdo do
certificado emitido pela ICP Brasil.
>>> with open('certificado.pem', 'r') as f:
... certificado = f.read()
...
>>> resp = cliente.comunicar_certificado_icpbrasil(certificado)
>>> resp.mensagem
'Certificado transmitido com sucesso'
ConfigurarInterfaceDeRede¶
A função ConfigurarInterfaceDeRede
(ER item 6.1.9, método
configurar_interface_de_rede()
) é
utilizada para configurar o acesso à rede para que o equipamento SAT possa ter
acesso à internet. Os parâmetros de configuração são informados através de uma
instância da classe ConfiguracaoRede
.
Nota
Se o equipamento ainda não tiver sido ativado, o código de ativação ao
invocar esta função deverá ser 00000000
(oito dígitos zero).
>>> from satcomum import constantes
>>> from satcfe.rede import ConfiguracaoRede
>>> rede = ConfiguracaoRede(
... tipoInter=constantes.REDE_TIPOINTER_ETHE,
... tipoLan=constantes.REDE_TIPOLAN_DHCP)
...
>>> resp = cliente.configurar_interface_de_rede(rede)
>>> resp.mensagem
'Rede configurada com sucesso'
AssociarAssinatura¶
A função AssociarAssinatura
(ER item 6.1.10, método
associar_assinatura()
) é usada para
vincular a assinatura do aplicativo comercial ao equipamento SAT. Essa mesma
assinatura é utilizada no atributo signAC
ao realizar
vendas e
cancelamentos.
>>> resp = cliente.associar_assinatura(
... '1111111111111122222222222222',
... 'RVlHYkYzcytsZFdiekM4SExmNFVLaXlaZF...')
...
>>> resp.mensagem
'Assinatura do AC registrada'
O primeiro argumento, sequencia_cnpj
, deve ser uma string de 28 digitos
contendo o CNPJ da software house e o CNPJ do estabelecimento contribuinte.
O segundo argumento, assinatura_ac
, deve ser uma sequência de 344
caracteres, contendo o hash SHA256
codificado em Base64.
Gerando a Assinatura AC
Este exemplo demonstra como gerar a assinatura em um terminal Linux,
usando OpenSSL e a parte privada da sua
chave RSA (assumindo que a chave privada tenha sido extraída do e-CNPJ ou
arquivo pfx
do certificado digital da software house em um arquivo
chamado ~/.keys/private.pem
):
$ echo -n 1111111111111122222222222222 | \
openssl dgst -sha256 -sign ~/.keys/private.pem | \
openssl enc -base64 -e
A saída desse comando é o valor que deverá ser informado no argumento
assinatura_ac
.
Dica
Consulte o item 2.1.3 da ER SAT para conhecer a especificação.
AtualizarSoftwareSAT¶
A função AtualizarSoftwareSAT
(ER item 6.1.11, método
atualizar_software_sat()
) é usada
para atualização do software básico do equipamento SAT.
>>> resp = cliente.atualizar_software_sat()
>>> resp.mensagem
'Software atualizado com sucesso'
BloquearSAT¶
A função BloquearSAT
(ER item 6.1.13, método
bloquear_sat()
) é usada para
realizar o bloqueio operacional do equipamento SAT.
>>> resp = cliente.bloquear_sat()
>>> resp.mensagem
'Equipamento SAT bloqueado com sucesso'
DesbloquearSAT¶
A função DesbloquearSAT
(ER item 6.1.14, método
desbloquear_sat()
) é usada para
realizar o desbloqueio operacional do equipamento SAT.
>>> resp = cliente.desbloquear_sat()
>>> resp.mensagem
'Equipamento SAT desbloqueado com sucesso'
TrocarCodigoDeAtivacao¶
A função TrocarCodigoDeAtivacao
(ER item 6.1.15, método
trocar_codigo_de_ativacao()
) é
usada, como o nome sugere, para trocar o código de ativação do equipamento SAT
que, na prática, é uma senha que é enviada ao equipamento SAT a cada comando.
>>> novo_codigo = 's3cr3t0'
>>> resp = cliente.trocar_codigo_de_ativacao(novo_codigo)
>>> resp.mensagem
'Código de ativação alterado com sucesso'
Todo equipamento SAT possui um código de ativação de emergência, que acompanha o produto (pode estar escrito no manual do usuário ou em alguma etiqueta na embalagem ou no próprio equipamento). Caso o código de ativação seja perdido, é possível trocar o código de ativação usando o código de ativação de emergência:
>>> from satcomum import constantes
>>> novo_codigo = 's3cr3t0'
>>> resp = cliente.trocar_codigo_de_ativacao(
... novo_codigo,
... opcao=constantes.CODIGO_ATIVACAO_EMERGENCIA,
... codigo_emergencia='d35c0nh3c1d0')
...
>>> resp.mensagem
'Código de ativação alterado com sucesso'
Veja AtivarSAT para mais informações.