Skip to content

Commit 7c8d57f

Browse files
committed
Version 2.1.0
- Fix `SumasConceptos` to work also with "ImpuestosLocales" - Add elements helpers `CfdiUtils\Elements\ImpLocal10\ImpuestosLocales` to work with "ImpuestosLocales" - Add `CfdiUtils\Certificado\CerRetriever` that works with `CfdiUtils\XmlResolver\XmlResolver` to download a certificate from the SAT repository - Add a new validator `CfdiUtils\Validate\Cfdi33\Standard\TimbreFiscalDigitalSello` to validate that the SelloSAT is actually the signature of the Timbre Fiscal Digital. If not then the CFDI was modified - Add a new real and valid CFDI to test, this allow `TimbreFiscalDigitalSello` to check real data and pass - Update test with `cfdi33-valid.xml` to allow fail `TimbreFiscalDigitalSello` - Travis: Remove xdebug for all but PHP 7.0
2 parents 460ab78 + d30befd commit 7c8d57f

27 files changed

+875
-11
lines changed

.travis.yml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,11 @@ php:
1111
sudo: false
1212

1313
before_script:
14-
- travis_retry composer self-update
1514
- travis_retry composer install --no-interaction --prefer-dist
15+
- |
16+
if [[ $TRAVIS_PHP_VERSION != '7.0' ]]; then
17+
phpenv config-rm xdebug.ini
18+
fi
1619
1720
script:
1821
- mkdir -p build/tests/

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ Main features:
2020
- Create CFDI version 3.3 based on a friendly extendable non-xml objects (`nodes`)
2121
- Read CFDI version 3.2 and 3.3
2222
- Validate CFDI version 3.3 against schemas, cfdi signature (`Sello`) and custom rules
23+
- Validate that the Timbre Fiscal Digital signature match with the CFDI 3.3,
24+
if not then the document was modified after signature.
2325
- Helper objects to deal with:
2426
- `Cadena de origen` generation
2527
- Extract information from CER files or `Certificado` attribute

composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
],
1818
"require": {
1919
"php": ">=7.0",
20-
"eclipxe/xmlresourceretriever": "^1.0",
20+
"eclipxe/xmlresourceretriever": "^1.2",
2121
"eclipxe/xmlschemavalidator": "^2.0"
2222
},
2323
"require-dev": {

docs/CHANGELOG.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,14 @@
1+
# Version 2.1.0 2018-01-17
2+
- Fix `SumasConceptos` to work also with "ImpuestosLocales"
3+
- Add elements helpers `CfdiUtils\Elements\ImpLocal10\ImpuestosLocales` to work with "ImpuestosLocales"
4+
- Add `CfdiUtils\Certificado\CerRetriever` that works with `CfdiUtils\XmlResolver\XmlResolver` to download
5+
a certificate from the SAT repository
6+
- Add a new validator `CfdiUtils\Validate\Cfdi33\Standard\TimbreFiscalDigitalSello` to validate that the SelloSAT
7+
is actually the signature of the Timbre Fiscal Digital. If not then the CFDI was modified
8+
- Add a new real and valid CFDI to test, this allow `TimbreFiscalDigitalSello` to check real data and pass
9+
- Update test with `cfdi33-valid.xml` to allow fail `TimbreFiscalDigitalSello`
10+
- Travis: Remove xdebug for all but PHP 7.0
11+
112
# Version 2.0.1 2018-01-03
213
- Small bugfixes thanks to scrutinizer-ci.com
314
- Fix some docblocks

docs/TODO.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
# eclipxe/CfdiUtils To Do List
22

33
- [ ] Create validation rules to detect errors on CFDI
4+
- [X] Validate Matriz de errores del CFDI version 3.3
5+
- [X] Validate SelloSAT at TimbreFiscalDigital
6+
- [ ] Validate ComplementoComercioExterior
47
- [ ] Add a pretty command line utility to validate cfdi files
5-
- [ ] Full code coverage on `CfdiUtils\Elements`
8+
- [X] Full code coverage on `CfdiUtils\Elements`
69
- [ ] Implement catalogs published by SAT
710
- Doubt: This catalogs may be on a database or hardcoded ?
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?php
2+
namespace CfdiUtils\Certificado;
3+
4+
use XmlResourceRetriever\AbstractBaseRetriever;
5+
use XmlResourceRetriever\RetrieverInterface;
6+
7+
class CerRetriever extends AbstractBaseRetriever implements RetrieverInterface
8+
{
9+
public function retrieve(string $resource): string
10+
{
11+
$this->clearHistory();
12+
$localFilename = $this->download($resource);
13+
$this->addToHistory($resource, $localFilename);
14+
return $localFilename;
15+
}
16+
17+
protected function checkIsValidDownloadedFile(string $source, string $localpath)
18+
{
19+
// check content is cer file
20+
try {
21+
new Certificado($localpath);
22+
} catch (\Throwable $ex) {
23+
unlink($localpath);
24+
throw new \RuntimeException("The source $source is not a cer file", 0, $ex);
25+
}
26+
}
27+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<?php
2+
namespace CfdiUtils\Certificado;
3+
4+
class SatCertificateNumber
5+
{
6+
/** @var string */
7+
private $id;
8+
9+
public function __construct(string $id)
10+
{
11+
if (! $this->isValidCertificateNumber($id)) {
12+
throw new \UnexpectedValueException('The certificate number is not correct');
13+
}
14+
$this->id = $id;
15+
}
16+
17+
public function number(): string
18+
{
19+
return $this->id;
20+
}
21+
22+
public function remoteUrl(): string
23+
{
24+
return sprintf(
25+
'https://rdc.sat.gob.mx/rccf/%s/%s/%s/%s/%s/%s.cer',
26+
substr($this->id, 0, 6),
27+
substr($this->id, 6, 6),
28+
substr($this->id, 12, 2),
29+
substr($this->id, 14, 2),
30+
substr($this->id, 16, 2),
31+
$this->id
32+
);
33+
}
34+
35+
public static function isValidCertificateNumber(string $id): bool
36+
{
37+
return (bool) preg_match('/^[0-9]{20}$/', $id);
38+
}
39+
}

src/CfdiUtils/Elements/Cfdi33/Retenciones.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,9 @@ public function getElementName(): string
1212

1313
public function addRetencion(array $attributes = []): Retencion
1414
{
15-
$Retencion = new Retencion($attributes);
16-
$this->addChild($Retencion);
17-
return $Retencion;
15+
$retencion = new Retencion($attributes);
16+
$this->addChild($retencion);
17+
return $retencion;
1818
}
1919

2020
public function multiRetencion(array ...$elementAttributes): self
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
<?php
2+
namespace CfdiUtils\Elements\ImpLocal10;
3+
4+
use CfdiUtils\Elements\Common\AbstractElement;
5+
6+
class ImpuestosLocales extends AbstractElement
7+
{
8+
public function addRetencionLocal(array $attributes = []): RetencionesLocales
9+
{
10+
$retencion = new RetencionesLocales($attributes);
11+
$this->addChild($retencion);
12+
return $retencion;
13+
}
14+
15+
public function addTrasladoLocal(array $attributes = []): TrasladosLocales
16+
{
17+
$traslado = new TrasladosLocales($attributes);
18+
$this->addChild($traslado);
19+
return $traslado;
20+
}
21+
22+
public function getElementName(): string
23+
{
24+
return 'implocal:ImpuestosLocales';
25+
}
26+
27+
public function getFixedAttributes(): array
28+
{
29+
return [
30+
'xmlns:implocal' => 'http://www.sat.gob.mx/implocal',
31+
'xsi:schemaLocation' => 'http://www.sat.gob.mx/implocal'
32+
. ' http://www.sat.gob.mx/sitio_internet/cfd/implocal/implocal.xsd',
33+
'Version' => '1.0',
34+
];
35+
}
36+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<?php
2+
namespace CfdiUtils\Elements\ImpLocal10;
3+
4+
use CfdiUtils\Elements\Common\AbstractElement;
5+
6+
class RetencionesLocales extends AbstractElement
7+
{
8+
public function getElementName(): string
9+
{
10+
return 'implocal:RetencionesLocales';
11+
}
12+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<?php
2+
namespace CfdiUtils\Elements\ImpLocal10;
3+
4+
use CfdiUtils\Elements\Common\AbstractElement;
5+
6+
class TrasladosLocales extends AbstractElement
7+
{
8+
public function getElementName(): string
9+
{
10+
return 'implocal:TrasladosLocales';
11+
}
12+
}

src/CfdiUtils/SumasConceptos/SumasConceptos.php

Lines changed: 66 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,14 @@ class SumasConceptos
1919
private $traslados = [];
2020
/** @var array */
2121
private $retenciones = [];
22+
/** @var float */
23+
private $localesImpuestosTrasladados;
24+
/** @var float */
25+
private $localesImpuestosRetenidos;
26+
/** @var array */
27+
private $localesTraslados = [];
28+
/** @var array */
29+
private $localesRetenciones = [];
2230
/** @var int */
2331
private $precision;
2432

@@ -42,6 +50,12 @@ private function addComprobante(NodeInterface $comprobante)
4250
foreach ($conceptos as $concepto) {
4351
$this->addConcepto($concepto);
4452
}
53+
54+
$this->localesTraslados = $this->populateImpuestosLocales($comprobante, 'TrasladosLocales', 'Traslado');
55+
$this->localesImpuestosTrasladados = array_sum(array_column($this->localesTraslados, 'Importe'));
56+
$this->localesRetenciones = $this->populateImpuestosLocales($comprobante, 'RetencionesLocales', 'Retenido');
57+
$this->localesImpuestosRetenidos = array_sum(array_column($this->localesRetenciones, 'Importe'));
58+
4559
$this->traslados = $this->roundImpuestosGroup($this->traslados);
4660
$this->retenciones = $this->roundImpuestosGroup($this->retenciones);
4761
$this->impuestosTrasladados = (float) array_sum(array_column($this->traslados, 'Importe'));
@@ -52,7 +66,14 @@ private function addComprobante(NodeInterface $comprobante)
5266
$this->importes = round($this->importes, $this->precision);
5367
$this->descuento = round($this->descuento, $this->precision);
5468

55-
$this->total = $this->importes - $this->descuento + $this->impuestosTrasladados - $this->impuestosRetenidos;
69+
$this->total = round(array_sum([
70+
$this->importes,
71+
- $this->descuento,
72+
$this->impuestosTrasladados,
73+
- $this->impuestosRetenidos,
74+
$this->localesImpuestosTrasladados,
75+
- $this->localesImpuestosRetenidos,
76+
]), $this->precision);
5677
}
5778

5879
private function addConcepto(NodeInterface $concepto)
@@ -71,6 +92,20 @@ private function addConcepto(NodeInterface $concepto)
7192
}
7293
}
7394

95+
private function populateImpuestosLocales(NodeInterface $comprobante, string $plural, string $singular): array
96+
{
97+
$locales = $comprobante->searchNodes('cfdi:Complemento', 'implocal:ImpuestosLocales', 'implocal:' . $plural);
98+
$list = [];
99+
foreach ($locales as $local) {
100+
$list[] = [
101+
'Impuesto' => $local['ImpLoc' . $singular],
102+
'Tasa' => (float) $local['Tasade' . $singular],
103+
'Importe' => (float) $local['Importe'],
104+
];
105+
}
106+
return $list;
107+
}
108+
74109
private function roundImpuestosGroup(array $group): array
75110
{
76111
foreach (array_keys($group) as $key) {
@@ -167,4 +202,34 @@ public function getPrecision(): int
167202
{
168203
return $this->precision;
169204
}
205+
206+
public function getLocalesImpuestosTrasladados(): float
207+
{
208+
return $this->localesImpuestosTrasladados;
209+
}
210+
211+
public function getLocalesImpuestosRetenidos(): float
212+
{
213+
return $this->localesImpuestosRetenidos;
214+
}
215+
216+
public function getLocalesTraslados(): array
217+
{
218+
return $this->localesTraslados;
219+
}
220+
221+
public function getLocalesRetenciones(): array
222+
{
223+
return $this->localesRetenciones;
224+
}
225+
226+
public function hasLocalesTraslados(): bool
227+
{
228+
return (count($this->localesTraslados) > 0);
229+
}
230+
231+
public function hasLocalesRetenciones(): bool
232+
{
233+
return (count($this->localesRetenciones) > 0);
234+
}
170235
}

src/CfdiUtils/Validate/Assert.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ class Assert
1919
* Assert constructor.
2020
* @param string $code
2121
* @param string $title
22-
* @param Status $status
22+
* @param Status|null $status If null the status will be NONE
2323
* @param string $explanation
2424
*/
2525
public function __construct(string $code, string $title = '', Status $status = null, string $explanation = '')

0 commit comments

Comments
 (0)