Skip to content

Commit b4da4ae

Browse files
authored
Merge pull request #7 from eclipxe13/master
Include a helper object `XmlCancelacionHelper` that simplify working with this library
2 parents b5fa7a2 + 60dc24e commit b4da4ae

7 files changed

+262
-8
lines changed

.scrutinizer.yml

+1-4
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,7 @@ filter:
55

66
build:
77
nodes:
8-
php73:
9-
environment:
10-
php:
11-
version: "7.3"
8+
build:
129
tests:
1310
override:
1411
- php-scrutinizer-run --enable-security-analysis

README.md

+38-4
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,21 @@ composer require phpcfdi/xml-cancelacion
3636

3737
## Ejemplo básico de uso
3838

39+
### Con el objeto de ayuda
40+
41+
```php
42+
<?php
43+
44+
use PhpCfdi\XmlCancelacion\XmlCancelacionHelper;
45+
46+
$solicitudCancelacion = (new XmlCancelacionHelper())
47+
->setNewCredentials('certificado.cer', 'llaveprivada.key', 'contraseña')
48+
->make('11111111-2222-3333-4444-000000000001');
49+
50+
```
51+
52+
### Con un uso detallado
53+
3954
```php
4055
<?php
4156
use PhpCfdi\XmlCancelacion\Capsule;
@@ -96,7 +111,25 @@ La salida esperada es algo como lo siguiente (sin los espacios en blanco que agr
96111
```
97112

98113

99-
## Objetos principales
114+
## Objeto de ayuda
115+
116+
**`XmlCancelacionHelper`** te permite usar la librería rápidamente.
117+
118+
Requiere de un objeto `Credentials` que puede ser insertado en la construcción,
119+
puede ser insertado con el método `setCredentials` o por `setNewCredentials`.
120+
La diferencia entre estos dos métodos es que el primero recibe un objeto, y el segundo
121+
recibe los parámetros de certificado, llave privada y contraseña.
122+
123+
Para crear la solicitud firmada se puede hacer con los métodos `make` para un sólo UUID
124+
o `makeUuids` para varios UUID. Como primer parámetro reciben qué UUID será cancelado y
125+
como segundo parámetro (opcional) un `DateTimeImmutable` o `null`, en ese caso tomará
126+
la fecha y hora del sistema.
127+
128+
Con esta herramienta de ayuda no se especifica el RFC, cuando se fabrica la solicitud firmada
129+
se obtiene el RFC directamente de las propiedades del certificado.
130+
131+
132+
## Objetos de trabajo
100133

101134
**`Capsule`** es un contenedor de información que contiene RFC, Fecha y UUID.
102135

@@ -119,8 +152,9 @@ Las otras dos desventajas están en la forma en que escribe los valores de `X509
119152
Que aunque creo que no son muy relevantes para generar una firma correcta, sí podrían ser importantes,
120153
y motivo de rechazo -o pretexto- en el servicio de cancelación del SAT.
121154

122-
Al momento existe una dependencia fuerte a `eclipxe/cfdiutils`, sin embargo, esta dependencia va a desaparecer porque
123-
se va a crear un nuevo paquete bajo la organización `PhpCfdi` para certificados, llaves privadas y llaves públicas.
155+
A partir de 2019-08-13 con la versión `0.4.0` se eliminó la dependencia a `eclipxe/cfdiutils` y se cambió a la
156+
librería [`phpcfdi/credentials`](https://github.com/phpcfdi/xml-cancelacion), con esta nueva dependencia se trabaja
157+
mucho mejor con los certificados y llaves privadas.
124158

125159

126160
## Compatilibilidad
@@ -158,7 +192,7 @@ and licensed for use under the MIT License (MIT). Please see [LICENSE][] for mor
158192

159193
[badge-source]: http://img.shields.io/badge/source-phpcfdi/xml--cancelacion-blue?style=flat-square
160194
[badge-release]: https://img.shields.io/github/release/phpcfdi/xml-cancelacion?style=flat-square
161-
[badge-license]: https://img.shields.io/badge/license-MIT-brightgreen?style=flat-square
195+
[badge-license]: https://img.shields.io/github/license/phpcfdi/xml-cancelacion?style=flat-square
162196
[badge-build]: https://img.shields.io/travis/phpcfdi/xml-cancelacion/master?style=flat-square
163197
[badge-quality]: https://img.shields.io/scrutinizer/g/phpcfdi/xml-cancelacion/master?style=flat-square
164198
[badge-coverage]: https://img.shields.io/scrutinizer/coverage/g/phpcfdi/xml-cancelacion/master?style=flat-square

docs/CHANGELOG.md

+6
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# CHANGELOG
22

3+
## Version 0.4.2 2019-09-05
4+
5+
- Include a helper object `XmlCancelacionHelper` that simplify working with this library,
6+
see [README](https://github.com/phpcfdi/xml-cancelacion/blob/master/README.md) for usage.
7+
- Other minimal changes on documentation.
8+
39

410
## Version 0.4.1 2019-08-13
511

src/Credentials.php

+5
Original file line numberDiff line numberDiff line change
@@ -89,4 +89,9 @@ protected function getCsd(): Credential
8989
}
9090
return $this->csd;
9191
}
92+
93+
public function rfc(): string
94+
{
95+
return $this->getCsd()->rfc();
96+
}
9297
}

src/XmlCancelacionHelper.php

+71
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace PhpCfdi\XmlCancelacion;
6+
7+
use DateTimeImmutable;
8+
9+
class XmlCancelacionHelper
10+
{
11+
/** @var Credentials|null */
12+
private $credentials;
13+
14+
/**
15+
* XmlCancelacionHelper constructor.
16+
* @param Credentials|null $credentials
17+
*/
18+
public function __construct(?Credentials $credentials = null)
19+
{
20+
$this->credentials = $credentials;
21+
}
22+
23+
public function hasCredentials(): bool
24+
{
25+
return (null !== $this->credentials);
26+
}
27+
28+
public function getCredentials(): Credentials
29+
{
30+
if (null === $this->credentials) {
31+
throw new \LogicException('The object has no credentials');
32+
}
33+
return $this->credentials;
34+
}
35+
36+
public function setCredentials(Credentials $credentials): self
37+
{
38+
$this->credentials = $credentials;
39+
return $this;
40+
}
41+
42+
public function setNewCredentials(string $certificate, string $privateKey, string $passPhrase): self
43+
{
44+
$credentials = new Credentials($certificate, $privateKey, $passPhrase);
45+
return $this->setCredentials($credentials);
46+
}
47+
48+
public function make(string $uuid, ? DateTimeImmutable $dateTime = null): string
49+
{
50+
return $this->makeUuids([$uuid], $dateTime);
51+
}
52+
53+
public function makeUuids(array $uuids, ? DateTimeImmutable $dateTime = null): string
54+
{
55+
$dateTime = $dateTime ?? new DateTimeImmutable();
56+
$credentials = $this->getCredentials();
57+
$capsule = $this->createCapsule($credentials->rfc(), $uuids, $dateTime);
58+
$signer = $this->createCapsuleSigner();
59+
return $signer->sign($capsule, $credentials);
60+
}
61+
62+
protected function createCapsule(string $rfc, array $uuids, DateTimeImmutable $dateTime): Capsule
63+
{
64+
return new Capsule($rfc, $uuids, $dateTime);
65+
}
66+
67+
protected function createCapsuleSigner(): CapsuleSigner
68+
{
69+
return new CapsuleSigner();
70+
}
71+
}

tests/Unit/CredentialsTest.php

+25
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,31 @@
1212

1313
class CredentialsTest extends TestCase
1414
{
15+
public function testValidCredentialsProperties(): void
16+
{
17+
$cerFile = $this->filePath('LAN7008173R5.cer.pem');
18+
$keyFile = $this->filePath('LAN7008173R5.key.pem');
19+
$passPhrase = trim($this->fileContents('LAN7008173R5.password'));
20+
$signature = 'Dw9fnvXKvDCy+oFqGNWG2ho1wcLaY4I9ddh5e+WqB5rfHbZEMyspuqQzYux2OL0U+g7arlx/w5imdQxjBlvrKgulX7'
21+
. 'K7HcHel60knsneDebEJNA0tyeTnJJn2e6DPd5GtxrLEHsjKtTGxl4p8QynX0x5uJoog09ZgIQ3adSq3cciH3FOfupiq9NbtMQ'
22+
. 'k9Da8ezI+pc5L0uu+mC9+RAR+r3agRkigGhGIeatS2QrA/B4FjZW2kzivz7J3zWEMm+JJMdYKzBoc7Us3aOS+kzuaz4T8+/yf'
23+
. 'IZy2qa9QEnxpOvk0Prh43LaObh9MKbu3uOnWaO3yMSuKE6DHZqmWtcO57A==';
24+
25+
$credentials = new Credentials($cerFile, $keyFile, $passPhrase);
26+
27+
$this->assertSame($cerFile, $credentials->certificate());
28+
$this->assertSame($keyFile, $credentials->privateKey());
29+
$this->assertSame($passPhrase, $credentials->passPhrase());
30+
31+
$this->assertSame('LAN7008173R5', $credentials->rfc());
32+
$this->assertSame('20001000000300022815', $credentials->serialNumber());
33+
$this->assertStringStartsWith(
34+
'CN=A.C. 2 de pruebas(4096),O=Servicio de Administración Tributaria',
35+
$credentials->certificateIssuerName()
36+
);
37+
$this->assertSame($signature, base64_encode($credentials->sign('foo')));
38+
}
39+
1540
public function testCreateCsdWithInvalidPassword(): void
1641
{
1742
$cerContent = $this->filePath('LAN7008173R5.cer.pem');
+116
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace PhpCfdi\XmlCancelacion\Tests\Unit;
6+
7+
use DateTimeImmutable;
8+
use LogicException;
9+
use PhpCfdi\XmlCancelacion\Capsule;
10+
use PhpCfdi\XmlCancelacion\Credentials;
11+
use PhpCfdi\XmlCancelacion\Tests\TestCase;
12+
use PhpCfdi\XmlCancelacion\XmlCancelacionHelper;
13+
use PHPUnit\Framework\MockObject\MockObject;
14+
15+
/** @covers \PhpCfdi\XmlCancelacion\XmlCancelacionHelper */
16+
class XmlCancelacionHelperTest extends TestCase
17+
{
18+
/** @return Credentials&MockObject */
19+
private function createFakeCredentials(): Credentials
20+
{
21+
/** @var Credentials&MockObject $credentials */
22+
$credentials = $this->createMock(Credentials::class);
23+
return $credentials;
24+
}
25+
26+
private function createRealCredentials(): Credentials
27+
{
28+
$cerFile = $this->filePath('LAN7008173R5.cer.pem');
29+
$keyFile = $this->filePath('LAN7008173R5.key.pem');
30+
$passPhrase = trim($this->fileContents('LAN7008173R5.password'));
31+
return new Credentials($cerFile, $keyFile, $passPhrase);
32+
}
33+
34+
public function testCredentialChanges(): void
35+
{
36+
$fakeCredentials = $this->createFakeCredentials();
37+
38+
$helper = new XmlCancelacionHelper();
39+
$this->assertFalse($helper->hasCredentials());
40+
41+
$this->assertSame($helper, $helper->setCredentials($fakeCredentials));
42+
$this->assertTrue($helper->hasCredentials());
43+
44+
$cerFile = $this->filePath('LAN7008173R5.cer.pem');
45+
$keyFile = $this->filePath('LAN7008173R5.key.pem');
46+
$passPhrase = trim($this->fileContents('LAN7008173R5.password'));
47+
$this->assertSame($helper, $helper->setNewCredentials($cerFile, $keyFile, $passPhrase));
48+
$this->assertTrue($helper->hasCredentials());
49+
$this->assertSame('LAN7008173R5', $helper->getCredentials()->rfc());
50+
}
51+
52+
public function testMakeCallsMakeUuids(): void
53+
{
54+
$uuid = '11111111-2222-3333-4444-000000000001';
55+
$predefinedReturn = 'signed-xml';
56+
57+
/** @var XmlCancelacionHelper&MockObject $helper */
58+
$helper = $this->getMockBuilder(XmlCancelacionHelper::class)
59+
->onlyMethods(['makeUuids'])
60+
->getMock();
61+
$helper->expects($this->once())
62+
->method('makeUuids')
63+
->with($this->equalTo([$uuid]), $this->isNull())
64+
->willReturn($predefinedReturn);
65+
66+
$this->assertSame($predefinedReturn, $helper->make($uuid));
67+
}
68+
69+
public function testMakeUuids(): void
70+
{
71+
$credentials = $this->createRealCredentials();
72+
$rfc = $credentials->rfc();
73+
$uuids = ['11111111-2222-3333-4444-000000000001', '11111111-2222-3333-4444-000000000002'];
74+
$helper = new class() extends XmlCancelacionHelper {
75+
/** @var Capsule */
76+
private $capsule;
77+
78+
protected function createCapsule(string $rfc, array $uuids, DateTimeImmutable $dateTime): Capsule
79+
{
80+
$this->capsule = parent::createCapsule($rfc, $uuids, $dateTime);
81+
return $this->capsule;
82+
}
83+
84+
public function getCreatedCapsule(): Capsule
85+
{
86+
return $this->capsule;
87+
}
88+
};
89+
90+
$now = new DateTimeImmutable();
91+
$result = $helper->setCredentials($credentials)->makeUuids($uuids);
92+
$this->assertStringContainsString('<Signature xmlns="http://www.w3.org/2000/09/xmldsig#">', $result);
93+
94+
/** @var Capsule $spyCapsule */
95+
$spyCapsule = $helper->getCreatedCapsule();
96+
$spyDate = $spyCapsule->date();
97+
$this->assertSame($rfc, $spyCapsule->rfc());
98+
$this->assertSame($uuids, $spyCapsule->uuids());
99+
$this->assertTrue($spyDate > $now->modify('-1 second') && $spyDate < $now->modify('+1 second'));
100+
}
101+
102+
public function testGetCredentialsWithoutSettingBefore(): void
103+
{
104+
$helper = new XmlCancelacionHelper();
105+
$this->expectException(LogicException::class);
106+
$this->expectExceptionMessage('The object has no credentials');
107+
$helper->getCredentials();
108+
}
109+
110+
public function testCanConstructWithCredentials(): void
111+
{
112+
$credentials = $this->createFakeCredentials();
113+
$helper = new XmlCancelacionHelper($credentials);
114+
$this->assertSame($credentials, $helper->getCredentials());
115+
}
116+
}

0 commit comments

Comments
 (0)