Completed
Push — master ( 404e18...7edabe )
by Jan
03:27
created

ApiClient::setLogger()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 4
ccs 3
cts 3
cp 1
rs 10
cc 1
eloc 2
nc 1
nop 1
crap 1
1
<?php declare(strict_types = 1);
2
3
namespace SlevomatCsobGateway\Api;
4
5
use DateTimeImmutable;
6
use Psr\Log\LoggerInterface;
7
use SlevomatCsobGateway\Call\ResponseExtensionHandler;
8
use SlevomatCsobGateway\Crypto\CryptoService;
9
use SlevomatCsobGateway\Crypto\PrivateKeyFileException;
10
use SlevomatCsobGateway\Crypto\PublicKeyFileException;
11
use SlevomatCsobGateway\Crypto\SignatureDataFormatter;
12
use SlevomatCsobGateway\Crypto\SigningFailedException;
13
use SlevomatCsobGateway\Crypto\VerificationFailedException;
14
15
class ApiClient
16
{
17
18
	/**
19
	 * @var ApiClientDriver
20
	 */
21
	private $driver;
22
23
	/**
24
	 * @var CryptoService
25
	 */
26
	private $cryptoService;
27
28
	/**
29
	 * @var LoggerInterface|null
30
	 */
31
	private $logger;
32
33
	/**
34
	 * @var string
35
	 */
36
	private $apiUrl;
37
38 16
	public function __construct(
39
		ApiClientDriver $driver,
40
		CryptoService $cryptoService,
41
		string $apiUrl = null
42
	)
43
	{
44 16
		$this->driver = $driver;
45 16
		$this->cryptoService = $cryptoService;
46 16
		$this->apiUrl = $apiUrl;
47 16
	}
48
49 5
	public function setLogger(LoggerInterface $logger = null)
50
	{
51 5
		$this->logger = $logger;
52 5
	}
53
54
	/**
55
	 * @param string $url
56
	 * @param mixed[]|null $data
57
	 * @param SignatureDataFormatter $requestSignatureDataFormatter
58
	 * @param SignatureDataFormatter $responseSignatureDataFormatter
59
	 * @param \Closure|null $responseValidityCallback
60
	 * @param ResponseExtensionHandler[] $extensions
61
	 * @return Response
62
	 *
63
	 * @throws PrivateKeyFileException
64
	 * @throws SigningFailedException
65
	 * @throws PublicKeyFileException
66
	 * @throws VerificationFailedException
67
	 * @throws RequestException
68
	 * @throws ApiClientDriverException
69
	 * @throws InvalidSignatureException
70
	 */
71 13 View Code Duplication
	public function get(
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
72
		string $url,
73
		array $data = [],
74
		SignatureDataFormatter $requestSignatureDataFormatter,
75
		SignatureDataFormatter $responseSignatureDataFormatter,
76
		\Closure $responseValidityCallback = null,
77
		array $extensions = []
78
	): Response
79
	{
80 13
		return $this->request(
81 13
			new HttpMethod(HttpMethod::GET),
82
			$url,
83 13
			$this->prepareData($data, $requestSignatureDataFormatter),
84 13
			null,
85
			$responseSignatureDataFormatter,
86
			$responseValidityCallback,
87
			$extensions
88
		);
89
	}
90
91
	/**
92
	 * @param string $url
93
	 * @param mixed[]|null $data
94
	 * @param SignatureDataFormatter $requestSignatureDataFormatter
95
	 * @param SignatureDataFormatter $responseSignatureDataFormatter
96
	 * @param ResponseExtensionHandler[] $extensions
97
	 * @return Response
98
	 *
99
	 * @throws PrivateKeyFileException
100
	 * @throws SigningFailedException
101
	 * @throws PublicKeyFileException
102
	 * @throws VerificationFailedException
103
	 * @throws RequestException
104
	 * @throws ApiClientDriverException
105
	 * @throws InvalidSignatureException
106
	 */
107 1 View Code Duplication
	public function post(
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
108
		string $url,
109
		array $data = [],
110
		SignatureDataFormatter $requestSignatureDataFormatter,
111
		SignatureDataFormatter $responseSignatureDataFormatter,
112
		array $extensions = []
113
	): Response
114
	{
115 1
		return $this->request(
116 1
			new HttpMethod(HttpMethod::POST),
117
			$url,
118 1
			[],
119 1
			$this->prepareData($data, $requestSignatureDataFormatter),
120
			$responseSignatureDataFormatter,
121 1
			null,
122
			$extensions
123
		);
124
	}
125
126
	/**
127
	 * @param string $url
128
	 * @param mixed[]|null $data
129
	 * @param SignatureDataFormatter $requestSignatureDataFormatter
130
	 * @param SignatureDataFormatter $responseSignatureDataFormatter
131
	 * @param ResponseExtensionHandler[] $extensions
132
	 * @return Response
133
	 *
134
	 * @throws PrivateKeyFileException
135
	 * @throws SigningFailedException
136
	 * @throws PublicKeyFileException
137
	 * @throws VerificationFailedException
138
	 * @throws RequestException
139
	 * @throws ApiClientDriverException
140
	 * @throws InvalidSignatureException
141
	 */
142 1 View Code Duplication
	public function put(
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
143
		string $url,
144
		array $data = [],
145
		SignatureDataFormatter $requestSignatureDataFormatter,
146
		SignatureDataFormatter $responseSignatureDataFormatter,
147
		array $extensions = []
148
	): Response
149
	{
150 1
		return $this->request(
151 1
			new HttpMethod(HttpMethod::PUT),
152
			$url,
153 1
			[],
154 1
			$this->prepareData($data, $requestSignatureDataFormatter),
155
			$responseSignatureDataFormatter,
156 1
			null,
157
			$extensions
158
		);
159
	}
160
161
	/**
162
	 * @param HttpMethod $method
163
	 * @param string $url
164
	 * @param string[] $queries
165
	 * @param mixed[]|null $data
166
	 * @param SignatureDataFormatter $responseSignatureDataFormatter
167
	 * @param \Closure|null $responseValidityCallback
168
	 * @param ResponseExtensionHandler[] $extensions
169
	 * @return Response
170
	 *
171
	 * @throws PrivateKeyFileException
172
	 * @throws SigningFailedException
173
	 * @throws PublicKeyFileException
174
	 * @throws VerificationFailedException
175
	 * @throws RequestException
176
	 * @throws ApiClientDriverException
177
	 * @throws InvalidSignatureException
178
	 */
179 15
	public function request(
180
		HttpMethod $method,
181
		string $url,
182
		array $queries = [],
183
		array $data = null,
184
		SignatureDataFormatter $responseSignatureDataFormatter,
185
		\Closure $responseValidityCallback = null,
186
		array $extensions = []
187
	): Response
188
	{
189 15
		$urlFirstQueryPosition = strpos($url, '{');
190 15
		$endpointName = ($urlFirstQueryPosition !== false ? substr($url, 0, $urlFirstQueryPosition - 1) : $url);
191 15
		$originalQueries = $queries;
192
193 15
		foreach ($queries as $key => $value) {
194 13
			if (strpos($url, '{' . $key . '}') !== false) {
195 13
				$url = str_replace('{' . $key . '}', urlencode((string) $value), $url);
0 ignored issues
show
Coding Style introduced by
Consider using a different name than the parameter $url. This often makes code more readable.
Loading history...
196 13
				unset($queries[$key]);
197
			}
198
		}
199
200 15
		if ($queries !== []) {
201
			throw new \InvalidArgumentException('Arguments are missing URL placeholders: ' . json_encode($queries));
202
		}
203
204 15
		$response = $this->driver->request(
205
			$method,
206 15
			$this->apiUrl . '/' . $url,
207
			$data
208
		);
209
210 15
		$this->logRequest($method, $endpointName, $originalQueries, $data, $response);
211
212 15
		if ($responseValidityCallback !== null) {
213
			$responseValidityCallback($response);
214
		}
215
216 15
		if ($response->getResponseCode()->equalsValue(ResponseCode::S200_OK)) {
217 7
			$decodedExtensions = [];
218 7
			if ($extensions !== [] && array_key_exists('extensions', $response->getData())) {
219 1
				foreach ($response->getData()['extensions'] as $extensionData) {
220 1
					$name = $extensionData['extension'];
221 1
					if (isset($extensions[$name])) {
222 1
						$handler = $extensions[$name];
223 1
						$decodedExtensions[$name] = $handler->createResponse($this->decodeData($extensionData, $handler->getSignatureDataFormatter()));
224
					}
225
				}
226
			}
227 7
			$responseData = $this->decodeData($response->getData(), $responseSignatureDataFormatter);
228 5
			unset($responseData['extensions']);
229
230 5
			return new Response(
231 5
				$response->getResponseCode(),
232
				$responseData,
233 5
				$response->getHeaders(),
234
				$decodedExtensions
235
			);
236
237 8
		} elseif ($response->getResponseCode()->equalsValue(ResponseCode::S303_SEE_OTHER)) {
238 1
			return new Response(
239 1
				$response->getResponseCode(),
240 1
				null,
241 1
				$response->getHeaders()
242
			);
243
244 7
		} elseif ($response->getResponseCode()->equalsValue(ResponseCode::S400_BAD_REQUEST)) {
245 1
			throw new BadRequestException($response);
246
247 6
		} elseif ($response->getResponseCode()->equalsValue(ResponseCode::S403_FORBIDDEN)) {
248 1
			throw new ForbiddenException($response);
249
250 5
		} elseif ($response->getResponseCode()->equalsValue(ResponseCode::S404_NOT_FOUND)) {
251 1
			throw new NotFoundException($response);
252
253 4
		} elseif ($response->getResponseCode()->equalsValue(ResponseCode::S405_METHOD_NOT_ALLOWED)) {
254 1
			throw new MethodNotAllowedException($response);
255
256 3
		} elseif ($response->getResponseCode()->equalsValue(ResponseCode::S429_TOO_MANY_REQUESTS)) {
257 1
			throw new TooManyRequestsException($response);
258
259 2
		} elseif ($response->getResponseCode()->equalsValue(ResponseCode::S503_SERVICE_UNAVAILABLE)) {
260 1
			throw new ServiceUnavailableException($response);
261
		}
262
263 1
		throw new InternalErrorException($response);
264
	}
265
266
	/**
267
	 * @param mixed[] $data
268
	 * @param SignatureDataFormatter $responseSignatureDataFormatter
269
	 * @return Response
270
	 *
271
	 * @throws InvalidSignatureException
272
	 * @throws PrivateKeyFileException
273
	 * @throws SigningFailedException
274
	 * @throws PublicKeyFileException
275
	 * @throws VerificationFailedException
276
	 */
277 1
	public function createResponseByData(array $data, SignatureDataFormatter $responseSignatureDataFormatter): Response
278
	{
279 1
		$response = new Response(
280 1
			new ResponseCode(ResponseCode::S200_OK),
281
			$data
282
		);
283
284 1
		$this->logRequest(new HttpMethod(HttpMethod::GET), 'payment/response', [], [], $response);
285
286 1
		return new Response(
287 1
			$response->getResponseCode(),
288 1
			$this->decodeData($data, $responseSignatureDataFormatter),
289 1
			$response->getHeaders()
290
		);
291
	}
292
293
	/**
294
	 * @param mixed[] $data
295
	 * @param SignatureDataFormatter $signatureDataFormatter
296
	 * @return mixed[]
297
	 *
298
	 * @throws PrivateKeyFileException
299
	 * @throws SigningFailedException
300
	 */
301 15
	private function prepareData(array $data, SignatureDataFormatter $signatureDataFormatter): array
302
	{
303 15
		$data['dttm'] = (new DateTimeImmutable())->format('YmdHis');
304 15
		$data['signature'] = $this->cryptoService->signData($data, $signatureDataFormatter);
305
306 15
		return $data;
307
	}
308
309
	/**
310
	 * @param mixed[] $responseData
311
	 * @param SignatureDataFormatter $signatureDataFormatter
312
	 * @return mixed[]
313
	 *
314
	 * @throws InvalidSignatureException
315
	 * @throws PublicKeyFileException
316
	 * @throws VerificationFailedException
317
	 */
318 8
	private function decodeData(array $responseData, SignatureDataFormatter $signatureDataFormatter): array
319
	{
320 8
		if (!array_key_exists('signature', $responseData)) {
321 1
			throw new InvalidSignatureException($responseData);
322
		}
323
324 7
		$signature = $responseData['signature'];
325 7
		unset($responseData['signature']);
326
327 7
		if (!$this->cryptoService->verifyData($responseData, $signature, $signatureDataFormatter)) {
328 1
			throw new InvalidSignatureException($responseData);
329
		}
330
331 6
		return $responseData;
332
	}
333
334 16
	private function logRequest(HttpMethod $method, string $url, array $queries, array $requestData = null, Response $response)
335
	{
336 16
		if ($this->logger === null) {
337 11
			return;
338
		}
339
340 5
		$responseData = $response->getData();
341
342 5
		unset($requestData['signature']);
343 5
		unset($queries['signature']);
344 5
		unset($responseData['signature']);
345
346 5
		if (isset($responseData['extensions'])) {
347
			foreach ($responseData['extensions'] as $key => $extensionData) {
348
				unset($responseData['extensions'][$key]['signature']);
349
			}
350
		}
351
		$context = [
352
			'request' => [
353 5
				'method' => $method->getValue(),
354 5
				'queries' => $queries,
355 5
				'data' => $requestData,
356
			],
357
			'response' => [
358 5
				'code' => $response->getResponseCode()->getValue(),
359 5
				'data' => $responseData,
360
			],
361
		];
362
363 5
		$this->logger->info($url, $context);
364 5
	}
365
366
}
367