Passed
Push — main ( 76877a...101e35 )
by Pedro
02:17
created

Cliente::verificarToken()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 2
c 2
b 0
f 0
dl 0
loc 4
rs 10
cc 2
nc 2
nop 0
1
<?php
2
3
namespace PedroQuezado\Code\Correios;
4
5
use \CURLFile;
0 ignored issues
show
Bug introduced by
The type \CURLFile was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
6
7
/**
8
 * Classe Cliente para integração com os serviços dos Correios.
9
 */
10
class Cliente
11
{
12
	/**
13
	 * @var string $usuario Usuário de autenticação.
14
	 */
15
	private $usuario;
16
17
	/**
18
	 * @var string $senha Senha de autenticação.
19
	 */
20
	private $senha;
21
22
	/**
23
	 * @var string $numeroCartaoPostagem Número do cartão de postagem.
24
	 */
25
	private $numeroCartaoPostagem;
26
27
	/**
28
	 * @var string $token Token de autenticação.
29
	 */
30
	private $token;
31
32
	/**
33
	 * @var int $tokenExpiration Timestamp de expiração do token.
34
	 */
35
	private $tokenExpiration;
36
37
	/**
38
	 * @var string $baseUrl URL base da API dos Correios.
39
	 */
40
	private $baseUrl;
41
42
	/**
43
	 * @var array $produtos Lista de produtos a serem consultados.
44
	 */
45
	private $produtos = [];
46
47
	/**
48
	 * @var array $respostaPreco Resposta da consulta de preços.
49
	 */
50
	private $respostaPreco;
51
52
	/**
53
	 * @var array $respostaPrazo Resposta da consulta de prazos.
54
	 */
55
	private $respostaPrazo;
56
57
	/**
58
	 * Construtor da classe Cliente.
59
	 *
60
	 * @param string $usuario Usuário de autenticação.
61
	 * @param string $senha Senha de autenticação.
62
	 * @param string $numeroCartaoPostagem Número do cartão de postagem.
63
	 * @param bool $producao Indica se é ambiente de produção (true) ou homologação (false).
64
	 */
65
	public function __construct($usuario, $senha, $numeroCartaoPostagem, $producao = true)
66
	{
67
		$this->usuario = $usuario;
68
		$this->senha = $senha;
69
		$this->numeroCartaoPostagem = $numeroCartaoPostagem;
70
		$this->baseUrl = $producao ? "https://api.correios.com.br" : "https://apihom.correios.com.br";
71
		$this->token = $this->obterToken();
72
	}
73
74
	/**
75
	 * Obtém o token de autenticação.
76
	 *
77
	 * @return string Token de autenticação.
78
	 * @throws ClienteException Em caso de erro na obtenção do token.
79
	 */
80
	private function obterToken()
81
	{
82
		$credenciais = base64_encode("{$this->usuario}:{$this->senha}");
83
		$ch = curl_init();
84
		curl_setopt($ch, CURLOPT_URL, "{$this->baseUrl}/token/v1/autentica/cartaopostagem");
85
		curl_setopt($ch, CURLOPT_POST, 1);
86
		curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode(["numero" => $this->numeroCartaoPostagem]));
87
		curl_setopt($ch, CURLOPT_HTTPHEADER, [
88
			"Content-Type: application/json",
89
			"Authorization: Basic $credenciais"
90
		]);
91
		curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
92
		$response = curl_exec($ch);
93
94
		if (curl_errno($ch)) {
95
			throw new ClienteException('Erro ao obter token: ' . curl_error($ch));
96
		}
97
98
		$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
99
		curl_close($ch);
100
101
		if ($httpCode != 201) {
102
			throw new ClienteException('Erro ao obter token', $httpCode, $response);
103
		}
104
105
		if (is_string($response)) {
106
			$responseDecoded = json_decode($response, true);
107
			$this->tokenExpiration = time() + 3600; // Supondo que o token expira em 1 hora
108
		} else {
109
			throw new ClienteException('Erro ao obter token: resposta inválida recebida.');
110
		}
111
112
		return $responseDecoded['token'];
113
	}
114
115
	/**
116
	 * Verifica a validade do token e renova se necessário.
117
	 */
118
	private function verificarToken()
119
	{
120
		if (time() >= $this->tokenExpiration) {
121
			$this->token = $this->obterToken();
122
		}
123
	}
124
125
	/**
126
	 * Insere um produto na lista de produtos a serem consultados.
127
	 *
128
	 * @param string $coProduto Código do produto.
129
	 * @param array $arrProduto Dados do produto.
130
	 */
131
	public function inserirProduto($coProduto, array $arrProduto) 
132
	{
133
		$arrProduto['coProduto'] = $coProduto;
134
		$this->produtos[] = $arrProduto;
135
	}
136
137
	/**
138
	 * Consulta o preço dos produtos inseridos dividindo-os em lotes.
139
	 * 
140
	 * @return array A resposta da consulta de preço.
141
	 * @throws ClienteException Se ocorrer um erro durante a consulta.
142
	 */
143
	public function consultarPreco()
144
	{
145
		if (empty($this->produtos)) {
146
			throw new ClienteException('Nenhum produto inserido para consulta de preço.');
147
		}
148
149
		$this->verificarToken();
150
		$chunks = $this->criarChunksDeProdutosPreco($this->produtos);
151
		$this->respostaPreco = $this->consultarPrecoEmChunks($chunks);
152
		return $this->respostaPreco;
153
	}
154
155
	/**
156
	 * Cria lotes de produtos divididos em grupos de no máximo 5 itens.
157
	 * 
158
	 * @param array $produtos Os produtos a serem divididos em lotes.
159
	 * @return array Os produtos divididos em lotes de no máximo 5 itens.
160
	 */
161
	private function criarChunksDeProdutosPreco(array $produtos)
162
	{
163
		return array_chunk($produtos, 5);
164
	}
165
166
	/**
167
	 * Consulta o preço dos produtos em lotes e retorna as respostas.
168
	 * 
169
	 * @param array $chunks Os lotes de produtos a serem consultados.
170
	 * @return array As respostas da consulta de preço.
171
	 * @throws ClienteException Se ocorrer um erro durante a consulta.
172
	 */
173
	private function consultarPrecoEmChunks(array $chunks)
174
	{
175
		$responses = [];
176
177
		foreach ($chunks as $chunk) {
178
			$ch = curl_init();
179
			curl_setopt($ch, CURLOPT_URL, "{$this->baseUrl}/preco/v1/nacional");
180
			curl_setopt($ch, CURLOPT_POST, 1);
181
			curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
182
				"idLote" => "1",
183
				"parametrosProduto" => $chunk
184
			]));
185
			curl_setopt($ch, CURLOPT_HTTPHEADER, [
186
				"Content-Type: application/json",
187
				"Authorization: Bearer {$this->token}"
188
			]);
189
			curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
190
			$response = curl_exec($ch);
191
192
			if (curl_errno($ch)) {
193
				throw new ClienteException('Erro ao consultar preço: ' . curl_error($ch));
194
			}
195
196
			$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
197
			curl_close($ch);
198
199
			if ($httpCode != 200) {
200
				throw new ClienteException('Erro ao consultar preço', $httpCode, $response);
201
			}
202
203
			if (is_string($response)) {
204
				$responses[] = json_decode($response, true);
205
			} else {
206
				throw new ClienteException('Erro ao consultar preço: resposta inválida recebida.');
207
			}
208
		}
209
210
		return array_merge(...$responses);
211
	}
212
213
	/**
214
	 * Consulta o preço total de um produto específico.
215
	 *
216
	 * @param string $coProduto Código do produto.
217
	 * @return float Preço total do produto.
218
	 * @throws ClienteException Em caso de erro na consulta de preços ou se preços não foram consultados ainda.
219
	 */
220
	public function consultarPrecoTotal($coProduto)
221
	{
222
		if (empty($this->respostaPreco)) {
223
			throw new ClienteException('Preço não foi consultado ainda.');
224
		}
225
226
		$total = 0;
227
		foreach ($this->respostaPreco as $produto) {
228
			if ($produto['coProduto'] === $coProduto) {
229
				$total += (float)str_replace(',', '.', $produto['pcFinal']);
230
			}
231
		}
232
233
		return $total;
234
	}
235
236
	/**
237
	 * Consulta os prazos de entrega dos produtos inseridos.
238
	 *
239
	 * @param string $dataPostagem Data de postagem no formato YYYY-MM-DD.
240
	 * @param string $cepOrigem CEP de origem.
241
	 * @param string $cepDestino CEP de destino.
242
	 * @param string|null $dtEvento Data do evento no formato DD-MM-YYYY (opcional).
243
	 * @return array Resposta da consulta de prazos.
244
	 * @throws ClienteException Em caso de erro na consulta de prazos.
245
	 */
246
	public function consultarPrazo($dataPostagem, $cepOrigem, $cepDestino, $dtEvento = null)
247
	{
248
		if (empty($this->produtos)) {
249
			throw new ClienteException('Nenhum produto inserido para consulta de prazo.');
250
		}
251
252
		$this->verificarToken(); // Verifica se o token de autenticação é válido e, se necessário, renova o token.
253
		$dtEvento = $dtEvento ? $dtEvento : date("d-m-Y", strtotime($dataPostagem)); // Define a data do evento. Se $dtEvento não for fornecido, usa a data de postagem convertida para o formato DD-MM-YYYY.
254
255
		$chunks = $this->criarChunksDeProdutosPrazo($dataPostagem, $cepOrigem, $cepDestino, $dtEvento);
256
257
		$responses = $this->consultarPrazoEmChunks($chunks);
258
259
		$this->respostaPrazo = array_merge(...$responses);
260
		return $this->respostaPrazo;
261
	}
262
263
	/**
264
	 * Cria lotes de produtos divididos em grupos de no máximo 5 itens.
265
	 * 
266
	 * @param string $dataPostagem A data de postagem no formato YYYY-MM-DD.
267
	 * @param string $cepOrigem O CEP de origem do envio.
268
	 * @param string $cepDestino O CEP de destino do envio.
269
	 * @param string $dtEvento A data do evento no formato DD-MM-YYYY.
270
	 * @return array Os produtos divididos em lotes de no máximo 5 itens.
271
	 */
272
	private function criarChunksDeProdutosPrazo($dataPostagem, $cepOrigem, $cepDestino, $dtEvento)
273
	{
274
		return array_chunk(array_map(function ($produto) use ($dataPostagem, $cepOrigem, $cepDestino, $dtEvento) {
275
			return [
276
				"coProduto" => $produto['coProduto'],
277
				"nuRequisicao" => $produto['nuRequisicao'],
278
				"dtEvento" => $dtEvento,
279
				"cepOrigem" => $cepOrigem,
280
				"cepDestino" => $cepDestino,
281
				"dataPostagem" => $dataPostagem
282
			];
283
		}, $this->produtos), 5);
284
	}
285
286
	/**
287
	 * Consulta o prazo de entrega dos produtos em lotes e retorna as respostas.
288
	 * 
289
	 * @param array $chunks Os lotes de produtos a serem consultados.
290
	 * @return array As respostas da consulta de prazo de entrega.
291
	 * @throws ClienteException Se ocorrer um erro durante a consulta.
292
	 */
293
	private function consultarPrazoEmChunks($chunks)
294
	{
295
		$responses = [];
296
297
		foreach ($chunks as $chunk) {
298
			$ch = curl_init();
299
			curl_setopt($ch, CURLOPT_URL, "{$this->baseUrl}/prazo/v1/nacional");
300
			curl_setopt($ch, CURLOPT_POST, 1);
301
			curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
302
				"idLote" => "1",
303
				"parametrosPrazo" => $chunk
304
			]));
305
			curl_setopt($ch, CURLOPT_HTTPHEADER, [
306
				"Content-Type: application/json",
307
				"Authorization: Bearer {$this->token}"
308
			]);
309
			curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
310
			$response = curl_exec($ch);
311
312
			if (curl_errno($ch)) {
313
				throw new ClienteException('Erro ao consultar prazo: ' . curl_error($ch));
314
			}
315
316
			$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
317
			curl_close($ch);
318
319
			if ($httpCode != 200) {
320
				throw new ClienteException('Erro ao consultar prazo', $httpCode, $response);
321
			}
322
323
			if (is_string($response)) {
324
				$responses[] = json_decode($response, true);
325
			} else {
326
				throw new ClienteException('Erro ao consultar prazo: resposta inválida recebida.');
327
			}
328
		}
329
330
		return $responses;
331
	}
332
333
	/**
334
	 * Consulta o prazo total de entrega de um produto específico.
335
	 *
336
	 * @param string $coProduto Código do produto.
337
	 * @return array Prazo total de entrega do produto.
338
	 * @throws ClienteException Em caso de erro na consulta de prazos ou se prazos não foram consultados ainda.
339
	 */
340
	public function consultarPrazoTotal($coProduto)
341
	{
342
		if (empty($this->respostaPrazo)) {
343
			throw new ClienteException('Prazo não foi consultado ainda.');
344
		}
345
346
		foreach ($this->respostaPrazo as $produto) {
347
			if ($produto['coProduto'] === $coProduto) {
348
				return $produto;
349
			}
350
		}
351
352
		throw new ClienteException('Produto não encontrado na resposta do prazo.');
353
	}
354
355
	/**
356
	 * Realiza a pré-postagem a faturar de objetos registrados.
357
	 *
358
	 * @param array $dadosPrePostagem Dados da pré-postagem.
359
	 * @return array Resposta da API dos Correios.
360
	 * @throws ClienteException Em caso de erro na solicitação.
361
	 */
362
	public function realizarPrePostagem(array $dadosPrePostagem)
363
	{
364
		$this->verificarToken();
365
366
		$ch = curl_init();
367
		curl_setopt($ch, CURLOPT_URL, "{$this->baseUrl}/prepostagem/v1/prepostagens");
368
		curl_setopt($ch, CURLOPT_POST, 1);
369
		curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($dadosPrePostagem));
370
		curl_setopt($ch, CURLOPT_HTTPHEADER, [
371
			"Content-Type: application/json",
372
			"Authorization: Bearer {$this->token}"
373
		]);
374
		curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
375
		$response = curl_exec($ch);
376
377
		if (curl_errno($ch)) {
378
			throw new ClienteException('Erro ao realizar pré-postagem: ' . curl_error($ch));
379
		}
380
381
		$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
382
		curl_close($ch);
383
384
		if ($httpCode != 200) {
385
			throw new ClienteException('Erro ao realizar pré-postagem', $httpCode, $response);
386
		}
387
388
		return json_decode($response, true);
0 ignored issues
show
Bug introduced by
It seems like $response can also be of type true; however, parameter $json of json_decode() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

388
		return json_decode(/** @scrutinizer ignore-type */ $response, true);
Loading history...
389
	}
390
391
	/**
392
	 * Cancela uma pré-postagem pelo identificador.
393
	 *
394
	 * @param string $idPrePostagem Identificador da pré-postagem.
395
	 * @param string|null $idSolicitante ID Correios do solicitante do cancelamento (opcional).
396
	 * @return array Resposta da API dos Correios.
397
	 * @throws ClienteException Se ocorrer um erro na solicitação.
398
	 */
399
	public function cancelarPrePostagem(string $idPrePostagem, string $idSolicitante = null)
400
	{
401
	    $this->verificarToken(); // Garante que o token esteja válido
402
403
	    // Monta a URL com os parâmetros
404
	    $url = "{$this->baseUrl}/prepostagem/v1/prepostagens/{$idPrePostagem}";
405
406
	    if (!empty($idSolicitante)) {
407
	        $url .= "?idCorreiosSolicitanteCancelamento={$idSolicitante}";
408
	    }
409
410
	    $ch = curl_init();
411
	    curl_setopt($ch, CURLOPT_URL, $url);
412
	    curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "DELETE");
413
	    curl_setopt($ch, CURLOPT_HTTPHEADER, [
414
	        "Content-Type: application/json",
415
	        "Authorization: Bearer {$this->token}"
416
	    ]);
417
	    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
418
	    
419
	    $response = curl_exec($ch);
420
421
	    if (curl_errno($ch)) {
422
	        throw new ClienteException('Erro ao cancelar pré-postagem: ' . curl_error($ch));
423
	    }
424
425
	    $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
426
	    curl_close($ch);
427
428
	    if ($httpCode != 200) {
429
	        throw new ClienteException("Erro ao cancelar pré-postagem", $httpCode, $response);
430
	    }
431
432
	    return json_decode($response, true);
0 ignored issues
show
Bug introduced by
It seems like $response can also be of type true; however, parameter $json of json_decode() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

432
	    return json_decode(/** @scrutinizer ignore-type */ $response, true);
Loading history...
433
	}
434
435
	/**
436
	 * Realiza a pré-postagem de múltiplos objetos registrados em um único envio.
437
	 *
438
	 * @param array $listaObjetos Lista de objetos para pré-postagem.
439
	 * @return array Resposta da API dos Correios.
440
	 * @throws ClienteException Em caso de erro na solicitação.
441
	 */
442
	public function realizarPrePostagemEmLote(array $listaObjetos)
443
	{
444
		$this->verificarToken();
445
446
		$caminhoArquivo = sys_get_temp_dir() . "/prepostagem.json";
447
448
		// Salvar o JSON no arquivo
449
		file_put_contents($caminhoArquivo, json_encode([$listaObjetos]));
450
451
		// Criar um objeto CURLFile para o upload do arquivo
452
    	$arquivo = new CURLFile($caminhoArquivo, 'application/json', 'prepostagem.json');
453
454
		$ch = curl_init();
455
		curl_setopt($ch, CURLOPT_URL, "{$this->baseUrl}/prepostagem/v1/prepostagens/lista/objetosregistrados");
456
		curl_setopt($ch, CURLOPT_POST, 1);
457
		// curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($listaObjetos));
458
		curl_setopt($ch, CURLOPT_POSTFIELDS, ["arquivo" => $arquivo]);
459
		curl_setopt($ch, CURLOPT_HTTPHEADER, [
460
			"Content-Type: multipart/form-data",
461
			"Authorization: Bearer {$this->token}"
462
		]);
463
		curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
464
		$response = curl_exec($ch);
465
466
		if (curl_errno($ch)) {
467
			throw new ClienteException('Erro ao realizar pré-postagem em lote: ' . curl_error($ch));
468
		}
469
470
		$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
471
		curl_close($ch);
472
473
		if ($httpCode != 200) {
474
			throw new ClienteException('Erro ao realizar pré-postagem em lote', $httpCode, $response);
475
		}
476
477
		return json_decode($response, true);
0 ignored issues
show
Bug introduced by
It seems like $response can also be of type true; however, parameter $json of json_decode() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

477
		return json_decode(/** @scrutinizer ignore-type */ $response, true);
Loading history...
478
	}
479
480
481
	/**
482
	 * Gera o rótulo para o objeto previamente cadastrado na pré-postagem.
483
	 *
484
	 * @param string $idCorreios ID retornado na pré-postagem.
485
	 * @return string URL para download do rótulo.
486
	 * @throws ClienteException Em caso de erro na solicitação.
487
	 */
488
	public function gerarRotulo($idCorreios)
489
	{
490
		$this->verificarToken();
491
492
		$ch = curl_init();
493
		curl_setopt($ch, CURLOPT_URL, "{$this->baseUrl}/prepostagem/v1/prepostagens/rotulo");
494
		curl_setopt($ch, CURLOPT_POST, 1);
495
		curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode(["idCorreios" => $idCorreios]));
496
		curl_setopt($ch, CURLOPT_HTTPHEADER, [
497
			"Content-Type: application/json",
498
			"Authorization: Bearer {$this->token}"
499
		]);
500
		curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
501
		$response = curl_exec($ch);
502
503
		if (curl_errno($ch)) {
504
			throw new ClienteException('Erro ao gerar rótulo: ' . curl_error($ch));
505
		}
506
507
		$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
508
		curl_close($ch);
509
510
		if ($httpCode != 200) {
511
			throw new ClienteException('Erro ao gerar rótulo', $httpCode, $response);
512
		}
513
514
		$responseDecoded = json_decode($response, true);
0 ignored issues
show
Bug introduced by
It seems like $response can also be of type true; however, parameter $json of json_decode() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

514
		$responseDecoded = json_decode(/** @scrutinizer ignore-type */ $response, true);
Loading history...
515
516
		return $responseDecoded['urlEtiqueta'] ?? '';
517
	}
518
}
519