From eccbcb1e92ad9855fc4ff3f26bf56b98f835a457 Mon Sep 17 00:00:00 2001 From: Giancarlos Salas Date: Sat, 7 Jan 2023 12:37:22 -0500 Subject: [PATCH 01/41] Add gre-api package --- composer.json | 1 + packages/ws/README.md | 2 +- packages/ws/composer.json | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 3dd7a8d3..0faefb7e 100644 --- a/composer.json +++ b/composer.json @@ -22,6 +22,7 @@ "require": { "php": ">=7.4", "bacon/bacon-qr-code": "^2.0", + "greenter/gre-api": "^0.1", "greenter/xmldsig": "^5.0", "mikehaertl/phpwkhtmltopdf": "^2.4", "nelexa/zip": "^4.0", diff --git a/packages/ws/README.md b/packages/ws/README.md index 3312e3ba..4d8a72ea 100644 --- a/packages/ws/README.md +++ b/packages/ws/README.md @@ -2,7 +2,7 @@ [![src greenter](https://greenter.dev/img/greenter_badge.svg)](https://github.com/thegreenter/greenter) -Conexión con los webservices de SUNAT, para el envío de comprabantes electrónicos. +Conexión con los webservices de SUNAT, para el envío de comprobantes electrónicos. ## Recursos - [Documentación](https://greenter.dev/packages/ws/) diff --git a/packages/ws/composer.json b/packages/ws/composer.json index f4ab1627..ee378625 100644 --- a/packages/ws/composer.json +++ b/packages/ws/composer.json @@ -20,6 +20,7 @@ "ext-dom": "*", "ext-soap": "*", "greenter/core": "^4.4", + "greenter/gre-api": "^0.1", "nelexa/zip": "^4.0" }, "require-dev": { From ba4ba8891389c6b557c67f603023a5440a6fae2a Mon Sep 17 00:00:00 2001 From: Giancarlos Salas Date: Tue, 10 Jan 2023 09:51:59 -0500 Subject: [PATCH 02/41] Add GRE sender --- packages/ws/src/Api/GreSender.php | 107 ++++++++++++++++++++++++++++++ 1 file changed, 107 insertions(+) create mode 100644 packages/ws/src/Api/GreSender.php diff --git a/packages/ws/src/Api/GreSender.php b/packages/ws/src/Api/GreSender.php new file mode 100644 index 00000000..74bd8955 --- /dev/null +++ b/packages/ws/src/Api/GreSender.php @@ -0,0 +1,107 @@ +api = $api; + } + + public function send(?string $filename, ?string $content): ?BaseResult + { + $result = new SummaryResult(); + try { + $zipContent = $this->compress($filename.'.xml', $content); + $document = (new CpeDocument()) + ->setArchivo((new CpeDocumentArchivo()) + ->setNomArchivo($filename.'.zip') + ->setArcGreZip(base64_encode($zipContent)) + ->setHashZip(hash('sha256', $zipContent)) + ); + $response = $this->api->enviarCpe($filename, $document); + $result + ->setTicket($response->getNumTicket()) + ->setSuccess(true); + } catch (ApiException $e) { + // TODO: parse error by code + $result->setError(new Error("API", $e->getMessage())); + } + + return $result; + } + + public function status(?string $ticket): StatusResult { + $result = new StatusResult(); + try { + $response = $this->api->consultarEnvio($ticket); + + return $this->processResponse($response); + } catch (ApiException $e) { + $result->setError(new Error("API", $e->getMessage())); + } + + return $result; + } + + /** + * @param StatusResponse $status + * @return StatusResult + */ + private function processResponse(StatusResponse $status): StatusResult + { + $code = $status->getCodRespuesta(); + + $result = new StatusResult(); + $result->setCode($code); + + $isPending = $code === '98'; + if ($isPending) { + $result->setError(new Error($code, 'En proceso')); + + return $result; + } + + $isCompleted = $code === '0' || $code === '99'; + if ($isCompleted) { + if ($status->getError()) { + $result->setError( + new Error( + $status->getError()->getNumError(), + $status->getError()->getDesError() + ) + ); + } + + if ($status->getIndCdrGenerado()) { + $cdrZip = base64_decode($status->getArcCdr()); + $result + ->setSuccess(true) + ->setCdrResponse($this->extractResponse((string)$cdrZip)) + ->setCdrZip($cdrZip); + } + } + + return $result; + } +} \ No newline at end of file From f6713ad204ac5f0f09858bfce6f5c4422018d95b Mon Sep 17 00:00:00 2001 From: Giancarlos Salas Date: Tue, 10 Jan 2023 10:32:10 -0500 Subject: [PATCH 03/41] Add token store --- .../core/src/Core/Services/Api/BasicToken.php | 57 +++++++++++++++++++ .../Core/Services/Api/TokenStoreInterface.php | 12 ++++ 2 files changed, 69 insertions(+) create mode 100644 packages/core/src/Core/Services/Api/BasicToken.php create mode 100644 packages/core/src/Core/Services/Api/TokenStoreInterface.php diff --git a/packages/core/src/Core/Services/Api/BasicToken.php b/packages/core/src/Core/Services/Api/BasicToken.php new file mode 100644 index 00000000..e3266415 --- /dev/null +++ b/packages/core/src/Core/Services/Api/BasicToken.php @@ -0,0 +1,57 @@ +value = $value; + $this->expire = $expire; + } + + /** + * @return string|null + */ + public function getValue(): ?string + { + return $this->value; + } + + /** + * @param string|null $value + * @return BasicToken + */ + public function setValue(?string $value): BasicToken + { + $this->value = $value; + return $this; + } + + /** + * @return DateTimeInterface|null + */ + public function getExpire(): ?DateTimeInterface + { + return $this->expire; + } + + /** + * @param DateTimeInterface|null $expire + * @return BasicToken + */ + public function setExpire(?DateTimeInterface $expire): BasicToken + { + $this->expire = $expire; + return $this; + } +} \ No newline at end of file diff --git a/packages/core/src/Core/Services/Api/TokenStoreInterface.php b/packages/core/src/Core/Services/Api/TokenStoreInterface.php new file mode 100644 index 00000000..cd0c910c --- /dev/null +++ b/packages/core/src/Core/Services/Api/TokenStoreInterface.php @@ -0,0 +1,12 @@ + Date: Tue, 10 Jan 2023 10:32:22 -0500 Subject: [PATCH 04/41] Add InMemory token store --- packages/ws/src/Api/InMemoryStore.php | 28 +++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 packages/ws/src/Api/InMemoryStore.php diff --git a/packages/ws/src/Api/InMemoryStore.php b/packages/ws/src/Api/InMemoryStore.php new file mode 100644 index 00000000..2fb33664 --- /dev/null +++ b/packages/ws/src/Api/InMemoryStore.php @@ -0,0 +1,28 @@ +tokens)) { + return $this->tokens[$id]; + } + + return null; + } + + function set(?string $id, ?BasicToken $token) + { + $this->tokens[$id] = $token; + } +} \ No newline at end of file From 86150e14b1b9ae1b0a2936812386402cc929231a Mon Sep 17 00:00:00 2001 From: Giancarlos Salas Date: Tue, 10 Jan 2023 11:53:33 -0500 Subject: [PATCH 05/41] Add api factory --- packages/ws/src/Api/ApiFactory.php | 73 ++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 packages/ws/src/Api/ApiFactory.php diff --git a/packages/ws/src/Api/ApiFactory.php b/packages/ws/src/Api/ApiFactory.php new file mode 100644 index 00000000..f5ef4028 --- /dev/null +++ b/packages/ws/src/Api/ApiFactory.php @@ -0,0 +1,73 @@ +api = $api; + $this->client = $client; + $this->store = $store; + } + + /** + * @throws ApiException + * @throws Exception + */ + public function create(?string $client_id, ?string $secret, ?string $user, ?string $password): CpeApi + { + $token = ''; + $tokenData = $this->store->get($client_id); + if ($tokenData && $tokenData->getExpire() > $this->addSeconds(new DateTime(), 600)) { + $token = $tokenData->getValue(); + } else { + $result = $this->api->getToken( + 'password', + 'https://api-cpe.sunat.gob.pe', + $client_id, + $secret, + $user, + $password); + + $token = $result->getAccessToken(); + $expire = $this->addSeconds(new DateTime(), $result->getExpiresIn()); + $this->store->set($client_id, new BasicToken($token, $expire)); + } + + $config = Configuration::getDefaultConfiguration() + ->setAccessToken($token); + + return new CpeApi( + $this->client, + $config->setHost($config->getHostFromSettings(1)) + ); + } + + /** + * @throws Exception + */ + function addSeconds(DateTime $time, int $seconds): DateTime { + return $time->add(new DateInterval('PT'.$seconds.'S')); + } +} \ No newline at end of file From d711691c19a09a218351726322523d882be2e9ca Mon Sep 17 00:00:00 2001 From: Giancarlos Salas Date: Tue, 10 Jan 2023 12:26:19 -0500 Subject: [PATCH 06/41] Add See API --- packages/lite/src/Greenter/Api.php | 146 +++++++++++++++++++++++++++++ 1 file changed, 146 insertions(+) create mode 100644 packages/lite/src/Greenter/Api.php diff --git a/packages/lite/src/Greenter/Api.php b/packages/lite/src/Greenter/Api.php new file mode 100644 index 00000000..95a6f285 --- /dev/null +++ b/packages/lite/src/Greenter/Api.php @@ -0,0 +1,146 @@ + false]; + + /** + * @param ApiFactory|null $factory + * @param SignedXml|null $signer + */ + public function __construct(?ApiFactory $factory, ?SignedXml $signer) + { + $this->factory = $factory ?? $this->createApiFactory(); + $this->signer = $signer ?? new SignedXml(); + } + + /** + * Set Xml Builder Options. + * + * @param array $options + * + * @return Api + */ + public function setBuilderOptions(array $options): Api + { + $this->options = array_merge($this->options, $options); + + return $this; + } + + /** + * @param string $client_id + * @param string $secret + * + * @return Api + */ + public function setApiCredentials(string $client_id, string $secret): Api + { + $this->credentials['client_id'] = $client_id; + $this->credentials['client_secret'] = $secret; + + return $this; + } + + /** + * Set Clave SOL de usuario secundario. + * + * @param string $ruc + * @param string $user + * @param string $password + * + * @return Api + */ + public function setClaveSOL(string $ruc, string $user, string $password): Api + { + $this->credentials['username'] = $ruc.$user; + $this->credentials['password'] = $password; + + return $this; + } + + /** + * @param string $certificate + * + * @return Api + */ + public function setCertificate(string $certificate): Api + { + $this->signer->setCertificate($certificate); + + return $this; + } + + /** + * Envia comprobante. + * + * @param DocumentInterface $document + * + * @return BaseResult|null + * @throws ApiException + */ + public function send(DocumentInterface $document): ?BaseResult + { + $buildResolver = new XmlBuilderResolver($this->options); + $builder = $buildResolver->find(get_class($document)); + $sender = $this->createSender(); + + $xml = $builder->build($document); + $xmlSigned = $this->signer->signXml($xml); + return $sender->send($document->getName(), $xmlSigned); + } + + /** + * Consultar el estado del envio. + * + * @param string|null $ticket + * @return StatusResult + * @throws ApiException + */ + public function getStatus(?string $ticket): StatusResult { + $sender = $this->createSender(); + + return $sender->status($ticket); + } + + /** + * @throws ApiException + */ + private function createSender(): GreSender { + $api = $this->factory->create( + $this->credentials['client_id'], + $this->credentials['client_secret'], + $this->credentials['username'], + $this->credentials['password'] + ); + + return new GreSender($api); + } + + private function createApiFactory(): ApiFactory { + $client = new Client(); + return new ApiFactory(new AuthApi($client), $client, new InMemoryStore()); + } +} \ No newline at end of file From fbf09bbf5cc554872739df9c33a3e06a91556ae6 Mon Sep 17 00:00:00 2001 From: Giancarlos Salas Date: Tue, 10 Jan 2023 17:11:09 -0500 Subject: [PATCH 07/41] Update gre-api package --- composer.json | 2 +- packages/ws/composer.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index 0faefb7e..7c8b9e26 100644 --- a/composer.json +++ b/composer.json @@ -22,7 +22,7 @@ "require": { "php": ">=7.4", "bacon/bacon-qr-code": "^2.0", - "greenter/gre-api": "^0.1", + "greenter/gre-api": "^1.0", "greenter/xmldsig": "^5.0", "mikehaertl/phpwkhtmltopdf": "^2.4", "nelexa/zip": "^4.0", diff --git a/packages/ws/composer.json b/packages/ws/composer.json index ee378625..2ae21b22 100644 --- a/packages/ws/composer.json +++ b/packages/ws/composer.json @@ -20,7 +20,7 @@ "ext-dom": "*", "ext-soap": "*", "greenter/core": "^4.4", - "greenter/gre-api": "^0.1", + "greenter/gre-api": "^1.0", "nelexa/zip": "^4.0" }, "require-dev": { From df5ef3b497c72104b4da4b1f6eeb9d463e078d26 Mon Sep 17 00:00:00 2001 From: Giancarlos Salas Date: Tue, 10 Jan 2023 17:20:25 -0500 Subject: [PATCH 08/41] Parse error --- packages/ws/src/Api/GreSender.php | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/packages/ws/src/Api/GreSender.php b/packages/ws/src/Api/GreSender.php index 74bd8955..c63c1839 100644 --- a/packages/ws/src/Api/GreSender.php +++ b/packages/ws/src/Api/GreSender.php @@ -44,8 +44,7 @@ public function send(?string $filename, ?string $content): ?BaseResult ->setTicket($response->getNumTicket()) ->setSuccess(true); } catch (ApiException $e) { - // TODO: parse error by code - $result->setError(new Error("API", $e->getMessage())); + $result->setError($this->processException($e)); } return $result; @@ -58,7 +57,7 @@ public function status(?string $ticket): StatusResult { return $this->processResponse($response); } catch (ApiException $e) { - $result->setError(new Error("API", $e->getMessage())); + $result->setError($this->processException($e)); } return $result; @@ -104,4 +103,21 @@ private function processResponse(StatusResponse $status): StatusResult return $result; } + + private function processException(ApiException $e): Error { + switch ($e->getCode()) { + case 422: + /**@var $resp \Greenter\Sunat\GRE\Model\CpeErrorValidation */ + $resp = $e->getResponseObject(); + foreach ($resp->getErrors() as $err) { + return new Error($err->getCod(), $err->getMsg()); + } + case 500: + /**@var $resp \Greenter\Sunat\GRE\Model\CpeError */ + $resp = $e->getResponseObject(); + return new Error($resp->getCod(), $resp->getMsg()); + } + + return new Error("API", $e->getMessage()); + } } \ No newline at end of file From 68b9ac7593415ec901c9f23e83f2f1e1ce39bb5a Mon Sep 17 00:00:00 2001 From: Giancarlos Salas Date: Tue, 10 Jan 2023 17:24:17 -0500 Subject: [PATCH 09/41] Use auth ApiInterface --- packages/ws/src/Api/ApiFactory.php | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/packages/ws/src/Api/ApiFactory.php b/packages/ws/src/Api/ApiFactory.php index f5ef4028..c2e4fb28 100644 --- a/packages/ws/src/Api/ApiFactory.php +++ b/packages/ws/src/Api/ApiFactory.php @@ -7,24 +7,24 @@ use Exception; use Greenter\Services\Api\BasicToken; use Greenter\Services\Api\TokenStoreInterface; -use Greenter\Sunat\GRE\Api\AuthApi; +use Greenter\Sunat\GRE\Api\AuthApiInterface; use Greenter\Sunat\GRE\Api\CpeApi; use Greenter\Sunat\GRE\ApiException; use Greenter\Sunat\GRE\Configuration; -use GuzzleHttp\Client; +use GuzzleHttp\ClientInterface; class ApiFactory { - private AuthApi $api; - private Client $client; + private AuthApiInterface $api; + private ClientInterface $client; private TokenStoreInterface $store; /** - * @param AuthApi $api - * @param Client $client + * @param AuthApiInterface $api + * @param ClientInterface $client * @param TokenStoreInterface $store */ - public function __construct(AuthApi $api, Client $client, TokenStoreInterface $store) + public function __construct(AuthApiInterface $api, ClientInterface $client, TokenStoreInterface $store) { $this->api = $api; $this->client = $client; @@ -37,7 +37,6 @@ public function __construct(AuthApi $api, Client $client, TokenStoreInterface $s */ public function create(?string $client_id, ?string $secret, ?string $user, ?string $password): CpeApi { - $token = ''; $tokenData = $this->store->get($client_id); if ($tokenData && $tokenData->getExpire() > $this->addSeconds(new DateTime(), 600)) { $token = $tokenData->getValue(); From 0c2db3e834e71f3e60a6da6655b66ad4f8a46582 Mon Sep 17 00:00:00 2001 From: Giancarlos Salas Date: Tue, 10 Jan 2023 17:24:46 -0500 Subject: [PATCH 10/41] Use cpe ApiInterface --- packages/ws/src/Api/GreSender.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/ws/src/Api/GreSender.php b/packages/ws/src/Api/GreSender.php index c63c1839..720708fe 100644 --- a/packages/ws/src/Api/GreSender.php +++ b/packages/ws/src/Api/GreSender.php @@ -10,6 +10,7 @@ use Greenter\Model\Response\SummaryResult; use Greenter\Services\SenderInterface; use Greenter\Sunat\GRE\Api\CpeApi; +use Greenter\Sunat\GRE\Api\CpeApiInterface; use Greenter\Sunat\GRE\ApiException; use Greenter\Sunat\GRE\Model\CpeDocument; use Greenter\Sunat\GRE\Model\CpeDocumentArchivo; @@ -18,12 +19,12 @@ class GreSender extends BaseSunat implements SenderInterface { - private CpeApi $api; + private CpeApiInterface $api; /** - * @param CpeApi $api + * @param CpeApiInterface $api */ - public function __construct(CpeApi $api) + public function __construct(CpeApiInterface $api) { $this->api = $api; } From 8b2ddab90ba841087b05c1387d83aa3ed2ec9691 Mon Sep 17 00:00:00 2001 From: Giancarlos Salas Date: Tue, 10 Jan 2023 17:33:11 -0500 Subject: [PATCH 11/41] Add cpe endpoint option --- packages/lite/src/Greenter/Api.php | 2 +- packages/ws/src/Api/ApiFactory.php | 11 +++++++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/packages/lite/src/Greenter/Api.php b/packages/lite/src/Greenter/Api.php index 95a6f285..9c23b078 100644 --- a/packages/lite/src/Greenter/Api.php +++ b/packages/lite/src/Greenter/Api.php @@ -141,6 +141,6 @@ private function createSender(): GreSender { private function createApiFactory(): ApiFactory { $client = new Client(); - return new ApiFactory(new AuthApi($client), $client, new InMemoryStore()); + return new ApiFactory(new AuthApi($client), $client, new InMemoryStore(), null); } } \ No newline at end of file diff --git a/packages/ws/src/Api/ApiFactory.php b/packages/ws/src/Api/ApiFactory.php index c2e4fb28..b0244ac1 100644 --- a/packages/ws/src/Api/ApiFactory.php +++ b/packages/ws/src/Api/ApiFactory.php @@ -18,17 +18,24 @@ class ApiFactory private AuthApiInterface $api; private ClientInterface $client; private TokenStoreInterface $store; + private ?string $cpeEndpoint; /** * @param AuthApiInterface $api * @param ClientInterface $client * @param TokenStoreInterface $store + * @param string|null $endpoint */ - public function __construct(AuthApiInterface $api, ClientInterface $client, TokenStoreInterface $store) + public function __construct( + AuthApiInterface $api, + ClientInterface $client, + TokenStoreInterface $store, + ?string $endpoint) { $this->api = $api; $this->client = $client; $this->store = $store; + $this->cpeEndpoint = $endpoint; } /** @@ -59,7 +66,7 @@ public function create(?string $client_id, ?string $secret, ?string $user, ?stri return new CpeApi( $this->client, - $config->setHost($config->getHostFromSettings(1)) + $config->setHost($this->cpeEndpoint ?? $config->getHostFromSettings(1)) ); } From 1b3ea10ad2c74b9976d889568c191e30dfcca852 Mon Sep 17 00:00:00 2001 From: Giancarlos Salas Date: Tue, 10 Jan 2023 17:36:56 -0500 Subject: [PATCH 12/41] Add allow custom endpoints --- packages/lite/src/Greenter/Api.php | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/packages/lite/src/Greenter/Api.php b/packages/lite/src/Greenter/Api.php index 9c23b078..5012af81 100644 --- a/packages/lite/src/Greenter/Api.php +++ b/packages/lite/src/Greenter/Api.php @@ -11,6 +11,7 @@ use Greenter\Model\Response\StatusResult; use Greenter\Sunat\GRE\Api\AuthApi; use Greenter\Sunat\GRE\ApiException; +use Greenter\Sunat\GRE\Configuration; use Greenter\XMLSecLibs\Sunat\SignedXml; use GuzzleHttp\Client; @@ -20,6 +21,10 @@ class Api private ?SignedXml $signer = null; private array $credentials = []; + private array $endpoints = [ + 'api' => 'https://api-seguridad.sunat.gob.pe/v1', + 'cpe' => 'https://api.sunat.gob.pe/v1', + ]; /** * Twig Render Options. @@ -50,6 +55,18 @@ public function setBuilderOptions(array $options): Api return $this; } + /** + * @param array $endpoints + * + * @return Api + */ + public function setEndpoint(array $endpoints): Api + { + $this->endpoints = $endpoints; + + return $this; + } + /** * @param string $client_id * @param string $secret @@ -141,6 +158,13 @@ private function createSender(): GreSender { private function createApiFactory(): ApiFactory { $client = new Client(); - return new ApiFactory(new AuthApi($client), $client, new InMemoryStore(), null); + $config = Configuration::getDefaultConfiguration(); + + return new ApiFactory( + new AuthApi($client, $config->setHost($this->endpoints['api'])), + $client, + new InMemoryStore(), + $this->endpoints['cpe'], + ); } } \ No newline at end of file From 50d2451afd51f3d7dc1e1d580b25799b5704403f Mon Sep 17 00:00:00 2001 From: Giancarlos Salas Date: Tue, 10 Jan 2023 20:16:53 -0500 Subject: [PATCH 13/41] Fix "Explicitly mention the visibility" --- .../Core/Services/Api/TokenStoreInterface.php | 17 +++++++++++++++-- packages/ws/src/Api/InMemoryStore.php | 4 ++-- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/packages/core/src/Core/Services/Api/TokenStoreInterface.php b/packages/core/src/Core/Services/Api/TokenStoreInterface.php index cd0c910c..caf7015c 100644 --- a/packages/core/src/Core/Services/Api/TokenStoreInterface.php +++ b/packages/core/src/Core/Services/Api/TokenStoreInterface.php @@ -6,7 +6,20 @@ interface TokenStoreInterface { - function get(?string $id): ?BasicToken; + /** + * Get Token by id. + * + * @param string|null $id + * @return BasicToken|null + */ + public function get(?string $id): ?BasicToken; - function set(?string $id, ?BasicToken $token); + /** + * Save token. + * + * @param string|null $id + * @param BasicToken|null $token + * @return mixed + */ + public function set(?string $id, BasicToken $token); } diff --git a/packages/ws/src/Api/InMemoryStore.php b/packages/ws/src/Api/InMemoryStore.php index 2fb33664..8e125077 100644 --- a/packages/ws/src/Api/InMemoryStore.php +++ b/packages/ws/src/Api/InMemoryStore.php @@ -12,7 +12,7 @@ class InMemoryStore implements TokenStoreInterface */ private array $tokens = []; - function get(?string $id): ?BasicToken + public function get(?string $id): ?BasicToken { if (array_key_exists($id, $this->tokens)) { return $this->tokens[$id]; @@ -21,7 +21,7 @@ function get(?string $id): ?BasicToken return null; } - function set(?string $id, ?BasicToken $token) + public function set(?string $id, BasicToken $token) { $this->tokens[$id] = $token; } From f6ef77a2a753f1aa37478d44d96c14359ea2a27e Mon Sep 17 00:00:00 2001 From: Giancarlos Salas Date: Tue, 10 Jan 2023 20:17:38 -0500 Subject: [PATCH 14/41] Fix sonarqube issues --- packages/ws/src/Api/ApiFactory.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ws/src/Api/ApiFactory.php b/packages/ws/src/Api/ApiFactory.php index b0244ac1..ede89a1b 100644 --- a/packages/ws/src/Api/ApiFactory.php +++ b/packages/ws/src/Api/ApiFactory.php @@ -73,7 +73,7 @@ public function create(?string $client_id, ?string $secret, ?string $user, ?stri /** * @throws Exception */ - function addSeconds(DateTime $time, int $seconds): DateTime { + private function addSeconds(DateTime $time, int $seconds): DateTime { return $time->add(new DateInterval('PT'.$seconds.'S')); } } \ No newline at end of file From 0f9a182042fdb57b566dfd8ca0644383e900a113 Mon Sep 17 00:00:00 2001 From: Giancarlos Salas Date: Tue, 10 Jan 2023 20:20:04 -0500 Subject: [PATCH 15/41] Remove zip ext --- .github/workflows/php.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml index 7de83c06..79261be0 100644 --- a/.github/workflows/php.yml +++ b/.github/workflows/php.yml @@ -24,8 +24,7 @@ jobs: uses: shivammathur/setup-php@v2 with: php-version: ${{ matrix.php }} - # zip only for linter - extensions: mbstring, soap, :fileinfo, zip + extensions: mbstring, soap, :fileinfo - name: Validate composer.json run: composer validate From 1da49e315286ed7d19c88d10ba4968cd14a8b128 Mon Sep 17 00:00:00 2001 From: Giancarlos Salas Date: Tue, 10 Jan 2023 20:20:28 -0500 Subject: [PATCH 16/41] Add curl ext --- .github/workflows/php.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml index 79261be0..395d5beb 100644 --- a/.github/workflows/php.yml +++ b/.github/workflows/php.yml @@ -24,7 +24,7 @@ jobs: uses: shivammathur/setup-php@v2 with: php-version: ${{ matrix.php }} - extensions: mbstring, soap, :fileinfo + extensions: mbstring, curl, soap, :fileinfo - name: Validate composer.json run: composer validate From 936d1e46ad5a9f7374aaee6e7439a83388d85290 Mon Sep 17 00:00:00 2001 From: Giancarlos Salas Date: Tue, 10 Jan 2023 20:21:35 -0500 Subject: [PATCH 17/41] Update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 19553d62..7472576f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ Los cambios notables de cada lanzamiento serán documentados en este archivo. ## Unreleased - #206 Agregar xml para nueva Guia de Remisión. - #209 Cambiar versión mínima de PHP a `7.4` +- #213 Agregar GRE cliente REST. ## 4.3.4 - 2022-11-21 From 9f0724343ad5471397af90e060aed149e16c7f44 Mon Sep 17 00:00:00 2001 From: Giancarlos Salas Date: Tue, 10 Jan 2023 20:23:53 -0500 Subject: [PATCH 18/41] Update curl required --- packages/ws/composer.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/ws/composer.json b/packages/ws/composer.json index 2ae21b22..288ceefd 100644 --- a/packages/ws/composer.json +++ b/packages/ws/composer.json @@ -37,6 +37,9 @@ "Tests\\Greenter\\": "tests/" } }, + "suggest": { + "ext-curl": "Necesario para enviar guia de remision" + }, "extra": { "branch-alias": { "dev-master": "4.4-dev" From b7283112ad7c4f988d2ee0a39916d64431e3329a Mon Sep 17 00:00:00 2001 From: Giancarlos Salas Date: Tue, 10 Jan 2023 20:24:22 -0500 Subject: [PATCH 19/41] Update requirements --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c76c0823..89569824 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ Puede ver una demostración en [@greenter/demo](https://github.com/thegreenter/d ## Requerimientos - PHP `7.4` o superior -- Extensiones PHP Activadas: `soap`, `zlib`, `openssl`. +- Extensiones PHP: `soap`, `zlib`, `openssl`, `curl`. ## Documentación - Lee esta [guia](https://fe-primer.greenter.dev/) para conocer mas sobre facturación electrónica. From 161b14a9230617396788b51ddbb5bb351f85c4ce Mon Sep 17 00:00:00 2001 From: Giancarlos Salas Date: Tue, 10 Jan 2023 20:27:34 -0500 Subject: [PATCH 20/41] Fix phpstan issues --- composer.json | 2 +- packages/core/src/Core/Services/Api/TokenStoreInterface.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index 7c8b9e26..7c561875 100644 --- a/composer.json +++ b/composer.json @@ -32,7 +32,7 @@ "require-dev": { "greenter/ubl-validator": "^2.0", "mockery/mockery": "^1.2", - "phpstan/phpstan": "^1.8", + "phpstan/phpstan": "^1.9", "phpunit/phpunit": "^8", "vimeo/psalm": "^4.4" }, diff --git a/packages/core/src/Core/Services/Api/TokenStoreInterface.php b/packages/core/src/Core/Services/Api/TokenStoreInterface.php index caf7015c..18bcae26 100644 --- a/packages/core/src/Core/Services/Api/TokenStoreInterface.php +++ b/packages/core/src/Core/Services/Api/TokenStoreInterface.php @@ -18,7 +18,7 @@ public function get(?string $id): ?BasicToken; * Save token. * * @param string|null $id - * @param BasicToken|null $token + * @param BasicToken $token * @return mixed */ public function set(?string $id, BasicToken $token); From 052c1d3ad98a0efbb7698f8ba152108619d26e98 Mon Sep 17 00:00:00 2001 From: Giancarlos Salas Date: Tue, 10 Jan 2023 20:36:35 -0500 Subject: [PATCH 21/41] Update phpunit v9 --- composer.json | 2 +- packages/cpe-validator/composer.json | 2 +- packages/htmltopdf/composer.json | 2 +- packages/lite/composer.json | 2 +- packages/report/composer.json | 2 +- packages/validator/composer.json | 2 +- packages/ws/composer.json | 2 +- packages/xcodes/composer.json | 2 +- packages/xml/composer.json | 2 +- 9 files changed, 9 insertions(+), 9 deletions(-) diff --git a/composer.json b/composer.json index 7c561875..b2306c4a 100644 --- a/composer.json +++ b/composer.json @@ -33,7 +33,7 @@ "greenter/ubl-validator": "^2.0", "mockery/mockery": "^1.2", "phpstan/phpstan": "^1.9", - "phpunit/phpunit": "^8", + "phpunit/phpunit": "^9", "vimeo/psalm": "^4.4" }, "suggest": { diff --git a/packages/cpe-validator/composer.json b/packages/cpe-validator/composer.json index 5b1ee772..c7d1402f 100644 --- a/packages/cpe-validator/composer.json +++ b/packages/cpe-validator/composer.json @@ -20,7 +20,7 @@ "ext-xsl": "*" }, "require-dev": { - "phpunit/phpunit": "^8" + "phpunit/phpunit": "^9" }, "autoload": { "psr-4": { diff --git a/packages/htmltopdf/composer.json b/packages/htmltopdf/composer.json index 7ec8f460..c82ce32a 100644 --- a/packages/htmltopdf/composer.json +++ b/packages/htmltopdf/composer.json @@ -20,7 +20,7 @@ "mikehaertl/phpwkhtmltopdf": "^2.4" }, "require-dev": { - "phpunit/phpunit": "^8" + "phpunit/phpunit": "^9" }, "autoload": { "psr-4": { diff --git a/packages/lite/composer.json b/packages/lite/composer.json index c004d235..791fd2cd 100644 --- a/packages/lite/composer.json +++ b/packages/lite/composer.json @@ -23,7 +23,7 @@ "greenter/xml": "^4.4" }, "require-dev": { - "phpunit/phpunit": "^8" + "phpunit/phpunit": "^9" }, "autoload": { "psr-4": { diff --git a/packages/report/composer.json b/packages/report/composer.json index d8218262..84cba6b2 100644 --- a/packages/report/composer.json +++ b/packages/report/composer.json @@ -21,7 +21,7 @@ "bacon/bacon-qr-code": "^2.0" }, "require-dev": { - "phpunit/phpunit": "^8", + "phpunit/phpunit": "^9", "greenter/data": "^4.4" }, "autoload": { diff --git a/packages/validator/composer.json b/packages/validator/composer.json index c64aeb7d..e65caf45 100644 --- a/packages/validator/composer.json +++ b/packages/validator/composer.json @@ -20,7 +20,7 @@ "symfony/validator": "^5.0 || ^6.0" }, "require-dev": { - "phpunit/phpunit": "^8" + "phpunit/phpunit": "^9" }, "autoload": { "psr-4": { diff --git a/packages/ws/composer.json b/packages/ws/composer.json index 288ceefd..bd7086ab 100644 --- a/packages/ws/composer.json +++ b/packages/ws/composer.json @@ -24,7 +24,7 @@ "nelexa/zip": "^4.0" }, "require-dev": { - "phpunit/phpunit": "^8", + "phpunit/phpunit": "^9", "mockery/mockery": "^1.2" }, "autoload": { diff --git a/packages/xcodes/composer.json b/packages/xcodes/composer.json index ef1f634f..4738164c 100644 --- a/packages/xcodes/composer.json +++ b/packages/xcodes/composer.json @@ -19,7 +19,7 @@ "greenter/core": "^4.4" }, "require-dev": { - "phpunit/phpunit": "^8" + "phpunit/phpunit": "^9" }, "autoload": { "psr-4": { diff --git a/packages/xml/composer.json b/packages/xml/composer.json index fd81da23..14ffcdee 100644 --- a/packages/xml/composer.json +++ b/packages/xml/composer.json @@ -22,7 +22,7 @@ "require-dev": { "greenter/data": "^4.4", "greenter/ubl-validator": "^2.0", - "phpunit/phpunit": "^8" + "phpunit/phpunit": "^9" }, "autoload": { "psr-4": { From 0fb33b387ac87fe997adfefc9bc1fa128a750bff Mon Sep 17 00:00:00 2001 From: Giancarlos Salas Date: Tue, 10 Jan 2023 20:39:30 -0500 Subject: [PATCH 22/41] Upgrade psalm v5 --- composer.json | 2 +- packages/core/src/Core/Services/Api/TokenStoreInterface.php | 4 ++-- packages/report/src/Report/XmlUtils.php | 4 ++-- packages/ws/src/Api/InMemoryStore.php | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/composer.json b/composer.json index b2306c4a..0554d5f3 100644 --- a/composer.json +++ b/composer.json @@ -34,7 +34,7 @@ "mockery/mockery": "^1.2", "phpstan/phpstan": "^1.9", "phpunit/phpunit": "^9", - "vimeo/psalm": "^4.4" + "vimeo/psalm": "^5.4" }, "suggest": { "ext-dom": "For xml, xml-parser, cpe-validator, ws package", diff --git a/packages/core/src/Core/Services/Api/TokenStoreInterface.php b/packages/core/src/Core/Services/Api/TokenStoreInterface.php index 18bcae26..cfb53790 100644 --- a/packages/core/src/Core/Services/Api/TokenStoreInterface.php +++ b/packages/core/src/Core/Services/Api/TokenStoreInterface.php @@ -19,7 +19,7 @@ public function get(?string $id): ?BasicToken; * * @param string|null $id * @param BasicToken $token - * @return mixed + * @return void */ - public function set(?string $id, BasicToken $token); + public function set(?string $id, BasicToken $token): void; } diff --git a/packages/report/src/Report/XmlUtils.php b/packages/report/src/Report/XmlUtils.php index 09e970bd..05d317b5 100644 --- a/packages/report/src/Report/XmlUtils.php +++ b/packages/report/src/Report/XmlUtils.php @@ -81,9 +81,9 @@ private function getXpath(DOMDocument $document) /** * @param DOMNodeList $exts * @param DOMXPath $xpt - * @return string + * @return string|null */ - private function getHash(DOMNodeList $exts, DOMXPath $xpt) + private function getHash(DOMNodeList $exts, DOMXPath $xpt): ?string { for ($i = $exts->length; $i-- > 0;) { $nodeSign = $exts->item($i); diff --git a/packages/ws/src/Api/InMemoryStore.php b/packages/ws/src/Api/InMemoryStore.php index 8e125077..a55f9ffc 100644 --- a/packages/ws/src/Api/InMemoryStore.php +++ b/packages/ws/src/Api/InMemoryStore.php @@ -21,7 +21,7 @@ public function get(?string $id): ?BasicToken return null; } - public function set(?string $id, BasicToken $token) + public function set(?string $id, BasicToken $token): void { $this->tokens[$id] = $token; } From 6af18a99b515a6fad833ec6981308ab13469f368 Mon Sep 17 00:00:00 2001 From: Giancarlos Salas Date: Tue, 10 Jan 2023 20:39:55 -0500 Subject: [PATCH 23/41] Sync packages --- composer.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 0554d5f3..4b3427bd 100644 --- a/composer.json +++ b/composer.json @@ -40,7 +40,8 @@ "ext-dom": "For xml, xml-parser, cpe-validator, ws package", "ext-soap": "For ws package", "ext-zlib": "For ws package", - "ext-xsl": "For cpe-validator package" + "ext-xsl": "For cpe-validator package", + "ext-curl": "For ws package (GRE)" }, "autoload": { "psr-4": { From b6103c23ea71dd5e6389d83869cbb8b187b3ac65 Mon Sep 17 00:00:00 2001 From: Giancarlos Salas Date: Tue, 10 Jan 2023 20:42:47 -0500 Subject: [PATCH 24/41] Set strict types --- packages/core/src/Core/Services/Api/BasicToken.php | 4 +++- packages/lite/src/Greenter/Api.php | 4 +++- packages/ws/src/Api/ApiFactory.php | 4 +++- packages/ws/src/Api/GreSender.php | 2 +- packages/ws/src/Api/InMemoryStore.php | 4 +++- 5 files changed, 13 insertions(+), 5 deletions(-) diff --git a/packages/core/src/Core/Services/Api/BasicToken.php b/packages/core/src/Core/Services/Api/BasicToken.php index e3266415..7d2a0447 100644 --- a/packages/core/src/Core/Services/Api/BasicToken.php +++ b/packages/core/src/Core/Services/Api/BasicToken.php @@ -1,5 +1,7 @@ expire = $expire; return $this; } -} \ No newline at end of file +} diff --git a/packages/lite/src/Greenter/Api.php b/packages/lite/src/Greenter/Api.php index 5012af81..10019fd7 100644 --- a/packages/lite/src/Greenter/Api.php +++ b/packages/lite/src/Greenter/Api.php @@ -1,5 +1,7 @@ endpoints['cpe'], ); } -} \ No newline at end of file +} diff --git a/packages/ws/src/Api/ApiFactory.php b/packages/ws/src/Api/ApiFactory.php index ede89a1b..79920128 100644 --- a/packages/ws/src/Api/ApiFactory.php +++ b/packages/ws/src/Api/ApiFactory.php @@ -1,5 +1,7 @@ add(new DateInterval('PT'.$seconds.'S')); } -} \ No newline at end of file +} diff --git a/packages/ws/src/Api/GreSender.php b/packages/ws/src/Api/GreSender.php index 720708fe..ccb4c767 100644 --- a/packages/ws/src/Api/GreSender.php +++ b/packages/ws/src/Api/GreSender.php @@ -121,4 +121,4 @@ private function processException(ApiException $e): Error { return new Error("API", $e->getMessage()); } -} \ No newline at end of file +} diff --git a/packages/ws/src/Api/InMemoryStore.php b/packages/ws/src/Api/InMemoryStore.php index a55f9ffc..bd95e959 100644 --- a/packages/ws/src/Api/InMemoryStore.php +++ b/packages/ws/src/Api/InMemoryStore.php @@ -1,5 +1,7 @@ tokens[$id] = $token; } -} \ No newline at end of file +} From c3476f33db11ff1a4442c627a9f46b6276fdcfc9 Mon Sep 17 00:00:00 2001 From: Giancarlos Salas Date: Tue, 10 Jan 2023 20:48:39 -0500 Subject: [PATCH 25/41] Fix code smells --- packages/lite/src/Greenter/Api.php | 15 +++++++++------ packages/ws/src/Api/ApiFactory.php | 17 ++++++++++------- packages/ws/src/Api/GreSender.php | 28 ++++++++++++++-------------- 3 files changed, 33 insertions(+), 27 deletions(-) diff --git a/packages/lite/src/Greenter/Api.php b/packages/lite/src/Greenter/Api.php index 10019fd7..7518a8cf 100644 --- a/packages/lite/src/Greenter/Api.php +++ b/packages/lite/src/Greenter/Api.php @@ -70,14 +70,14 @@ public function setEndpoint(array $endpoints): Api } /** - * @param string $client_id + * @param string $clientId * @param string $secret * * @return Api */ - public function setApiCredentials(string $client_id, string $secret): Api + public function setApiCredentials(string $clientId, string $secret): Api { - $this->credentials['client_id'] = $client_id; + $this->credentials['client_id'] = $clientId; $this->credentials['client_secret'] = $secret; return $this; @@ -138,7 +138,8 @@ public function send(DocumentInterface $document): ?BaseResult * @return StatusResult * @throws ApiException */ - public function getStatus(?string $ticket): StatusResult { + public function getStatus(?string $ticket): StatusResult + { $sender = $this->createSender(); return $sender->status($ticket); @@ -147,7 +148,8 @@ public function getStatus(?string $ticket): StatusResult { /** * @throws ApiException */ - private function createSender(): GreSender { + private function createSender(): GreSender + { $api = $this->factory->create( $this->credentials['client_id'], $this->credentials['client_secret'], @@ -158,7 +160,8 @@ private function createSender(): GreSender { return new GreSender($api); } - private function createApiFactory(): ApiFactory { + private function createApiFactory(): ApiFactory + { $client = new Client(); $config = Configuration::getDefaultConfiguration(); diff --git a/packages/ws/src/Api/ApiFactory.php b/packages/ws/src/Api/ApiFactory.php index 79920128..bdeb3191 100644 --- a/packages/ws/src/Api/ApiFactory.php +++ b/packages/ws/src/Api/ApiFactory.php @@ -32,7 +32,8 @@ public function __construct( AuthApiInterface $api, ClientInterface $client, TokenStoreInterface $store, - ?string $endpoint) + ?string $endpoint + ) { $this->api = $api; $this->client = $client; @@ -44,23 +45,24 @@ public function __construct( * @throws ApiException * @throws Exception */ - public function create(?string $client_id, ?string $secret, ?string $user, ?string $password): CpeApi + public function create(?string $clientId, ?string $secret, ?string $user, ?string $password): CpeApi { - $tokenData = $this->store->get($client_id); + $tokenData = $this->store->get($clientId); if ($tokenData && $tokenData->getExpire() > $this->addSeconds(new DateTime(), 600)) { $token = $tokenData->getValue(); } else { $result = $this->api->getToken( 'password', 'https://api-cpe.sunat.gob.pe', - $client_id, + $clientId, $secret, $user, - $password); + $password + ); $token = $result->getAccessToken(); $expire = $this->addSeconds(new DateTime(), $result->getExpiresIn()); - $this->store->set($client_id, new BasicToken($token, $expire)); + $this->store->set($clientId, new BasicToken($token, $expire)); } $config = Configuration::getDefaultConfiguration() @@ -75,7 +77,8 @@ public function create(?string $client_id, ?string $secret, ?string $user, ?stri /** * @throws Exception */ - private function addSeconds(DateTime $time, int $seconds): DateTime { + private function addSeconds(DateTime $time, int $seconds): DateTime + { return $time->add(new DateInterval('PT'.$seconds.'S')); } } diff --git a/packages/ws/src/Api/GreSender.php b/packages/ws/src/Api/GreSender.php index ccb4c767..91150181 100644 --- a/packages/ws/src/Api/GreSender.php +++ b/packages/ws/src/Api/GreSender.php @@ -9,7 +9,6 @@ use Greenter\Model\Response\StatusResult; use Greenter\Model\Response\SummaryResult; use Greenter\Services\SenderInterface; -use Greenter\Sunat\GRE\Api\CpeApi; use Greenter\Sunat\GRE\Api\CpeApiInterface; use Greenter\Sunat\GRE\ApiException; use Greenter\Sunat\GRE\Model\CpeDocument; @@ -51,7 +50,8 @@ public function send(?string $filename, ?string $content): ?BaseResult return $result; } - public function status(?string $ticket): StatusResult { + public function status(?string $ticket): StatusResult + { $result = new StatusResult(); try { $response = $this->api->consultarEnvio($ticket); @@ -105,18 +105,18 @@ private function processResponse(StatusResponse $status): StatusResult return $result; } - private function processException(ApiException $e): Error { - switch ($e->getCode()) { - case 422: - /**@var $resp \Greenter\Sunat\GRE\Model\CpeErrorValidation */ - $resp = $e->getResponseObject(); - foreach ($resp->getErrors() as $err) { - return new Error($err->getCod(), $err->getMsg()); - } - case 500: - /**@var $resp \Greenter\Sunat\GRE\Model\CpeError */ - $resp = $e->getResponseObject(); - return new Error($resp->getCod(), $resp->getMsg()); + private function processException(ApiException $e): Error + { + if ($e->getCode() === 422) { + /**@var $resp \Greenter\Sunat\GRE\Model\CpeErrorValidation */ + $resp = $e->getResponseObject(); + foreach ($resp->getErrors() as $err) { + return new Error($err->getCod(), $err->getMsg()); + } + } elseif ($e->getCode() === 500) { + /**@var $resp \Greenter\Sunat\GRE\Model\CpeError */ + $resp = $e->getResponseObject(); + return new Error($resp->getCod(), $resp->getMsg()); } return new Error("API", $e->getMessage()); From 3d0086d5dfc2e81a7183ea67c072a7c7b2d46f0c Mon Sep 17 00:00:00 2001 From: Giancarlos Salas Date: Tue, 10 Jan 2023 20:54:41 -0500 Subject: [PATCH 26/41] Update codacy warns --- packages/ws/src/Api/ApiFactory.php | 46 ++++++++++++++++++------------ packages/ws/src/Api/GreSender.php | 12 ++++---- 2 files changed, 34 insertions(+), 24 deletions(-) diff --git a/packages/ws/src/Api/ApiFactory.php b/packages/ws/src/Api/ApiFactory.php index bdeb3191..c4d6f36e 100644 --- a/packages/ws/src/Api/ApiFactory.php +++ b/packages/ws/src/Api/ApiFactory.php @@ -47,25 +47,9 @@ public function __construct( */ public function create(?string $clientId, ?string $secret, ?string $user, ?string $password): CpeApi { - $tokenData = $this->store->get($clientId); - if ($tokenData && $tokenData->getExpire() > $this->addSeconds(new DateTime(), 600)) { - $token = $tokenData->getValue(); - } else { - $result = $this->api->getToken( - 'password', - 'https://api-cpe.sunat.gob.pe', - $clientId, - $secret, - $user, - $password - ); + $token = $this->getToken($clientId, $secret, $user, $password); - $token = $result->getAccessToken(); - $expire = $this->addSeconds(new DateTime(), $result->getExpiresIn()); - $this->store->set($clientId, new BasicToken($token, $expire)); - } - - $config = Configuration::getDefaultConfiguration() + $config = (new Configuration()) ->setAccessToken($token); return new CpeApi( @@ -74,6 +58,32 @@ public function create(?string $clientId, ?string $secret, ?string $user, ?strin ); } + /** + * @throws Exception + */ + private function getToken(?string $clientId, ?string $secret, ?string $user, ?string $password): ?string + { + $tokenData = $this->store->get($clientId); + if ($tokenData && $tokenData->getExpire() > $this->addSeconds(new DateTime(), 600)) { + return $tokenData->getValue(); + } + + $result = $this->api->getToken( + 'password', + 'https://api-cpe.sunat.gob.pe', + $clientId, + $secret, + $user, + $password + ); + + $token = $result->getAccessToken(); + $expire = $this->addSeconds(new DateTime(), $result->getExpiresIn()); + $this->store->set($clientId, new BasicToken($token, $expire)); + + return $token; + } + /** * @throws Exception */ diff --git a/packages/ws/src/Api/GreSender.php b/packages/ws/src/Api/GreSender.php index 91150181..c9811587 100644 --- a/packages/ws/src/Api/GreSender.php +++ b/packages/ws/src/Api/GreSender.php @@ -105,20 +105,20 @@ private function processResponse(StatusResponse $status): StatusResult return $result; } - private function processException(ApiException $e): Error + private function processException(ApiException $ex): Error { - if ($e->getCode() === 422) { + if ($ex->getCode() === 422) { /**@var $resp \Greenter\Sunat\GRE\Model\CpeErrorValidation */ - $resp = $e->getResponseObject(); + $resp = $ex->getResponseObject(); foreach ($resp->getErrors() as $err) { return new Error($err->getCod(), $err->getMsg()); } - } elseif ($e->getCode() === 500) { + } elseif ($ex->getCode() === 500) { /**@var $resp \Greenter\Sunat\GRE\Model\CpeError */ - $resp = $e->getResponseObject(); + $resp = $ex->getResponseObject(); return new Error($resp->getCod(), $resp->getMsg()); } - return new Error("API", $e->getMessage()); + return new Error("API", $ex->getMessage()); } } From 353fe95b0f34f525543751afc9f2242ed6fff6b3 Mon Sep 17 00:00:00 2001 From: Giancarlos Salas Date: Tue, 10 Jan 2023 20:59:44 -0500 Subject: [PATCH 27/41] Update return type --- packages/ws/src/Api/ApiFactory.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/ws/src/Api/ApiFactory.php b/packages/ws/src/Api/ApiFactory.php index c4d6f36e..982469b2 100644 --- a/packages/ws/src/Api/ApiFactory.php +++ b/packages/ws/src/Api/ApiFactory.php @@ -11,6 +11,7 @@ use Greenter\Services\Api\TokenStoreInterface; use Greenter\Sunat\GRE\Api\AuthApiInterface; use Greenter\Sunat\GRE\Api\CpeApi; +use Greenter\Sunat\GRE\Api\CpeApiInterface; use Greenter\Sunat\GRE\ApiException; use Greenter\Sunat\GRE\Configuration; use GuzzleHttp\ClientInterface; @@ -45,7 +46,7 @@ public function __construct( * @throws ApiException * @throws Exception */ - public function create(?string $clientId, ?string $secret, ?string $user, ?string $password): CpeApi + public function create(?string $clientId, ?string $secret, ?string $user, ?string $password): CpeApiInterface { $token = $this->getToken($clientId, $secret, $user, $password); From dbb5224e878c9a887bd52c2ffe29d44e300b15c4 Mon Sep 17 00:00:00 2001 From: Giancarlos Salas Date: Tue, 10 Jan 2023 21:21:55 -0500 Subject: [PATCH 28/41] Update phpunit xsd --- packages/cpe-validator/phpunit.xml.dist | 48 +++++++++++----------- packages/htmltopdf/phpunit.xml.dist | 48 +++++++++++----------- packages/lite/phpunit.xml.dist | 48 +++++++++++----------- packages/report/phpunit.xml.dist | 48 +++++++++++----------- packages/validator/phpunit.xml.dist | 48 +++++++++++----------- packages/ws/phpunit.xml.dist | 54 ++++++++++++------------- packages/xcodes/phpunit.xml.dist | 48 +++++++++++----------- packages/xml-parser/phpunit.xml.dist | 48 +++++++++++----------- packages/xml/phpunit.xml.dist | 48 +++++++++++----------- phpunit.xml.dist | 38 ++++++++--------- 10 files changed, 226 insertions(+), 250 deletions(-) diff --git a/packages/cpe-validator/phpunit.xml.dist b/packages/cpe-validator/phpunit.xml.dist index 4cf00867..43e91e73 100644 --- a/packages/cpe-validator/phpunit.xml.dist +++ b/packages/cpe-validator/phpunit.xml.dist @@ -1,25 +1,23 @@ - - - - - tests - - - - - src - - ./tests - ./vendor - - - - - - - - \ No newline at end of file + + + + + src + + + ./tests + ./vendor + + + + + + + + tests + + + + + + diff --git a/packages/htmltopdf/phpunit.xml.dist b/packages/htmltopdf/phpunit.xml.dist index 5d57d93b..43e91e73 100644 --- a/packages/htmltopdf/phpunit.xml.dist +++ b/packages/htmltopdf/phpunit.xml.dist @@ -1,25 +1,23 @@ - - - - - tests - - - - - src - - ./tests - ./vendor - - - - - - - - \ No newline at end of file + + + + + src + + + ./tests + ./vendor + + + + + + + + tests + + + + + + diff --git a/packages/lite/phpunit.xml.dist b/packages/lite/phpunit.xml.dist index 4cf00867..43e91e73 100644 --- a/packages/lite/phpunit.xml.dist +++ b/packages/lite/phpunit.xml.dist @@ -1,25 +1,23 @@ - - - - - tests - - - - - src - - ./tests - ./vendor - - - - - - - - \ No newline at end of file + + + + + src + + + ./tests + ./vendor + + + + + + + + tests + + + + + + diff --git a/packages/report/phpunit.xml.dist b/packages/report/phpunit.xml.dist index 4cf00867..43e91e73 100644 --- a/packages/report/phpunit.xml.dist +++ b/packages/report/phpunit.xml.dist @@ -1,25 +1,23 @@ - - - - - tests - - - - - src - - ./tests - ./vendor - - - - - - - - \ No newline at end of file + + + + + src + + + ./tests + ./vendor + + + + + + + + tests + + + + + + diff --git a/packages/validator/phpunit.xml.dist b/packages/validator/phpunit.xml.dist index 4cf00867..43e91e73 100644 --- a/packages/validator/phpunit.xml.dist +++ b/packages/validator/phpunit.xml.dist @@ -1,25 +1,23 @@ - - - - - tests - - - - - src - - ./tests - ./vendor - - - - - - - - \ No newline at end of file + + + + + src + + + ./tests + ./vendor + + + + + + + + tests + + + + + + diff --git a/packages/ws/phpunit.xml.dist b/packages/ws/phpunit.xml.dist index f3422fea..beb3b2c4 100644 --- a/packages/ws/phpunit.xml.dist +++ b/packages/ws/phpunit.xml.dist @@ -1,28 +1,26 @@ - - - - - tests - - - - - src - - ./tests - ./vendor - - - - - - - - - - - \ No newline at end of file + + + + + src + + + ./tests + ./vendor + + + + + + + + tests + + + + + + + + + diff --git a/packages/xcodes/phpunit.xml.dist b/packages/xcodes/phpunit.xml.dist index 4cf00867..43e91e73 100644 --- a/packages/xcodes/phpunit.xml.dist +++ b/packages/xcodes/phpunit.xml.dist @@ -1,25 +1,23 @@ - - - - - tests - - - - - src - - ./tests - ./vendor - - - - - - - - \ No newline at end of file + + + + + src + + + ./tests + ./vendor + + + + + + + + tests + + + + + + diff --git a/packages/xml-parser/phpunit.xml.dist b/packages/xml-parser/phpunit.xml.dist index 4cf00867..43e91e73 100644 --- a/packages/xml-parser/phpunit.xml.dist +++ b/packages/xml-parser/phpunit.xml.dist @@ -1,25 +1,23 @@ - - - - - tests - - - - - src - - ./tests - ./vendor - - - - - - - - \ No newline at end of file + + + + + src + + + ./tests + ./vendor + + + + + + + + tests + + + + + + diff --git a/packages/xml/phpunit.xml.dist b/packages/xml/phpunit.xml.dist index 4cf00867..43e91e73 100644 --- a/packages/xml/phpunit.xml.dist +++ b/packages/xml/phpunit.xml.dist @@ -1,25 +1,23 @@ - - - - - tests - - - - - src - - ./tests - ./vendor - - - - - - - - \ No newline at end of file + + + + + src + + + ./tests + ./vendor + + + + + + + + tests + + + + + + diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 6b9c13f1..4b9bcedd 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,22 +1,16 @@ - - - - packages/**/tests - - - - api - - - - - packages/*/src - - - \ No newline at end of file + + + + + packages/*/src + + + + packages/**/tests + + + + api + + + From 19c0ec65d76a65c47e685b5d0d7a640e9d593460 Mon Sep 17 00:00:00 2001 From: Giancarlos Salas Date: Tue, 10 Jan 2023 21:35:42 -0500 Subject: [PATCH 29/41] Add factory tests --- packages/ws/tests/Ws/Api/ApiFactoryTest.php | 52 +++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 packages/ws/tests/Ws/Api/ApiFactoryTest.php diff --git a/packages/ws/tests/Ws/Api/ApiFactoryTest.php b/packages/ws/tests/Ws/Api/ApiFactoryTest.php new file mode 100644 index 00000000..0b3de063 --- /dev/null +++ b/packages/ws/tests/Ws/Api/ApiFactoryTest.php @@ -0,0 +1,52 @@ +deps(); + $factory = new ApiFactory($auth, $client, $store, null); + $api = $factory->create('x', 'x', '20123456780MODDATOS', 'moddatos'); + $token = $store->get('x'); + + $this->assertInstanceOf(CpeApi::class, $api); + $this->assertNotNull($token); + + $factory->create('x', 'x', '20123456780MODDATOS', 'moddatos'); + $token2 = $store->get('x'); // no changes + + $this->assertEquals($token, $token2); + } + + private function deps(): array + { + $auth = Mockery::mock(AuthApiInterface::class); + $auth->shouldReceive('getToken') + ->once() + ->andReturn(new ApiToken([ + 'access_token' => 'xxxx.xxxx.xxxx', + 'token_type' => 'bearer', + 'expires_in' => 3600, + ])); + + $client = Mockery::mock(ClientInterface::class); + return [$auth, $client, new InMemoryStore()]; + } + + public function tearDown(): void + { + Mockery::close(); + } +} \ No newline at end of file From f29963b98b359fa404e06cedf8206b0a2e9c6b91 Mon Sep 17 00:00:00 2001 From: Giancarlos Salas Date: Tue, 10 Jan 2023 22:00:48 -0500 Subject: [PATCH 30/41] Fix validate cdr exists --- packages/ws/src/Api/GreSender.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ws/src/Api/GreSender.php b/packages/ws/src/Api/GreSender.php index c9811587..4c58e3d3 100644 --- a/packages/ws/src/Api/GreSender.php +++ b/packages/ws/src/Api/GreSender.php @@ -93,7 +93,7 @@ private function processResponse(StatusResponse $status): StatusResult ); } - if ($status->getIndCdrGenerado()) { + if ($status->getIndCdrGenerado() === '1') { $cdrZip = base64_decode($status->getArcCdr()); $result ->setSuccess(true) From d8b6b297ed699cdd6ffa2e16f4bf24c9438f62f5 Mon Sep 17 00:00:00 2001 From: Giancarlos Salas Date: Tue, 10 Jan 2023 22:12:18 -0500 Subject: [PATCH 31/41] Add GRE sender tests --- packages/ws/tests/Resources/cdrGRE.zip | Bin 0 -> 963 bytes packages/ws/tests/Ws/Api/GreSenderTest.php | 131 +++++++++++++++++++++ 2 files changed, 131 insertions(+) create mode 100644 packages/ws/tests/Resources/cdrGRE.zip create mode 100644 packages/ws/tests/Ws/Api/GreSenderTest.php diff --git a/packages/ws/tests/Resources/cdrGRE.zip b/packages/ws/tests/Resources/cdrGRE.zip new file mode 100644 index 0000000000000000000000000000000000000000..3d9673ab3aa690bf5302eda0a01e910eabeb67b5 GIT binary patch literal 963 zcmWIWW@Zs#U|`^2xX_~&cIBQ`2s<+aLjorQgEUYyNY}^!1`Kr#EOkQ+3=DM*^(u06 zmWIXV&o&ULy|?5q`|7FROb#?I@r^h&#cXa!=Bi~|?(R0^b7Y!*YnkWw*X2Js)Kp$h z)AKKQxA*zH-;HxMC;rnpf4N0yF2}wEE%6w`EhmH4)u?*Zm2SIa^b>8v)3q1J@Py{-|NNINX4D{9bQw; zdmd9LK9{j6C*-H=j9;Zy^^2AMyQR#0u-Rgoj@qn)+8t>z+NX0p4}YH+*KBR;a>DFa zY|D1(vpbkRD+Vd~Kd~{;O5nDu{bRt|`0w)9ezSPaYZ>lL?dq~VbDay0K9X59)5+ZX zgZO0C5_A3-Y5%!)w|3nV@#@z3>08pvzwO?!4blw#4-;PU?(K+r+%34R?cR(CiT-KV zG735mYee(%0-x^R`!i z^_m2?RR>2#hiN4ImVWc$(4PA$24B8!m>qlR)n4T*Hy-X4zmo8q)r9kz=bVg5+jwVY z`o7R&wsdpdx^~j!l7rKHjQq;4*yz4%`t0{wDo6W8L`~-J-3%XI_O8xsYbFJD`p zfBYWnk{qEKrHM94MYngFtN-V3uFrm#aK}I5d9=Z)Nz?yNd^k^Q@%&AZl8)U)cDMCU zXJiU0FOaoNscrF#xX>*ZSN*>3gt=0Z<8M~!HLtDHyH2UBS2@f#C4G;GV&i8K#ki2& z0qK95!dO)iQP1|Sf{`&RQtFUCI-JPd)M;?57W{v8?4VRCE zvFdD>?Y_{K{U`N70ORXfEl0IojM(2rI^4Q6MVL`?qG9E<`(Z9?=RLjp?8o|$)RH&i zHCy(SJ)dO5c>Cq?d#zU^_XP9pJGE=Rxx1b8zt*)!nEK`OvJ1Of_R w5^sLuf+}KUkYKRab#QS}y~%j!*|g*+APwUOc(byBOkxDWTp;ZL%&H6w0Dc3X8UO$Q literal 0 HcmV?d00001 diff --git a/packages/ws/tests/Ws/Api/GreSenderTest.php b/packages/ws/tests/Ws/Api/GreSenderTest.php new file mode 100644 index 00000000..cf187b88 --- /dev/null +++ b/packages/ws/tests/Ws/Api/GreSenderTest.php @@ -0,0 +1,131 @@ +shouldReceive('enviarCpe') + ->andReturn(new CpeResponse(['num_ticket' => 'a.b.c', 'fec_recepcion' => '2023-01-10T20:10:50'])); + $sender = new GreSender($api); + + $nameXml = '20600055519-01-F001-00000001'; + $xml = file_get_contents(__DIR__."/../../Resources/$nameXml.xml"); + /**@var $result SummaryResult */ + $result = $sender->send($nameXml, $xml); + + $this->assertTrue($result->isSuccess()); + $this->assertEquals('a.b.c', $result->getTicket()); + } + + public function testSendInValid(): void + { + $api = Mockery::mock(CpeApiInterface::class); + $api->shouldReceive('enviarCpe') + ->andThrowExceptions([ + $this->createException(422, new CpeErrorValidation([ + 'cod' => '422', + 'msg' => 'Unprocessable Entity', + 'errors' => [new CpeError(['cod' => '501', 'msg' => 'El valor de codCpe no permitido o no valido'])] + ])), + $this->createException(500, new CpeError(['cod' => '500', 'msg' => 'Internal Server Error'])), + $this->createException(400, new CpeError()) + ]); + $sender = new GreSender($api); + + $nameXml = '20600055519-01-F001-00000001'; + $xml = file_get_contents(__DIR__."/../../Resources/$nameXml.xml"); + /**@var $result SummaryResult */ + $result = $sender->send($nameXml, $xml); + + $this->assertFalse($result->isSuccess()); + $this->assertEquals('501', $result->getError()->getCode()); + + /**@var $result SummaryResult */ + $result = $sender->send($nameXml, $xml); + + $this->assertFalse($result->isSuccess()); + $this->assertEquals('500', $result->getError()->getCode()); + + /**@var $result SummaryResult */ + $result = $sender->send($nameXml, $xml); + + $this->assertFalse($result->isSuccess()); + $this->assertEquals('API', $result->getError()->getCode()); + } + + public function testStatusCompleted(): void + { + $api = Mockery::mock(CpeApiInterface::class); + $api->shouldReceive('consultarEnvio') + ->andReturn( + new StatusResponse([ + 'cod_respuesta' => '0', + 'arc_cdr' => base64_encode(file_get_contents(__DIR__."/../../Resources/cdrGRE.zip")), + 'ind_cdr_generado' => '1', + ]) + ); + $sender = new GreSender($api); + $result = $sender->status('a.b.c'); + $cdr = $result->getCdrResponse(); + $this->assertTrue($result->isSuccess()); + $this->assertNotEmpty($result->getCdrZip()); + $this->assertStringStartsWith('https://e-factura.sunat.gob.pe/', $cdr->getReference()); + $this->assertEquals(1, count($cdr->getNotes())); + $this->assertEquals('T001-1', $cdr->getId()); + $this->assertEquals('0', $cdr->getCode()); + } + + public function testStatusPending(): void + { + $api = Mockery::mock(CpeApiInterface::class); + $api->shouldReceive('consultarEnvio') + ->andReturn( + new StatusResponse([ + 'cod_respuesta' => '98', + 'ind_cdr_generado' => '0', + ]), + new StatusResponse([ + 'cod_respuesta' => '99', + 'error' => new StatusResponseError([ + 'num_error' => '1345', + 'des_error' => 'El RUC no valido' + ]), + 'ind_cdr_generado' => '0', + ]) + ); + $sender = new GreSender($api); + $result = $sender->status('a.b.c'); + + $this->assertFalse($result->isSuccess()); + $this->assertEquals('98', $result->getCode()); + + $result = $sender->status('a.b.c'); + + $this->assertFalse($result->isSuccess()); + $this->assertEquals('99', $result->getCode()); + $this->assertEquals('1345', $result->getError()->getCode()); + } + + private function createException(int $code, object $data): ApiException + { + $ex = new ApiException('TEST ERROR', $code); + $ex->setResponseObject($data); + + return $ex; + } +} \ No newline at end of file From dc299b2f21d64c0f7ce84825c3c146ff8522c5f4 Mon Sep 17 00:00:00 2001 From: Giancarlos Salas Date: Tue, 10 Jan 2023 22:16:14 -0500 Subject: [PATCH 32/41] Fix endpoints init --- packages/lite/src/Greenter/Api.php | 25 +++++++------------------ 1 file changed, 7 insertions(+), 18 deletions(-) diff --git a/packages/lite/src/Greenter/Api.php b/packages/lite/src/Greenter/Api.php index 7518a8cf..109236c4 100644 --- a/packages/lite/src/Greenter/Api.php +++ b/packages/lite/src/Greenter/Api.php @@ -23,7 +23,7 @@ class Api private ?SignedXml $signer = null; private array $credentials = []; - private array $endpoints = [ + private array $defaaultEndpoints = [ 'api' => 'https://api-seguridad.sunat.gob.pe/v1', 'cpe' => 'https://api.sunat.gob.pe/v1', ]; @@ -34,12 +34,13 @@ class Api private array $options = ['autoescape' => false]; /** + * @param array|null $endpoints * @param ApiFactory|null $factory * @param SignedXml|null $signer */ - public function __construct(?ApiFactory $factory, ?SignedXml $signer) + public function __construct(?array $endpoints, ?ApiFactory $factory, ?SignedXml $signer) { - $this->factory = $factory ?? $this->createApiFactory(); + $this->factory = $factory ?? $this->createApiFactory($endpoints ?? $this->defaaultEndpoints); $this->signer = $signer ?? new SignedXml(); } @@ -57,18 +58,6 @@ public function setBuilderOptions(array $options): Api return $this; } - /** - * @param array $endpoints - * - * @return Api - */ - public function setEndpoint(array $endpoints): Api - { - $this->endpoints = $endpoints; - - return $this; - } - /** * @param string $clientId * @param string $secret @@ -160,16 +149,16 @@ private function createSender(): GreSender return new GreSender($api); } - private function createApiFactory(): ApiFactory + private function createApiFactory(array $endpoints): ApiFactory { $client = new Client(); $config = Configuration::getDefaultConfiguration(); return new ApiFactory( - new AuthApi($client, $config->setHost($this->endpoints['api'])), + new AuthApi($client, $config->setHost($endpoints['api'])), $client, new InMemoryStore(), - $this->endpoints['cpe'], + $endpoints['cpe'], ); } } From 7dbb83116d43e07ff2d4bf33642fa94985b78ad5 Mon Sep 17 00:00:00 2001 From: Giancarlos Salas Date: Tue, 10 Jan 2023 22:17:23 -0500 Subject: [PATCH 33/41] Set strict types --- packages/ws/tests/Ws/Api/ApiFactoryTest.php | 5 +++-- packages/ws/tests/Ws/Api/GreSenderTest.php | 4 +++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/packages/ws/tests/Ws/Api/ApiFactoryTest.php b/packages/ws/tests/Ws/Api/ApiFactoryTest.php index 0b3de063..37daebba 100644 --- a/packages/ws/tests/Ws/Api/ApiFactoryTest.php +++ b/packages/ws/tests/Ws/Api/ApiFactoryTest.php @@ -1,10 +1,11 @@ Date: Wed, 11 Jan 2023 09:13:10 -0500 Subject: [PATCH 34/41] Fix codacy warn --- packages/lite/src/Greenter/Api.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/lite/src/Greenter/Api.php b/packages/lite/src/Greenter/Api.php index 109236c4..9a519900 100644 --- a/packages/lite/src/Greenter/Api.php +++ b/packages/lite/src/Greenter/Api.php @@ -152,7 +152,7 @@ private function createSender(): GreSender private function createApiFactory(array $endpoints): ApiFactory { $client = new Client(); - $config = Configuration::getDefaultConfiguration(); + $config = new Configuration(); return new ApiFactory( new AuthApi($client, $config->setHost($endpoints['api'])), From 04b45b51e5fd1eb54decc2f2425eac032fd6fad9 Mon Sep 17 00:00:00 2001 From: Giancarlos Salas Date: Wed, 11 Jan 2023 09:13:35 -0500 Subject: [PATCH 35/41] Set default values --- packages/lite/src/Greenter/Api.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/lite/src/Greenter/Api.php b/packages/lite/src/Greenter/Api.php index 9a519900..68e2408d 100644 --- a/packages/lite/src/Greenter/Api.php +++ b/packages/lite/src/Greenter/Api.php @@ -38,7 +38,7 @@ class Api * @param ApiFactory|null $factory * @param SignedXml|null $signer */ - public function __construct(?array $endpoints, ?ApiFactory $factory, ?SignedXml $signer) + public function __construct(?array $endpoints = null, ?ApiFactory $factory = null, ?SignedXml $signer = null) { $this->factory = $factory ?? $this->createApiFactory($endpoints ?? $this->defaaultEndpoints); $this->signer = $signer ?? new SignedXml(); From 256401b1a7088aa32e08876f093a37612e2e0934 Mon Sep 17 00:00:00 2001 From: Giancarlos Salas Date: Wed, 11 Jan 2023 09:16:03 -0500 Subject: [PATCH 36/41] Test status error --- packages/ws/tests/Ws/Api/GreSenderTest.php | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/packages/ws/tests/Ws/Api/GreSenderTest.php b/packages/ws/tests/Ws/Api/GreSenderTest.php index faadb84e..4252ef96 100644 --- a/packages/ws/tests/Ws/Api/GreSenderTest.php +++ b/packages/ws/tests/Ws/Api/GreSenderTest.php @@ -93,6 +93,21 @@ public function testStatusCompleted(): void } public function testStatusPending(): void + { + $api = Mockery::mock(CpeApiInterface::class); + $api->shouldReceive('consultarEnvio') + ->andThrowExceptions([ + $this->createException(500, new CpeError(['cod' => '500', 'msg' => 'Internal Server Error'])), + ]); + $sender = new GreSender($api); + $result = $sender->status('a.b.c'); + + $this->assertFalse($result->isSuccess()); + $this->assertEmpty($result->getCode()); + $this->assertNotNull($result->getError()); + } + + public function testStatusError(): void { $api = Mockery::mock(CpeApiInterface::class); $api->shouldReceive('consultarEnvio') From 65cc538f6028a2bc944fb8bc61453afca4d1899f Mon Sep 17 00:00:00 2001 From: Giancarlos Salas Date: Wed, 11 Jan 2023 09:25:45 -0500 Subject: [PATCH 37/41] Test expire token --- packages/ws/tests/Ws/Api/ApiFactoryTest.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/ws/tests/Ws/Api/ApiFactoryTest.php b/packages/ws/tests/Ws/Api/ApiFactoryTest.php index 37daebba..b003b9b9 100644 --- a/packages/ws/tests/Ws/Api/ApiFactoryTest.php +++ b/packages/ws/tests/Ws/Api/ApiFactoryTest.php @@ -4,6 +4,7 @@ namespace Tests\Greenter\Ws\Api; +use DateTime; use Greenter\Api\ApiFactory; use Greenter\Api\InMemoryStore; use Greenter\Sunat\GRE\Api\AuthApiInterface; @@ -29,13 +30,17 @@ public function testCreate() $token2 = $store->get('x'); // no changes $this->assertEquals($token, $token2); + + // get token if expireIn < 10min + $store->set('x', $token->setValue('xx')->setExpire(new DateTime('+9 minutes'))); + $factory->create('x', 'x', '20123456780MODDATOS', 'moddatos'); } private function deps(): array { $auth = Mockery::mock(AuthApiInterface::class); $auth->shouldReceive('getToken') - ->once() + ->twice() ->andReturn(new ApiToken([ 'access_token' => 'xxxx.xxxx.xxxx', 'token_type' => 'bearer', From a5261929868ea3d7cbdf9ab65b833633d70381f1 Mon Sep 17 00:00:00 2001 From: Giancarlos Salas Date: Wed, 11 Jan 2023 09:26:54 -0500 Subject: [PATCH 38/41] Update url keys --- packages/lite/src/Greenter/Api.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/lite/src/Greenter/Api.php b/packages/lite/src/Greenter/Api.php index 68e2408d..57565ad9 100644 --- a/packages/lite/src/Greenter/Api.php +++ b/packages/lite/src/Greenter/Api.php @@ -24,7 +24,7 @@ class Api private array $credentials = []; private array $defaaultEndpoints = [ - 'api' => 'https://api-seguridad.sunat.gob.pe/v1', + 'auth' => 'https://api-seguridad.sunat.gob.pe/v1', 'cpe' => 'https://api.sunat.gob.pe/v1', ]; @@ -155,7 +155,7 @@ private function createApiFactory(array $endpoints): ApiFactory $config = new Configuration(); return new ApiFactory( - new AuthApi($client, $config->setHost($endpoints['api'])), + new AuthApi($client, $config->setHost($endpoints['auth'])), $client, new InMemoryStore(), $endpoints['cpe'], From dbe4ad12b8d8edacb1a074b0d89553b990f859ea Mon Sep 17 00:00:00 2001 From: Giancarlos Salas Date: Wed, 11 Jan 2023 09:29:13 -0500 Subject: [PATCH 39/41] Deprecated GUIA soap endpoint --- packages/ws/src/Ws/Services/SunatEndpoints.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/ws/src/Ws/Services/SunatEndpoints.php b/packages/ws/src/Ws/Services/SunatEndpoints.php index 64350123..a0776140 100644 --- a/packages/ws/src/Ws/Services/SunatEndpoints.php +++ b/packages/ws/src/Ws/Services/SunatEndpoints.php @@ -25,8 +25,13 @@ final class SunatEndpoints /** * GUIA DE REMISION SERVICES. + * + * @deprecated use API endpoint */ public const GUIA_BETA = 'https://e-beta.sunat.gob.pe/ol-ti-itemision-guia-gem-beta/billService'; + /** + * @deprecated use API endpoint + */ public const GUIA_PRODUCCION = 'https://e-guiaremision.sunat.gob.pe/ol-ti-itemision-guia-gem/billService'; /** From 8eee529b11764f9d58dbbdfa5ffbfff9074bc92d Mon Sep 17 00:00:00 2001 From: Giancarlos Salas Date: Wed, 11 Jan 2023 09:32:22 -0500 Subject: [PATCH 40/41] Remove soap despatch tests --- .../tests/Greenter/Factory/CeFactoryBase.php | 90 ------------------- .../tests/Greenter/Factory/CeFactoryTest.php | 24 +---- packages/lite/tests/Greenter/SeeCeTest.php | 12 --- 3 files changed, 2 insertions(+), 124 deletions(-) diff --git a/packages/lite/tests/Greenter/Factory/CeFactoryBase.php b/packages/lite/tests/Greenter/Factory/CeFactoryBase.php index 1859e95d..127ff2a8 100644 --- a/packages/lite/tests/Greenter/Factory/CeFactoryBase.php +++ b/packages/lite/tests/Greenter/Factory/CeFactoryBase.php @@ -15,10 +15,6 @@ use Greenter\Model\Company\Address; use Greenter\Model\Company\Company; use Greenter\Model\Despatch\Despatch; -use Greenter\Model\Despatch\DespatchDetail; -use Greenter\Model\Despatch\Direction; -use Greenter\Model\Despatch\Shipment; -use Greenter\Model\Despatch\Transportist; use Greenter\Model\DocumentInterface; use Greenter\Model\Perception\Perception; use Greenter\Model\Perception\PerceptionDetail; @@ -27,7 +23,6 @@ use Greenter\Model\Retention\Payment; use Greenter\Model\Retention\Retention; use Greenter\Model\Retention\RetentionDetail; -use Greenter\Model\Sale\Document; use Greenter\Model\Summary\Summary; use Greenter\Model\Voided\Reversion; use Greenter\Model\Voided\VoidedDetail; @@ -82,9 +77,6 @@ protected function setUp(): void protected function getFactoryResult(DocumentInterface $document) { $url = SunatEndpoints::RETENCION_BETA; - if ($document instanceof Despatch) { - $url = SunatEndpoints::GUIA_BETA; - } $sender = $this->getSender(get_class($document), $url); $builder = new $this->builders[get_class($document)](); @@ -272,88 +264,6 @@ protected function getReversion() return $reversion; } - /** - * @return Despatch - */ - protected function getDespatch() - { - list($baja, $rel, $envio) = $this->getExtrasDespatch(); - $despatch = new Despatch(); - $despatch->setTipoDoc('09') - ->setSerie('T001') - ->setCorrelativo('123') - ->setFechaEmision(new \DateTime()) - ->setCompany($this->getCompany()) - ->setDestinatario((new Client()) - ->setTipoDoc('6') - ->setNumDoc('20000000002') - ->setRznSocial('EMPRESA ( />) 1')) - ->setTercero((new Client()) - ->setTipoDoc('6') - ->setNumDoc('20000000003') - ->setRznSocial('EMPRESA SA')) - ->setObservacion('NOTA GUIA') - ->setDocBaja($baja) - ->setRelDoc($rel) - ->setEnvio($envio); - - $detail = new DespatchDetail(); - $detail->setCantidad(2) - ->setUnidad('ZZ') - ->setCodProdSunat('22222') - ->setDescripcion('PROD 1') - ->setCodigo('PROD1'); - - $despatch->setDetails([$detail]); - - return $despatch; - } - - /** - * @return array - */ - private function getExtrasDespatch() - { - $baja = new Document(); - $baja->setTipoDoc('09') - ->setNroDoc('T001-00001'); - - $rel = new Document(); - $rel->setTipoDoc('02') // Tipo: Numero de Orden de Entrega - ->setNroDoc('213123'); - - $envio = new Shipment(); - $envio - ->setCodTraslado('01') - ->setDesTraslado('VENTA') - ->setFecTraslado(new \DateTime()) - ->setCodPuerto('123') - ->setIndTransbordo(false) - ->setPesoTotal(12.5) - ->setUndPesoTotal('KGM') -// ->setNumBultos(2) // Solo en Importación: CodTraslado: 08 - ->setModTraslado('01') - ->setNumContenedor('XD-2232') - ->setLlegada(new Direction('150101', 'AV LIMA')) - ->setPartida(new Direction('150203', 'AV ITALIA')) - ->setTransportista($this->getTransportist()); - - return [$baja, $rel, $envio]; - } - - private function getTransportist() - { - $transp = new Transportist(); - $transp->setTipoDoc('6') - ->setNumDoc('20000000002') - ->setRznSocial('TRANSPORTES S.A.C') - ->setPlaca('ABI-453') - ->setChoferTipoDoc('1') - ->setChoferDoc('40003344'); - - return $transp; - } - /** * @return Company */ diff --git a/packages/lite/tests/Greenter/Factory/CeFactoryTest.php b/packages/lite/tests/Greenter/Factory/CeFactoryTest.php index 5c73ab96..3054d592 100644 --- a/packages/lite/tests/Greenter/Factory/CeFactoryTest.php +++ b/packages/lite/tests/Greenter/Factory/CeFactoryTest.php @@ -19,26 +19,6 @@ */ class CeFactoryTest extends CeFactoryBase { - public function testDespatch() - { - $despatch = $this->getDespatch(); - $result = $this->getFactoryResult($despatch); - - // Enviar a API - if (!$result->isSuccess() && - $result->getError()->getCode() == '1085') { - return; - } - - $this->assertTrue($result->isSuccess()); - $this->assertNotNull($result->getCdrResponse()); - $this->assertEquals( - '0', - $result->getCdrResponse()->getCode() - ); - - } - public function testRetention() { $retention = $this->getRetention(); @@ -54,8 +34,8 @@ public function testRetention() public function testGetXmlSigned() { - $despatch = $this->getDespatch(); - $signXml = $this->getXmlSigned($despatch); + $perception = $this->getPerception(); + $signXml = $this->getXmlSigned($perception); $this->assertNotEmpty($signXml); } diff --git a/packages/lite/tests/Greenter/SeeCeTest.php b/packages/lite/tests/Greenter/SeeCeTest.php index 504da943..4afd9cd5 100644 --- a/packages/lite/tests/Greenter/SeeCeTest.php +++ b/packages/lite/tests/Greenter/SeeCeTest.php @@ -23,18 +23,6 @@ */ class SeeCeTest extends CeFactoryBase { - public function testSendDespatch() - { - $doc = $this->getDespatch(); - - /**@var $result BillResult*/ - $result = $this->getSee(SunatEndpoints::GUIA_BETA)->send($doc); - - // Enviar a API - $this->assertFalse($result->isSuccess()); - $this->assertEquals($result->getError()->getCode(), '1085'); - } - /** * @dataProvider providerBillDocs * @param DocumentInterface $doc From 2bb0562309ba7d4d167b317f1d6ec1e0548f5e55 Mon Sep 17 00:00:00 2001 From: Giancarlos Salas Date: Wed, 11 Jan 2023 10:07:43 -0500 Subject: [PATCH 41/41] Add api tests --- packages/lite/tests/Greenter/ApiTest.php | 92 +++++++++++++++++++++ packages/ws/tests/Ws/Api/ApiFactoryTest.php | 2 +- 2 files changed, 93 insertions(+), 1 deletion(-) create mode 100644 packages/lite/tests/Greenter/ApiTest.php diff --git a/packages/lite/tests/Greenter/ApiTest.php b/packages/lite/tests/Greenter/ApiTest.php new file mode 100644 index 00000000..51c0d31c --- /dev/null +++ b/packages/lite/tests/Greenter/ApiTest.php @@ -0,0 +1,92 @@ +getApi(); + $despatch = $this->createDespatch(); + + /**@var $result SummaryResult */ + $result = $api->send($despatch); + + $this->assertTrue($result->isSuccess()); + $this->assertNotEmpty($result->getTicket()); + + $res = $api->getStatus($result->getTicket()); + $this->assertTrue($res->isSuccess()); + $this->assertEquals('0', $res->getCode()); + $this->assertNotEmpty($res->getCdrZip()); + $this->assertStringContainsString('ACEPTADA', $res->getCdrResponse()->getDescription()); + $this->assertStringStartsWith("http", $res->getCdrResponse()->getReference()); + } + + private function getApi(): Api + { + $api = new Api([ + 'auth' => 'https://gre-test.nubefact.com/v1', + 'cpe' => 'https://gre-test.nubefact.com/v1', + ]); + + return $api->setBuilderOptions([ + 'strict_variables' => true, + 'optimizations' => 0, + 'debug' => true, + 'cache' => false, + ]) + ->setApiCredentials('test-85e5b0ae-255c-4891-a595-0b98c65c9854', 'test-Hty/M6QshYvPgItX2P0+Kw==') + ->setClaveSOL('20161515648', 'MODDATOS', 'MODDATOS') + ->setCertificate(file_get_contents(__DIR__.'/../Resources/SFSCert.pem')); + } + + private function createDespatch(): Despatch + { + $envio = (new Shipment()) + ->setCodTraslado('01') + ->setIndicadores(['SUNAT_Envio_IndicadorTrasladoVehiculoM1L']) + ->setModTraslado('02') + ->setFecTraslado(new DateTime()) + ->setPesoTotal(12.5) + ->setUndPesoTotal('KGM') + ->setLlegada(new Direction('150101', 'AV LIMA')) + ->setPartida(new Direction('150203', 'AV ITALIA')); + + $despatch = new Despatch(); + $despatch->setVersion('2022') + ->setTipoDoc('09') + ->setSerie('T001') + ->setCorrelativo('1') + ->setFechaEmision(new DateTime()) + ->setCompany((new Company()) + ->setRuc('20161515648') + ->setRazonSocial('GREENTER SAC')) + ->setDestinatario((new Client()) + ->setTipoDoc('6') + ->setNumDoc('20000000002') + ->setRznSocial('EMPRESA DEST 1')) + ->setEnvio($envio); + + $detail = new DespatchDetail(); + $detail->setCantidad(2) + ->setUnidad('ZZ') + ->setDescripcion('PROD 1') + ->setCodigo('PROD1'); + + return $despatch->setDetails([$detail]); + } +} \ No newline at end of file diff --git a/packages/ws/tests/Ws/Api/ApiFactoryTest.php b/packages/ws/tests/Ws/Api/ApiFactoryTest.php index b003b9b9..7d5d4430 100644 --- a/packages/ws/tests/Ws/Api/ApiFactoryTest.php +++ b/packages/ws/tests/Ws/Api/ApiFactoryTest.php @@ -43,7 +43,7 @@ private function deps(): array ->twice() ->andReturn(new ApiToken([ 'access_token' => 'xxxx.xxxx.xxxx', - 'token_type' => 'bearer', + 'token_type' => 'JWT', 'expires_in' => 3600, ]));