cfdi-test/source/comercio/comercio.py

188 lines
5.6 KiB
Python

#!/usr/bin/env python
# ~
# ~ PAC
# ~ Copyright (C) 2018-2019 Mauricio Baeza Servin - public [AT] elmau [DOT] net
# ~
# ~ This program is free software: you can redistribute it and/or modify
# ~ it under the terms of the GNU General Public License as published by
# ~ the Free Software Foundation, either version 3 of the License, or
# ~ (at your option) any later version.
# ~
# ~ This program is distributed in the hope that it will be useful,
# ~ but WITHOUT ANY WARRANTY; without even the implied warranty of
# ~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# ~ GNU General Public License for more details.
# ~
# ~ You should have received a copy of the GNU General Public License
# ~ along with this program. If not, see <http://www.gnu.org/licenses/>.
import logging
import lxml.etree as ET
import requests
from requests.exceptions import ConnectionError
from .conf import DEBUG, DEBUG_SOAP, AUTH
logging.getLogger('requests').setLevel(logging.ERROR)
TIMEOUT = 10
class PACComercioDigital(object):
ws = 'https://{}.comercio-digital.mx/{}'
URL = {
'timbra': ws.format('ws', 'timbre/timbrarV5.aspx'),
'cancel': ws.format('cancela', 'cancela3/cancelarUuid'),
'cancelxml': ws.format('cancela', 'cancela3/cancelarXml'),
}
if DEBUG:
ws = 'https://pruebas.comercio-digital.mx/{}'
URL = {
'timbra': ws.format('timbre/timbrarV5.aspx'),
'cancel': ws.format('cancela3/cancelarUuid'),
'cancelxml': ws.format('cancela3/cancelarXml'),
}
def __init__(self):
self.error = ''
def stamp(self, cfdi):
auth = AUTH
host = self.URL['timbra'].split('/')[2]
headers = {
'Content-type': 'text/plain',
'usrws': auth['user'],
'pwdws': auth['pass'],
'tipo': 'XML',
'Host': host,
'Expect' : '100-continue',
'Connection' : 'Keep-Alive',
}
result = requests.post(self.URL['timbra'],
data=cfdi, headers=headers, timeout=TIMEOUT)
if result.status_code != 200:
return ''
if 'errmsg' in result.headers:
self.error = result.headers['errmsg']
print(self.error)
return ''
return result.text
def _get_data_cancel(self, cfdi, info):
NS_CFDI = {
'cfdi': 'http://www.sat.gob.mx/cfd/3',
'tdf': 'http://www.sat.gob.mx/TimbreFiscalDigital',
}
tree = ET.fromstring(cfdi)
tipo = tree.xpath(
'string(//cfdi:Comprobante/@TipoDeComprobante)',
namespaces=NS_CFDI)
total = tree.xpath(
'string(//cfdi:Comprobante/@Total)',
namespaces=NS_CFDI)
rfc_emisor = tree.xpath(
'string(//cfdi:Comprobante/cfdi:Emisor/@Rfc)',
namespaces=NS_CFDI)
rfc_receptor = tree.xpath(
'string(//cfdi:Comprobante/cfdi:Receptor/@Rfc)',
namespaces=NS_CFDI)
uid = tree.xpath(
'string(//cfdi:Complemento/tdf:TimbreFiscalDigital/@UUID)',
namespaces=NS_CFDI)
data = (
f"USER={AUTH['user']}",
f"PWDW={AUTH['pass']}",
f"RFCE={rfc_emisor}",
f"UUID={uid}",
f"PWDK={info['pass']}",
f"KEYF={info['key']}",
f"CERT={info['cer']}",
f"TIPO={info['tipo']}",
f"ACUS=SI",
f"RFCR={rfc_receptor}",
f"TIPOC={tipo}",
f"TOTAL={total}",
)
return '\n'.join(data)
def cancel(self, cfdi, info):
url = self.URL['cancel']
host = url.split('/')[2]
headers = {
'Content-type': 'text/plain',
'Host': host,
'Expect' : '100-continue',
'Connection' : 'Keep-Alive',
}
data = self._get_data_cancel(cfdi, info)
result = requests.post(url, data=data, headers=headers, timeout=TIMEOUT)
if result.status_code != 200:
return ''
if result.headers['codigo'] != '000':
self.error = result.headers['errmsg']
print(self.error)
return ''
return result.content
def _get_headers_cancel_xml(self, cfdi, info):
auth = AUTH
host = self.URL['cancelxml'].split('/')[2]
NS_CFDI = {
'cfdi': 'http://www.sat.gob.mx/cfd/3',
'tdf': 'http://www.sat.gob.mx/TimbreFiscalDigital',
}
tree = ET.fromstring(cfdi)
tipo = tree.xpath(
'string(//cfdi:Comprobante/@TipoDeComprobante)',
namespaces=NS_CFDI)
total = tree.xpath(
'string(//cfdi:Comprobante/@Total)',
namespaces=NS_CFDI)
rfc_receptor = tree.xpath(
'string(//cfdi:Comprobante/cfdi:Receptor/@Rfc)',
namespaces=NS_CFDI)
headers = {
'usrws': auth['user'],
'pwdws': auth['pass'],
'rfcr': rfc_receptor,
'total': total,
'tipocfdi': tipo,
'Content-type': 'text/plain',
'Host': host,
'Expect' : '100-continue',
'Connection' : 'Keep-Alive',
}
headers.update(info)
return headers
def cancel_xml(self, cfdi, xml, info):
url = self.URL['cancelxml']
headers = self._get_headers_cancel_xml(cfdi, info)
result = requests.post(url, data=xml, headers=headers, timeout=TIMEOUT)
if result.status_code != 200:
return ''
if result.headers['codigo'] != '000':
self.error = result.headers['errmsg']
print(self.error)
return ''
return result.content