126 lines
3.9 KiB
Python
126 lines
3.9 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>
|
|
"""
|
|
|
|
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
|
|
|
|
@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):
|
|
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': '12345678',
|
|
'tipo': 'cfdi3.3',
|
|
}
|
|
result = self.pac.cancel(result, info)
|
|
|
|
self.assertFalse(bool(self.pac.error))
|
|
self.assertTrue(bool(result))
|
|
|
|
|
|
if __name__ == '__main__':
|
|
unittest.main()
|