1
|
|
|
<?php |
2
|
|
|
namespace CfdiUtils; |
3
|
|
|
|
4
|
|
|
use CfdiUtils\CadenaOrigen\DOMBuilder; |
5
|
|
|
use CfdiUtils\CadenaOrigen\XsltBuilderInterface; |
6
|
|
|
use CfdiUtils\CadenaOrigen\XsltBuilderPropertyInterface; |
7
|
|
|
use CfdiUtils\CadenaOrigen\XsltBuilderPropertyTrait; |
8
|
|
|
use CfdiUtils\Certificado\Certificado; |
9
|
|
|
use CfdiUtils\Certificado\CertificadoPropertyInterface; |
10
|
|
|
use CfdiUtils\Certificado\CertificadoPropertyTrait; |
11
|
|
|
use CfdiUtils\Elements\Cfdi33\Comprobante; |
12
|
|
|
use CfdiUtils\Elements\Cfdi33\Helpers\SumasConceptosWriter; |
13
|
|
|
use CfdiUtils\Nodes\NodeInterface; |
14
|
|
|
use CfdiUtils\Nodes\XmlNodeUtils; |
15
|
|
|
use CfdiUtils\PemPrivateKey\PemPrivateKey; |
16
|
|
|
use CfdiUtils\SumasConceptos\SumasConceptos; |
17
|
|
|
use CfdiUtils\Validate\Asserts; |
18
|
|
|
use CfdiUtils\Validate\Hydrater; |
19
|
|
|
use CfdiUtils\Validate\MultiValidatorFactory; |
20
|
|
|
use CfdiUtils\XmlResolver\XmlResolver; |
21
|
|
|
use CfdiUtils\XmlResolver\XmlResolverPropertyInterface; |
22
|
|
|
use CfdiUtils\XmlResolver\XmlResolverPropertyTrait; |
23
|
|
|
|
24
|
|
|
class CfdiCreator33 implements |
25
|
|
|
CertificadoPropertyInterface, |
26
|
|
|
XmlResolverPropertyInterface, |
27
|
|
|
XsltBuilderPropertyInterface |
28
|
|
|
{ |
29
|
|
|
use CertificadoPropertyTrait; |
30
|
|
|
use XmlResolverPropertyTrait; |
31
|
|
|
use XsltBuilderPropertyTrait; |
32
|
|
|
|
33
|
|
|
/** @var Comprobante */ |
34
|
|
|
private $comprobante; |
35
|
|
|
|
36
|
|
|
/** |
37
|
|
|
* CfdiCreator33 constructor. |
38
|
|
|
* @param string[] $complementoAttributes |
39
|
|
|
* @param Certificado|null $certificado |
40
|
|
|
* @param XmlResolver|null $xmlResolver |
41
|
|
|
* @param XsltBuilderInterface|null $xsltBuilder |
42
|
|
|
*/ |
43
|
15 |
|
public function __construct( |
44
|
|
|
array $complementoAttributes = [], |
45
|
|
|
Certificado $certificado = null, |
46
|
|
|
XmlResolver $xmlResolver = null, |
47
|
|
|
XsltBuilderInterface $xsltBuilder = null |
48
|
|
|
) { |
49
|
15 |
|
$this->comprobante = new Comprobante($complementoAttributes); |
50
|
15 |
|
$this->setXmlResolver($xmlResolver ? : new XmlResolver()); |
51
|
15 |
|
if (null !== $certificado) { |
52
|
13 |
|
$this->putCertificado($certificado); |
53
|
|
|
} |
54
|
15 |
|
$this->setXsltBuilder($xsltBuilder ? : new DOMBuilder()); |
55
|
15 |
|
} |
56
|
|
|
|
57
|
|
|
public static function newUsingNode( |
58
|
|
|
NodeInterface $node, |
59
|
|
|
Certificado $certificado = null, |
60
|
|
|
XmlResolver $xmlResolver = null |
61
|
|
|
): self { |
62
|
|
|
$new = new self($node->attributes()->exportArray(), $certificado, $xmlResolver); |
63
|
|
|
$comprobante = $new->comprobante(); |
64
|
|
|
foreach ($node as $child) { |
65
|
|
|
$comprobante->addChild($child); |
66
|
|
|
} |
67
|
|
|
return $new; |
68
|
|
|
} |
69
|
|
|
|
70
|
13 |
|
public function comprobante(): Comprobante |
71
|
|
|
{ |
72
|
13 |
|
return $this->comprobante; |
73
|
|
|
} |
74
|
|
|
|
75
|
13 |
|
public function putCertificado(Certificado $certificado, bool $putEmisorRfcNombre = true) |
76
|
|
|
{ |
77
|
13 |
|
$this->setCertificado($certificado); |
78
|
13 |
|
$cerfile = $certificado->getFilename(); |
79
|
13 |
|
$this->comprobante['NoCertificado'] = $certificado->getSerial(); |
80
|
13 |
|
if (file_exists($cerfile)) { |
81
|
13 |
|
$this->comprobante['Certificado'] = base64_encode(file_get_contents($cerfile)); |
82
|
|
|
} |
83
|
13 |
|
if ($putEmisorRfcNombre) { |
84
|
13 |
|
$this->comprobante->addEmisor([ |
85
|
13 |
|
'Nombre' => $certificado->getName(), |
86
|
13 |
|
'Rfc' => $certificado->getRfc(), |
87
|
|
|
]); |
88
|
|
|
} |
89
|
13 |
|
} |
90
|
|
|
|
91
|
2 |
|
public function asXml(): string |
92
|
|
|
{ |
93
|
2 |
|
return XmlNodeUtils::nodeToXmlString($this->comprobante, true); |
94
|
|
|
} |
95
|
|
|
|
96
|
|
|
public function saveXml(string $filename): bool |
97
|
|
|
{ |
98
|
|
|
return (false !== file_put_contents($filename, $this->asXml())); |
99
|
|
|
} |
100
|
|
|
|
101
|
1 |
|
public function buildCadenaDeOrigen(): string |
102
|
|
|
{ |
103
|
1 |
|
if (! $this->hasXmlResolver()) { |
104
|
|
|
throw new \LogicException( |
105
|
|
|
'Cannot build the cadena de origen since there is no xml resolver' |
106
|
|
|
); |
107
|
|
|
} |
108
|
1 |
|
$xsltLocation = $this->getXmlResolver()->resolveCadenaOrigenLocation('3.3'); |
109
|
1 |
|
return $this->getXsltBuilder()->build($this->asXml(), $xsltLocation); |
110
|
|
|
} |
111
|
|
|
|
112
|
1 |
|
public function buildSumasConceptos(int $precision = 2): SumasConceptos |
113
|
|
|
{ |
114
|
1 |
|
return new SumasConceptos($this->comprobante, $precision); |
115
|
|
|
} |
116
|
|
|
|
117
|
1 |
|
public function addSumasConceptos(SumasConceptos $sumasConceptos = null, int $precision = 2) |
118
|
|
|
{ |
119
|
1 |
|
$sumasConceptos = $sumasConceptos ? : $this->buildSumasConceptos($precision); |
120
|
1 |
|
$writer = new SumasConceptosWriter($this->comprobante, $sumasConceptos, $precision); |
121
|
1 |
|
$writer->put(); |
122
|
1 |
|
} |
123
|
|
|
|
124
|
1 |
|
public function addSello(string $key, string $passPhrase = '') |
125
|
|
|
{ |
126
|
|
|
// create private key |
127
|
1 |
|
$privateKey = new PemPrivateKey($key); |
128
|
1 |
|
if (! $privateKey->open($passPhrase)) { |
129
|
|
|
throw new \RuntimeException('Cannot open the private key'); |
130
|
|
|
} |
131
|
|
|
|
132
|
|
|
// check privatekey belongs to certificado |
133
|
1 |
|
if ($this->hasCertificado()) { |
134
|
1 |
|
if (! $privateKey->belongsTo($this->getCertificado()->getPemContents())) { |
135
|
|
|
throw new \RuntimeException('The private key does not belong to the current certificate'); |
136
|
|
|
} |
137
|
|
|
} |
138
|
|
|
|
139
|
|
|
// create sign and set into Sello attribute |
140
|
1 |
|
$this->comprobante['Sello'] = base64_encode( |
141
|
1 |
|
$privateKey->sign($this->buildCadenaDeOrigen(), OPENSSL_ALGO_SHA256) |
142
|
|
|
); |
143
|
1 |
|
} |
144
|
|
|
|
145
|
|
|
/** |
146
|
|
|
* @return Asserts|\CfdiUtils\Validate\Assert[] |
147
|
|
|
*/ |
148
|
1 |
|
public function validate(): Asserts |
149
|
|
|
{ |
150
|
1 |
|
$factory = new MultiValidatorFactory(); |
151
|
1 |
|
$validator = $factory->newCreated33(); |
152
|
|
|
|
153
|
1 |
|
$hydrater = new Hydrater(); |
154
|
1 |
|
$hydrater->setXmlString($this->asXml()); |
155
|
1 |
|
$hydrater->setXmlResolver(($this->hasXmlResolver()) ? $this->getXmlResolver() : null); |
156
|
1 |
|
$hydrater->setXsltBuilder($this->getXsltBuilder()); |
157
|
1 |
|
$validator->hydrate($hydrater); |
158
|
|
|
|
159
|
1 |
|
$asserts = new Asserts(); |
160
|
1 |
|
$validator->validate($this->comprobante(), $asserts); |
161
|
|
|
|
162
|
1 |
|
return $asserts; |
163
|
|
|
} |
164
|
|
|
|
165
|
|
|
/** |
166
|
|
|
* @return string |
167
|
|
|
*/ |
168
|
2 |
|
public function __toString() |
169
|
|
|
{ |
170
|
|
|
try { |
171
|
2 |
|
return $this->asXml(); |
172
|
1 |
|
} catch (\Throwable $ex) { |
173
|
1 |
|
return ''; |
174
|
|
|
} |
175
|
|
|
} |
176
|
|
|
} |
177
|
|
|
|