Completed
Push — master ( 3140c3...1d6b96 )
by Jan
07:07
created

ApiClient::put()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 18
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 18
ccs 9
cts 9
cp 1
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 14
nc 1
nop 5
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|null
35
	 */
36
	private $apiUrl;
37
38 16
	public function __construct(
39
		ApiClientDriver $driver,
40
		CryptoService $cryptoService,
41
		string $apiUrl
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): void
50
	{
51 5
		$this->logger = $logger;
52 5
	}
53
54
	/**
55
	 * @param string $url
56
	 * @param mixed[] $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
	public function get(
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
			HttpMethod::get(HttpMethod::GET),
82 13
			$url,
83 13
			$this->prepareData($data, $requestSignatureDataFormatter),
84 13
			null,
85 13
			$responseSignatureDataFormatter,
86 13
			$responseValidityCallback,
87 13
			$extensions
88
		);
89
	}
90
91
	/**
92
	 * @param string $url
93
	 * @param mixed[] $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
	public function post(
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
			HttpMethod::get(HttpMethod::POST),
117 1
			$url,
118 1
			[],
119 1
			$this->prepareData($data, $requestSignatureDataFormatter),
120 1
			$responseSignatureDataFormatter,
121 1
			null,
122 1
			$extensions
123
		);
124
	}
125
126
	/**
127
	 * @param string $url
128
	 * @param mixed[] $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
	public function put(
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
			HttpMethod::get(HttpMethod::PUT),
152 1
			$url,
153 1
			[],
154 1
			$this->prepareData($data, $requestSignatureDataFormatter),
155 1
			$responseSignatureDataFormatter,
156 1
			null,
157 1
			$extensions
158
		);
159
	}
160
161
	/**
162
	 * @param HttpMethod $method
163
	 * @param string $url
164
	 * @param mixed[] $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
	private function request(
180
		HttpMethod $method,
181
		string $url,
182
		array $queries = [],
183
		?array $data,
184
		SignatureDataFormatter $responseSignatureDataFormatter,
185
		?\Closure $responseValidityCallback,
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);
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 15
			$method,
206 15
			$this->apiUrl . '/' . $url,
207 15
			$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 5
				$responseData,
233 5
				$response->getHeaders(),
234 5
				$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
			ResponseCode::get(ResponseCode::S200_OK),
281 1
			$data
282
		);
283
284 1
		$this->logRequest(HttpMethod::get(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
	/**
335
	 * @param \SlevomatCsobGateway\Api\HttpMethod $method
336
	 * @param string $url
337
	 * @param mixed[] $queries
338
	 * @param mixed[]|null $requestData
339
	 * @param \SlevomatCsobGateway\Api\Response $response
340
	 */
341 16
	private function logRequest(HttpMethod $method, string $url, array $queries, ?array $requestData, Response $response): void
342
	{
343 16
		if ($this->logger === null) {
344 11
			return;
345
		}
346
347 5
		$responseData = $response->getData();
348
349 5
		unset($requestData['signature']);
350 5
		unset($queries['signature']);
351 5
		unset($responseData['signature']);
352
353 5
		if (isset($responseData['extensions'])) {
354
			foreach ($responseData['extensions'] as $key => $extensionData) {
355
				unset($responseData['extensions'][$key]['signature']);
356
			}
357
		}
358
		$context = [
359
			'request' => [
360 5
				'method' => $method->getValue(),
361 5
				'queries' => $queries,
362 5
				'data' => $requestData,
363
			],
364
			'response' => [
365 5
				'code' => $response->getResponseCode()->getValue(),
366 5
				'data' => $responseData,
367 5
				'headers' => $response->getHeaders(),
368
			],
369
		];
370
371 5
		$this->logger->info($url, $context);
372 5
	}
373
374
}
375