Test Failed
Push — master ( 8cbe19...3a660b )
by Esteban De La Fuente
06:47
created

AbstractBuilderStrategy::sign()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 34
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 15
CRAP Score 2.1288

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
eloc 19
nc 2
nop 1
dl 0
loc 34
ccs 15
cts 22
cp 0.6818
crap 2.1288
rs 9.6333
c 1
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
/**
6
 * LibreDTE: Biblioteca PHP (Núcleo).
7
 * Copyright (C) LibreDTE <https://www.libredte.cl>
8
 *
9
 * Este programa es software libre: usted puede redistribuirlo y/o modificarlo
10
 * bajo los términos de la Licencia Pública General Affero de GNU publicada por
11
 * la Fundación para el Software Libre, ya sea la versión 3 de la Licencia, o
12
 * (a su elección) cualquier versión posterior de la misma.
13
 *
14
 * Este programa se distribuye con la esperanza de que sea útil, pero SIN
15
 * GARANTÍA ALGUNA; ni siquiera la garantía implícita MERCANTIL o de APTITUD
16
 * PARA UN PROPÓSITO DETERMINADO. Consulte los detalles de la Licencia Pública
17
 * General Affero de GNU para obtener una información más detallada.
18
 *
19
 * Debería haber recibido una copia de la Licencia Pública General Affero de
20
 * GNU junto a este programa.
21
 *
22
 * En caso contrario, consulte <http://www.gnu.org/licenses/agpl.html>.
23
 */
24
25
namespace libredte\lib\Core\Package\Billing\Component\Document\Abstract;
26
27
use DateTime;
28
use Derafu\Backbone\Abstract\AbstractStrategy;
29
use Derafu\Signature\Contract\SignatureServiceInterface;
30
use Derafu\Signature\Exception\SignatureException;
31
use Derafu\Xml\Contract\XmlDocumentInterface;
32
use Derafu\Xml\Contract\XmlServiceInterface;
33
use libredte\lib\Core\Package\Billing\Component\Document\Contract\BuilderStrategyInterface;
34
use libredte\lib\Core\Package\Billing\Component\Document\Contract\DocumentBagInterface;
35
use libredte\lib\Core\Package\Billing\Component\Document\Contract\DocumentInterface;
36
use libredte\lib\Core\Package\Billing\Component\Document\Contract\NormalizerWorkerInterface;
37
use libredte\lib\Core\Package\Billing\Component\Document\Contract\SanitizerWorkerInterface;
38
use libredte\lib\Core\Package\Billing\Component\Document\Contract\ValidatorWorkerInterface;
39
use libredte\lib\Core\Package\Billing\Component\Document\Exception\BuilderException;
40
41
/**
42
 * Clase abstracta (base) para las estrategias de construcción ("builders") de
43
 * documentos tributarios.
44
 */
45
abstract class AbstractBuilderStrategy extends AbstractStrategy implements BuilderStrategyInterface
46
{
47
    /**
48
     * Clase del documento que este "builder" construirá.
49
     *
50
     * @var string
51
     */
52
    protected string $documentClass;
53
54 53
    public function __construct(
55
        private NormalizerWorkerInterface $normalizerWorker,
56
        private SanitizerWorkerInterface $sanitizerWorker,
57
        private ValidatorWorkerInterface $validatorWorker,
58
        private XmlServiceInterface $xmlService,
59
        private SignatureServiceInterface $signatureService
60
    ) {
61 53
    }
62
63
    /**
64
     * {@inheritdoc}
65
     */
66 53
    public function create(XmlDocumentInterface $xmlDocument): DocumentInterface
67
    {
68 53
        $class = $this->documentClass;
69 53
        $document = new $class($xmlDocument);
70
71 53
        return $document;
72
    }
73
74
    /**
75
     * {@inheritdoc}
76
     */
77 53
    public function build(DocumentBagInterface $bag): DocumentInterface
78
    {
79
        // Normalizar los datos del documento de la bolsa si corresponde.
80 53
        $normalize = $bag->getNormalizerOptions()['normalize'] ?? true;
81 53
        if ($normalize) {
82
            // Normalizar los datos parseados de la bolsa.
83 53
            $this->normalizerWorker->normalize($bag);
84
85
            // Sanitizar los datos normalizados de la bolsa.
86 53
            $this->sanitizerWorker->sanitize($bag);
87
88
            // Validar los datos normalizados y sanitizados de la bolsa.
89 53
            $this->validatorWorker->validate($bag);
90
        }
91
92
        // Si existe un CAF en la bolsa se timbra.
93 53
        if ($bag->getCaf()) {
94 53
            $this->stamp($bag);
95
        }
96
97
        // Si existe un certificado en la bolsa se firma.
98 53
        if ($bag->getCertificate()) {
99 53
            $this->sign($bag);
100
        }
101
102
        // Crear el documento XML del DTE y agregar a la bolsa si no se agregó
103
        // previamente (al firmar).
104 53
        if (!$bag->getXmlDocument()) {
105 52
            $xmlDocument = $this->xmlService->encode($bag->getData());
0 ignored issues
show
Bug introduced by
It seems like $bag->getData() can also be of type null; however, parameter $data of Derafu\Xml\Contract\XmlEncoderInterface::encode() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

105
            $xmlDocument = $this->xmlService->encode(/** @scrutinizer ignore-type */ $bag->getData());
Loading history...
106 52
            $bag->setXmlDocument($xmlDocument);
107
        }
108
109
        // Crear el DTE y agregar a la bolsa.
110 53
        $document = $this->create($bag->getXmlDocument());
0 ignored issues
show
Bug introduced by
It seems like $bag->getXmlDocument() can also be of type null; however, parameter $xmlDocument of libredte\lib\Core\Packag...ilderStrategy::create() does only seem to accept Derafu\Xml\Contract\XmlDocumentInterface, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

110
        $document = $this->create(/** @scrutinizer ignore-type */ $bag->getXmlDocument());
Loading history...
111 53
        $bag->setDocument($document);
112
113
        // Entregar la instancia del documento tributario creado.
114 53
        return $document;
115
    }
116
117
    /**
118
     * Timbra un documento tributario.
119
     *
120
     * @param DocumentBagInterface $bag
121
     * @return void
122
     */
123 53
    protected function stamp(DocumentBagInterface $bag): void
124
    {
125
        // Obtener el CAF de la bolsa.
126 53
        $caf = $bag->getCaf();
127
128
        // Generar un borrador del DTE para manipular sus datos.
129 53
        $xmlDocument = $this->xmlService->encode($bag->getNormalizedData());
0 ignored issues
show
Bug introduced by
It seems like $bag->getNormalizedData() can also be of type null; however, parameter $data of Derafu\Xml\Contract\XmlEncoderInterface::encode() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

129
        $xmlDocument = $this->xmlService->encode(/** @scrutinizer ignore-type */ $bag->getNormalizedData());
Loading history...
130 53
        $draft = $this->create($xmlDocument);
131
132
        // Verificar que el folio del documento esté dentro del rango del CAF.
133 53
        if (!$caf->enRango($draft->getFolio())) {
134
            throw new BuilderException(sprintf(
135
                'El folio %d del documento %s no está disponible en el rango del CAF %s.',
136
                $draft->getFolio(),
137
                $draft->getId(),
138
                $caf->getId()
139
            ));
140
        }
141
142
        // Asignar marca de tiempo si no se pasó una.
143 53
        $timestamp = $bag->getBuilderOptions()['timestamp'] ?? date('Y-m-d\TH:i:s');
144
145
        // Corroborar que el CAF esté vigente según el timestamp usado.
146 53
        if (!$caf->vigente($timestamp)) {
147
            throw new BuilderException(sprintf(
148
                'El CAF %s que contiene el folio %d del documento %s no está vigente, venció el día %s.',
149
                $caf->getId(),
150
                $draft->getFolio(),
151
                $draft->getId(),
152
                (new DateTime($caf->getFechaVencimiento()))->format('d/m/Y'),
153
            ));
154
        }
155
156
        // Preparar datos del timbre.
157 53
        $tedData = $draft->getPlantillaTED();
158 53
        $cafArray = $this->xmlService->decode($caf->getXmlDocument());
159 53
        $tedData['TED']['DD']['CAF'] = $cafArray['AUTORIZACION']['CAF'];
160 53
        $tedData['TED']['DD']['TSTED'] = $timestamp;
161
162
        // Armar XML del timbre y obtener los datos a timbrar (tag DD: datos
163
        // del documento).
164 53
        $tedXmlDocument = $this->xmlService->encode($tedData);
165 53
        $ddToStamp = $tedXmlDocument->C14NWithIso88591EncodingFlattened('/TED/DD');
166
167
        // Timbrar los "datos a timbrar" $ddToStamp.
168 53
        $privateKey = $caf->getPrivateKey();
169 53
        $signatureAlgorithm = OPENSSL_ALGO_SHA1;
170
        try {
171 53
            $timbre = $this->signatureService->sign(
172 53
                $ddToStamp,
173 53
                $privateKey,
174 53
                $signatureAlgorithm
175 53
            );
176
        } catch (SignatureException $e) {
177
            throw new BuilderException('No fue posible timbrar los datos.');
178
        }
179
180
        // Agregar timbre al TED.
181 53
        $tedData['TED']['FRMT']['@value'] = $timbre;
182
183
        // Actualizar los datos del documento incorporando el timbre calculado.
184 53
        $bag->setTimbre($tedData);
185
    }
186
187
    /**
188
     * Firma un documento tributario.
189
     *
190
     * @param DocumentBagInterface $bag
191
     * @return void
192
     */
193 53
    protected function sign(DocumentBagInterface $bag): void
194
    {
195 53
        $certificate = $bag->getCertificate();
196
197
        // Asignar marca de tiempo si no se pasó una.
198 53
        $timestamp = $bag->getBuilderOptions()['timestamp'] ?? date('Y-m-d\TH:i:s');
199
200
        // Corroborar que el certificado esté vigente según el timestamp usado.
201 53
        if (!$certificate->isActive($timestamp)) {
202
            throw new BuilderException(sprintf(
203
                'El certificado digital de %s no está vigente en el tiempo %s, su rango de vigencia es del %s al %s.',
204
                $certificate->getID(),
205
                (new DateTime($timestamp))->format('d/m/Y H:i'),
206
                (new DateTime($certificate->getFrom()))->format('d/m/Y H:i'),
207
                (new DateTime($certificate->getTo()))->format('d/m/Y H:i'),
208
            ));
209
        }
210
211
        // Agregar timestamp.
212 53
        $data = $bag->getData();
213 53
        $tagXml = $bag->getTipoDocumento()->getTagXml()->getNombre();
214 53
        $data['DTE'][$tagXml]['TmstFirma'] = $timestamp;
215 53
        $xmlDocument = $this->xmlService->encode($data);
0 ignored issues
show
Bug introduced by
It seems like $data can also be of type null; however, parameter $data of Derafu\Xml\Contract\XmlEncoderInterface::encode() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

215
        $xmlDocument = $this->xmlService->encode(/** @scrutinizer ignore-type */ $data);
Loading history...
216
217
        // Firmar el tag que contiene el documento y retornar el XML firmado.
218 53
        $xmlSigned = $this->signatureService->signXml(
219 53
            $xmlDocument,
220 53
            $certificate,
0 ignored issues
show
Bug introduced by
It seems like $certificate can also be of type null; however, parameter $certificate of Derafu\Signature\Contrac...torInterface::signXml() does only seem to accept Derafu\Certificate\Contract\CertificateInterface, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

220
            /** @scrutinizer ignore-type */ $certificate,
Loading history...
221 53
            $bag->getId()
222 53
        );
223
224
        // Cargar XML en el documento y luego en la bolsa.
225 53
        $xmlDocument->loadXml($xmlSigned);
226 53
        $bag->setXmlDocument($xmlDocument);
227
    }
228
}
229