cfdi-pac/source/tests/tests_comercio.py

240 lines
7.6 KiB
Python

#!/usr/bin/env python3
import base64
import datetime
import sys
import time
import unittest
import uuid
import lxml.etree as ET
from io import BytesIO
from pathlib import Path
sys.path.append('..')
from pycert import SATCertificate
from comerciodigital import PACComercioDigital
NAME = 'comercio'
TEMPLATE_CFDI = """<cfdi:Comprobante xmlns:cfdi="http://www.sat.gob.mx/cfd/3" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" LugarExpedicion="06850" Moneda="MXN" SubTotal="10000.00" TipoCambio="1" TipoDeComprobante="I" Total="11600.00" FormaPago="01" MetodoPago="PUE" Version="3.3" xsi:schemaLocation="http://www.sat.gob.mx/cfd/3 http://www.sat.gob.mx/sitio_internet/cfd/3/cfdv33.xsd">
<cfdi:Emisor Rfc="EKU9003173C9" RegimenFiscal="601"/>
<cfdi:Receptor Rfc="BASM740115RW0" UsoCFDI="G01"/>
<cfdi:Conceptos>
<cfdi:Concepto Cantidad="1.0" ClaveProdServ="60121001" ClaveUnidad="ACT" Descripcion="Asesoría en desarrollo" Importe="10000.00" ValorUnitario="10000.00">
<cfdi:Impuestos>
<cfdi:Traslados>
<cfdi:Traslado Base="10000.00" Importe="1600.00" Impuesto="002" TasaOCuota="0.160000" TipoFactor="Tasa"/>
</cfdi:Traslados>
</cfdi:Impuestos>
</cfdi:Concepto>
</cfdi:Conceptos>
<cfdi:Impuestos TotalImpuestosTrasladados="1600.00">
<cfdi:Traslados>
<cfdi:Traslado Importe="1600.00" Impuesto="002" TasaOCuota="0.160000" TipoFactor="Tasa"/>
</cfdi:Traslados>
</cfdi:Impuestos>
</cfdi:Comprobante>
"""
TEMPLATE_CANCEL = """<Cancelacion RfcEmisor="{rfc}" Fecha="{fecha}" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://cancelacfd.sat.gob.mx">
<Folios>
<UUID>{uuid}</UUID>
</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>
<X509SubjectName />
<X509IssuerSerial />
<X509Certificate />
</X509Data>
<KeyValue />
</KeyInfo>
</Signature>
</Cancelacion>
"""
class TestCfdi(object):
def __init__(self):
self._xml = ''
self._make_cfdi()
@property
def xml(self):
return self._xml.decode()
def _make_cfdi(self):
path = Path(__file__)
path_cer = Path(path.parent).joinpath('certificados', f'{NAME}.cer')
path_key = Path(path.parent).joinpath('certificados', f'{NAME}.enc')
path_xslt = Path(path.parent).joinpath('xslt', 'cadena.xslt')
self._cer_ori = cer = path_cer.read_bytes()
self._key_ori = key = path_key.read_bytes()
self._cert = SATCertificate(cer, key)
self._doc = ET.parse(BytesIO(TEMPLATE_CFDI.encode()))
self._root = self._doc.getroot()
self._root.attrib['Fecha'] = datetime.datetime.now().isoformat()[:19]
self._root.attrib['NoCertificado'] = self._cert.serial_number
self._root.attrib['Certificado'] = self._cert.cer_txt
self._add_stamp(path_xslt)
self._xml = ET.tostring(self._root,
pretty_print=True, doctype='<?xml version="1.0" encoding="utf-8"?>')
return
def _add_stamp(self, path_xslt):
xslt = open(path_xslt, 'rb')
transfor = ET.XSLT(ET.parse(xslt))
cadena = str(transfor(self._doc)).encode()
stamp = self._cert.sign(cadena)
self._root.attrib['Sello'] = stamp
xslt.close()
return
def sign_xml(self, template):
tree = ET.fromstring(template.encode())
tree = self._cert.sign_xml(tree)
xml = ET.tostring(tree).decode()
return xml
@property
def cert(self):
cer = base64.b64encode(self._cer_ori).decode()
key = base64.b64encode(self._key_ori).decode()
return key, cer
class TestStamp(unittest.TestCase):
def setUp(self):
print(f'In method: {self._testMethodName}')
self.pac = PACComercioDigital()
def test_cfdi_stamp(self):
cfdi = TestCfdi().xml
result = self.pac.stamp(cfdi)
cfdi_uuid = self.pac.cfdi_uuid
self.assertFalse(bool(self.pac.error))
self.assertTrue(bool(uuid.UUID(cfdi_uuid)))
def test_cfdi_cancel(self):
expected = '201'
cfdi = TestCfdi()
result = self.pac.stamp(cfdi.xml)
cfdi_uuid = self.pac.cfdi_uuid
self.assertFalse(bool(self.pac.error))
self.assertTrue(bool(uuid.UUID(cfdi_uuid)))
time.sleep(1)
cert = cfdi.cert
info = {
'key': cert[0],
'cer': cert[1],
'pass': '12345678a',
'tipo': 'cfdi3.3',
}
result = self.pac.cancel(result, info)
self.assertFalse(bool(self.pac.error))
tree = ET.fromstring(result)
cancel_uuid = tree.xpath('string(//Acuse/Folios/UUID)')
status = tree.xpath('string(//Acuse/Folios/EstatusUUID)')
self.assertEqual(cfdi_uuid, cancel_uuid)
self.assertEqual(status, expected)
def test_cfdi_cancel_xml(self):
expected = '201'
cfdi = TestCfdi()
result = self.pac.stamp(cfdi.xml)
cfdi_uuid = self.pac.cfdi_uuid
self.assertFalse(bool(self.pac.error))
self.assertTrue(bool(uuid.UUID(cfdi_uuid)))
NS_CFDI = {
'cfdi': 'http://www.sat.gob.mx/cfd/3',
'tdf': 'http://www.sat.gob.mx/TimbreFiscalDigital',
}
tree = ET.fromstring(result.encode())
rfc_emisor = tree.xpath(
'string(//cfdi:Comprobante/cfdi:Emisor/@Rfc)',
namespaces=NS_CFDI)
time.sleep(1)
data = {
'rfc': rfc_emisor,
'fecha': datetime.datetime.now().isoformat()[:19],
'uuid': cfdi_uuid,
}
template = TEMPLATE_CANCEL.format(**data)
sign_xml = cfdi.sign_xml(template)
info = {
'tipo': 'cfdi3.3',
}
result = self.pac.cancel_xml(result, sign_xml, info)
tree = ET.fromstring(result)
uid = tree.xpath('string(//Acuse/Folios/UUID)')
status = tree.xpath('string(//Acuse/Folios/EstatusUUID)')
self.assertEqual(cfdi_uuid, uid)
self.assertEqual(status, expected)
def test_cfdi_status(self):
expected = ''
cfdi = TestCfdi()
result = self.pac.stamp(cfdi.xml)
cfdi_uuid = self.pac.cfdi_uuid
self.assertFalse(bool(self.pac.error))
self.assertTrue(bool(uuid.UUID(cfdi_uuid)))
NS_CFDI = {
'cfdi': 'http://www.sat.gob.mx/cfd/3',
'tdf': 'http://www.sat.gob.mx/TimbreFiscalDigital',
}
tree = ET.fromstring(result.encode())
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)
total = tree.xpath(
'string(//cfdi:Comprobante/@Total)',
namespaces=NS_CFDI)
time.sleep(3)
data = {
'rfc_receptor': rfc_receptor,
'rfc_emisor': rfc_emisor,
'total': total,
'uuid': cfdi_uuid,
}
result = self.pac.status(data)
self.assertEqual(result, expected)
if __name__ == '__main__':
unittest.main()