Arreglado el error al cancelar con CD

This commit is contained in:
El Mau 2022-01-26 22:32:38 -06:00
commit dd606be46b
10 changed files with 125 additions and 82 deletions

View File

@ -1,3 +1,13 @@
v 1.45.4 [25-Ene-2022]
----------------------
- Error: Al timbrar carta porte.
- Error: Al cancelar con Comercio Digital.
- Error: Issue #42
- Mejora: Issue #44
* IMPORTANTE: Es necesario subir de nuevo tus certificados de sello, **solo** si timbras con Comercio Digital.
v 1.45.3 [23-Ene-2022] v 1.45.3 [23-Ene-2022]
---------------------- ----------------------
- Error: El enviar por correo CFDI de pago. Ticket #40 - Error: El enviar por correo CFDI de pago. Ticket #40

View File

@ -1,2 +1,2 @@
1.45.3 1.45.4

View File

@ -2,6 +2,7 @@
import falcon import falcon
from middleware import get_template from middleware import get_template
from urllib.parse import unquote
class AppEmpresas(object): class AppEmpresas(object):
@ -111,6 +112,14 @@ class AppValues(object):
def on_get(self, req, resp, table): def on_get(self, req, resp, table):
values = req.params values = req.params
session = req.env['beaker.session'] session = req.env['beaker.session']
if table == 'product':
original = values['name']
try:
values['name'] = unquote(req.query_string.split('=')[1])
except Exception as e:
values['name'] = original
if req.path in ('/values/titlelogin', '/values/empresas'): if req.path in ('/values/titlelogin', '/values/empresas'):
req.context['result'] = self._db.get_values(table, values, session) req.context['result'] = self._db.get_values(table, values, session)
resp.status = falcon.HTTP_200 resp.status = falcon.HTTP_200
@ -280,6 +289,7 @@ class AppProducts(object):
def on_get(self, req, resp): def on_get(self, req, resp):
values = req.params values = req.params
user = req.env['beaker.session']['userobj'] user = req.env['beaker.session']['userobj']
if 'opt' in values: if 'opt' in values:
req.context['result'] = self._db.products_get(values, user) req.context['result'] = self._db.products_get(values, user)
else: else:

View File

@ -28,18 +28,18 @@ class SATCertificate(object):
self._init_values() self._init_values()
self._get_data_cer(cer) self._get_data_cer(cer)
self._get_data_key(key, password) self._get_data_key(key, password)
if not password: # ~ if not password:
self._test() # ~ self._test()
def _test(self): # ~ def _test(self):
key = self._get_key('') # ~ key = self._get_key('')
self._p = TOKEN # ~ self._p = ''
self._key_der = key.private_bytes( # ~ self._key_der = key.private_bytes(
encoding=serialization.Encoding.DER, # ~ encoding=serialization.Encoding.DER,
format=serialization.PrivateFormat.PKCS8, # ~ format=serialization.PrivateFormat.PKCS8,
encryption_algorithm=serialization.BestAvailableEncryption(self._p.encode()) # ~ encryption_algorithm=serialization.BestAvailableEncryption(self._p.encode())
) # ~ )
return # ~ return
def _init_values(self): def _init_values(self):
self._rfc = '' self._rfc = ''
@ -52,6 +52,7 @@ class SATCertificate(object):
self._is_fiel = False self._is_fiel = False
self._are_couple = False self._are_couple = False
self._is_valid_time = False self._is_valid_time = False
self._key = b''
self._cer = b'' self._cer = b''
self._cer_pem = '' self._cer_pem = ''
self._cer_txt = '' self._cer_txt = ''
@ -110,6 +111,8 @@ class SATCertificate(object):
return return
def _get_data_key(self, key, password): def _get_data_key(self, key, password):
self._key = key
self._keyp = password
self._key_enc = key self._key_enc = key
if not key or not password: if not key or not password:
return return
@ -175,17 +178,18 @@ class SATCertificate(object):
node = xmlsec.tree.find_node(tree, 'X509Certificate') node = xmlsec.tree.find_node(tree, 'X509Certificate')
node.text = self.cer_txt node.text = self.cer_txt
# ~ node = xmlsec.tree.find_node(tree, 'X509IssuerName') node = xmlsec.tree.find_node(tree, 'X509IssuerName')
# ~ node.text = self.issuer node.text = self.issuer
node = xmlsec.tree.find_node(tree, 'X509SerialNumber') node = xmlsec.tree.find_node(tree, 'X509SerialNumber')
node.text = self.serial_number node.text = self.serial_number
# ~ node = xmlsec.tree.find_node(tree, 'SignatureValue') node = xmlsec.tree.find_node(tree, 'SignatureValue')
# ~ node.text = node.text.replace('\n', '') node.text = node.text.replace('\n', '')
# ~ node = xmlsec.tree.find_node(tree, 'Modulus') node = xmlsec.tree.find_node(tree, 'Modulus')
# ~ node.text = node.text.replace('\n', '') node.text = node.text.replace('\n', '')
xml_signed = ET.tostring(tree, # ~ xml_signed = ET.tostring(tree,
xml_declaration=True, encoding='UTF-8').decode() # ~ xml_declaration=True, encoding='UTF-8').decode()
xml_signed = ET.tostring(tree, encoding='UTF-8').decode().replace('\n', '')
return xml_signed return xml_signed

View File

@ -42,6 +42,23 @@ logging.getLogger('requests').setLevel(logging.ERROR)
TIMEOUT = 10 TIMEOUT = 10
def pretty_print_POST(req):
"""
At this point it is completely built and ready
to be fired; it is "prepared".
However pay attention at the formatting used in
this function because it is programmed to be pretty
printed and may differ from the actual request.
"""
print('{}\n{}\r\n{}\r\n\r\n{}'.format(
'-----------START-----------',
req.method + ' ' + req.url,
'\r\n'.join('{}: {}'.format(k, v) for k, v in req.headers.items()),
req.body,
))
class PACComercioDigital(object): class PACComercioDigital(object):
ws = 'https://{}.comercio-digital.mx/{}' ws = 'https://{}.comercio-digital.mx/{}'
api = 'https://app2.comercio-digital.mx/{}' api = 'https://app2.comercio-digital.mx/{}'
@ -91,6 +108,12 @@ class PACComercioDigital(object):
headers['host'] = url.split('/')[2] headers['host'] = url.split('/')[2]
headers['Content-type'] = 'text/plain' headers['Content-type'] = 'text/plain'
headers['Connection'] = 'Keep-Alive' headers['Connection'] = 'Keep-Alive'
headers['Expect'] = '100-continue'
if DEBUG:
req = requests.Request('POST', url, headers=headers, data=data)
prepared = req.prepare()
pretty_print_POST(prepared)
try: try:
result = requests.post(url, data=data, headers=headers, timeout=TIMEOUT) result = requests.post(url, data=data, headers=headers, timeout=TIMEOUT)
@ -150,7 +173,7 @@ class PACComercioDigital(object):
def _get_data_cancel(self, cfdi, info, auth): def _get_data_cancel(self, cfdi, info, auth):
info['tipo'] = 'cfdi' info['tipo'] = 'cfdi'
info['key'] = base64.b64encode(info['key_enc']).decode() info['key'] = base64.b64encode(info['key_enc']).decode()
info['cer'] = base64.b64encode(info['cer']).decode() info['cer'] = base64.b64encode(info['cer_ori']).decode()
NS_CFDI = { NS_CFDI = {
'cfdi': 'http://www.sat.gob.mx/cfd/3', 'cfdi': 'http://www.sat.gob.mx/cfd/3',

View File

@ -765,9 +765,9 @@ def get_pac_by_rfc(cfdi):
def _cancel_with_cert(invoice, args, auth, certificado): def _cancel_with_cert(invoice, args, auth, certificado):
cert = SATCertificate(certificado.cer, certificado.key_enc.encode()) cert = SATCertificate(certificado.cer, certificado.key_enc.encode())
pac = PACS[auth['pac']]() pac = PACS[auth['pac']]()
info = {'cer': cert.cer_pem, 'key': cert.key_pem, 'pass': '', 'args': args} # ~ info = {'cer': cert.cer_pem, 'key': cert.key_pem, 'pass': '', 'args': args}
# ~ info = {'cer': cert.cer_pem, 'key': cert.key_pem, info = {'cer': cert.cer_pem, 'key': cert.key_pem, 'cer_ori': cert.cer,
# ~ 'key_enc': cert._key_der, 'pass': cert._p, 'args': args} 'key_enc': certificado.key, 'pass': decrypt(bytes(certificado.p12).decode(), certificado.serie), 'args': args}
result = pac.cancel(invoice.xml, info, auth) result = pac.cancel(invoice.xml, info, auth)
if pac.error: if pac.error:
@ -781,8 +781,8 @@ def _cancel_with_cert(invoice, args, auth, certificado):
def cancel_xml_sign(invoice, args, auth, certificado): def cancel_xml_sign(invoice, args, auth, certificado):
if auth['pac'] == 'finkok': # ~ if auth['pac'] == 'finkok':
return _cancel_with_cert(invoice, args, auth, certificado) return _cancel_with_cert(invoice, args, auth, certificado)
cert = SATCertificate(certificado.cer, certificado.key_enc.encode()) cert = SATCertificate(certificado.cer, certificado.key_enc.encode())
pac = PACS[auth['pac']]() pac = PACS[auth['pac']]()
@ -796,11 +796,11 @@ def cancel_xml_sign(invoice, args, auth, certificado):
'motivo': args['reason'], 'motivo': args['reason'],
'folio': folio_new, 'folio': folio_new,
} }
template = TEMPLATE_CANCEL.format(**data) template = TEMPLATE_CANCEL.format(**data)
tree = ET.fromstring(template.encode()) tree = ET.fromstring(template.encode())
sign_xml = cert.sign_xml(tree) sign_xml = cert.sign_xml(tree)
# ~ print(sign_xml) # ~ print(sign_xml)
result = pac.cancel_xml(sign_xml, auth, invoice.xml) result = pac.cancel_xml(sign_xml, auth, invoice.xml)
if pac.error: if pac.error:

View File

@ -1167,9 +1167,12 @@ class Certificado(BaseModel):
result['msg'] = 'El RFC del certificado no corresponde.' result['msg'] = 'El RFC del certificado no corresponde.'
return result return result
obj.key = cert._key
obj.key_enc = cert.key_enc obj.key_enc = cert.key_enc
obj.cer = cert.cer obj.cer = cert.cer
obj.serie = cert.serial_number obj.serie = cert.serial_number
obj.cer_txt = cert.cer_txt
obj.p12 = utils.encrypt(cert._keyp, cert.serial_number)
obj.desde = cert.not_before obj.desde = cert.not_before
obj.hasta = cert.not_after obj.hasta = cert.not_after
obj.save() obj.save()
@ -4158,6 +4161,12 @@ class Productos(BaseModel):
msg = 'Ya existe un producto con esta clave' msg = 'Ya existe un producto con esta clave'
data = {'ok': False, 'row': {}, 'new': False, 'msg': msg} data = {'ok': False, 'row': {}, 'new': False, 'msg': msg}
return data return data
except Exception as e:
msg = str(e)
if 'productos_clave' in msg:
msg = 'Ya existe un producto con esta clave'
data = {'ok': False, 'row': {}, 'new': False, 'msg': msg}
return data
obj = Productos.get(Productos.id==id) obj = Productos.get(Productos.id==id)
obj.impuestos = obj_taxes obj.impuestos = obj_taxes
@ -5357,7 +5366,7 @@ class Facturas(BaseModel):
ubicaciones = values['ubicaciones'] ubicaciones = values['ubicaciones']
for ubicacion in ubicaciones: for ubicacion in ubicaciones:
if 'DistanciaRecorrida' in ubicacion: if 'DistanciaRecorrida' in ubicacion and ubicacion['DistanciaRecorrida']:
total_distance += float(ubicacion['DistanciaRecorrida']) total_distance += float(ubicacion['DistanciaRecorrida'])
if isinstance(ubicacion['DistanciaRecorrida'], (int, float)): if isinstance(ubicacion['DistanciaRecorrida'], (int, float)):
ubicacion['DistanciaRecorrida'] = f"{ubicacion['DistanciaRecorrida']:.2f}" ubicacion['DistanciaRecorrida'] = f"{ubicacion['DistanciaRecorrida']:.2f}"

View File

@ -42,7 +42,7 @@ except ImportError:
DEBUG = DEBUG DEBUG = DEBUG
VERSION = '1.45.3' VERSION = '1.45.4'
EMAIL_SUPPORT = ('soporte@empresalibre.mx',) EMAIL_SUPPORT = ('soporte@empresalibre.mx',)
TITLE_APP = '{} v{}'.format(TITLE_APP, VERSION) TITLE_APP = '{} v{}'.format(TITLE_APP, VERSION)
@ -256,38 +256,41 @@ DEFAULT_GLOBAL = {
'clave_sat': '01010101', 'clave_sat': '01010101',
} }
TEMPLATE_CANCEL = """<Cancelacion xmlns="http://cancelacfd.sat.gob.mx" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" RfcEmisor="{rfc}" Fecha="{fecha}"> # ~ TEMPLATE_CANCEL = """<CancelaCFD xmlns="http://cancelacfd.sat.gob.mx">
<Folios> # ~ <Cancelacion xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" RfcEmisor="{rfc}" Fecha="{fecha}">
<Folio UUID="{uuid}" Motivo="{motivo}"{folio}/> # ~ <Folios>
</Folios> # ~ <Folio UUID="{uuid}" Motivo="{motivo}"{folio}/>
<Signature xmlns="http://www.w3.org/2000/09/xmldsig#"> # ~ </Folios>
<SignedInfo> # ~ <Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
<CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315" /> # ~ <SignedInfo>
<SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1" /> # ~ <CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315" />
<Reference URI=""> # ~ <SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1" />
<Transforms> # ~ <Reference URI="">
<Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature" /> # ~ <Transforms>
</Transforms> # ~ <Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature" />
<DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" /> # ~ </Transforms>
<DigestValue/> # ~ <DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" />
</Reference> # ~ <DigestValue/>
</SignedInfo> # ~ </Reference>
<SignatureValue/> # ~ </SignedInfo>
<KeyInfo> # ~ <SignatureValue/>
<X509Data> # ~ <KeyInfo>
<X509IssuerSerial> # ~ <X509Data>
<X509IssuerName/> # ~ <X509IssuerSerial>
<X509SerialNumber/> # ~ <X509IssuerName/>
</X509IssuerSerial> # ~ <X509SerialNumber/>
<X509Certificate/> # ~ </X509IssuerSerial>
</X509Data> # ~ <X509Certificate/>
<KeyValue> # ~ </X509Data>
<RSAKeyValue> # ~ <KeyValue>
<Modulus/> # ~ <RSAKeyValue>
<Exponent/> # ~ <Modulus/>
</RSAKeyValue> # ~ <Exponent/>
</KeyValue> # ~ </RSAKeyValue>
</KeyInfo> # ~ </KeyValue>
</Signature> # ~ </KeyInfo>
</Cancelacion>""" # ~ </Signature>
# ~ TEMPLATE_CANCEL = """<Cancelacion xmlns="http://cancelacfd.sat.gob.mx" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" RfcEmisor="{rfc}" Fecha="{fecha}"><Folios><Folio UUID="{uuid}" Motivo="{motivo}"{folio}/></Folios><Signature xmlns="http://www.w3.org/2000/09/xmldsig#"><SignedInfo><CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315" /><SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1" /><Reference URI=""><Transforms><Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature" /></Transforms><DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" /><DigestValue/></Reference></SignedInfo><SignatureValue/><KeyInfo><X509Data><X509IssuerSerial><X509IssuerName/><X509SerialNumber/></X509IssuerSerial><X509Certificate/></X509Data><KeyValue><RSAKeyValue><Modulus/><Exponent/></RSAKeyValue></KeyValue></KeyInfo></Signature></Cancelacion>""" # ~ </Cancelacion>
# ~ </CancelaCFD>"""
TEMPLATE_CANCEL = """<CancelaCFD xmlns="http://cancelacfd.sat.gob.mx"><Cancelacion xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" Fecha="{fecha}" RfcEmisor="{rfc}" xmlns="http://cancelacfd.sat.gob.mx"><Folios><Folio UUID="{uuid}" Motivo="{motivo}"{folio}/></Folios><Signature xmlns="http://www.w3.org/2000/09/xmldsig#"><SignedInfo><CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315" /><SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1" /><Reference URI=""><Transforms><Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature" /></Transforms><DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" /><DigestValue/></Reference></SignedInfo><SignatureValue/><KeyInfo><X509Data><X509IssuerSerial><X509IssuerName/><X509SerialNumber/></X509IssuerSerial><X509Certificate/></X509Data><KeyValue><RSAKeyValue><Modulus/><Exponent/></RSAKeyValue></KeyValue></KeyInfo></Signature></Cancelacion></CancelaCFD>"""

View File

@ -1097,22 +1097,6 @@ function search_product_by_key(key){
} }
} }
}) })
//~ webix.ajax().get('/values/productokey', {'key': key}, {
//~ error: function(text, data, xhr) {
//~ msg_error('Error al consultar')
//~ },
//~ success: function(text, data, xhr){
//~ var values = data.json()
//~ if (values.ok){
//~ set_product(values)
//~ } else {
//~ msg = 'No se encontró un producto con la clave: ' + key
//~ msg_error(msg)
//~ }
//~ }
//~ })
} }

View File

@ -746,7 +746,7 @@ var opt_clave_entidad = [
{id: 'GRO', value: 'Guerrero'}, {id: 'GRO', value: 'Guerrero'},
{id: 'HID', value: 'Hidalgo'}, {id: 'HID', value: 'Hidalgo'},
{id: 'JAL', value: 'Jalisco'}, {id: 'JAL', value: 'Jalisco'},
{id: 'MEX', value: 'México'}, {id: 'MEX', value: 'Estado de México'},
{id: 'MIC', value: 'Michoacán'}, {id: 'MIC', value: 'Michoacán'},
{id: 'MOR', value: 'Morelos'}, {id: 'MOR', value: 'Morelos'},
{id: 'NAC', value: 'Nacional'}, {id: 'NAC', value: 'Nacional'},
@ -881,7 +881,7 @@ var opt_carta_estados = [
{id: 'GRO', value: 'Guerrero'}, {id: 'GRO', value: 'Guerrero'},
{id: 'HID', value: 'Hidalgo'}, {id: 'HID', value: 'Hidalgo'},
{id: 'JAL', value: 'Jalisco'}, {id: 'JAL', value: 'Jalisco'},
{id: 'MEX', value: 'México'}, {id: 'MEX', value: 'Estado de México'},
{id: 'MIC', value: 'Michoacán'}, {id: 'MIC', value: 'Michoacán'},
{id: 'MOR', value: 'Morelos'}, {id: 'MOR', value: 'Morelos'},
{id: 'NAC', value: 'Nacional'}, {id: 'NAC', value: 'Nacional'},