|
1
|
|
|
<?php declare(strict_types = 1); |
|
2
|
|
|
|
|
3
|
|
|
namespace SlevomatCsobGateway\Api; |
|
4
|
|
|
|
|
5
|
|
|
use DateTimeImmutable; |
|
6
|
|
|
use SlevomatCsobGateway\Call\ResponseExtensionHandler; |
|
7
|
|
|
use SlevomatCsobGateway\Crypto\CryptoService; |
|
8
|
|
|
use SlevomatCsobGateway\Crypto\PrivateKeyFileException; |
|
9
|
|
|
use SlevomatCsobGateway\Crypto\PublicKeyFileException; |
|
10
|
|
|
use SlevomatCsobGateway\Crypto\SignatureDataFormatter; |
|
11
|
|
|
use SlevomatCsobGateway\Crypto\SigningFailedException; |
|
12
|
|
|
use SlevomatCsobGateway\Crypto\VerificationFailedException; |
|
13
|
|
|
|
|
14
|
|
|
class ApiClient |
|
15
|
|
|
{ |
|
16
|
|
|
|
|
17
|
|
|
/** |
|
18
|
|
|
* @var ApiClientDriver |
|
19
|
|
|
*/ |
|
20
|
|
|
private $driver; |
|
21
|
|
|
|
|
22
|
|
|
/** |
|
23
|
|
|
* @var CryptoService |
|
24
|
|
|
*/ |
|
25
|
|
|
private $cryptoService; |
|
26
|
|
|
|
|
27
|
|
|
/** |
|
28
|
|
|
* @var string |
|
29
|
|
|
*/ |
|
30
|
|
|
private $apiUrl; |
|
31
|
|
|
|
|
32
|
|
|
public function __construct( |
|
33
|
|
|
ApiClientDriver $driver, |
|
34
|
|
|
CryptoService $cryptoService, |
|
35
|
|
|
string $apiUrl = null |
|
36
|
|
|
) |
|
37
|
|
|
{ |
|
38
|
|
|
$this->driver = $driver; |
|
39
|
|
|
$this->cryptoService = $cryptoService; |
|
40
|
|
|
$this->apiUrl = $apiUrl; |
|
41
|
|
|
} |
|
42
|
|
|
|
|
43
|
|
|
/** |
|
44
|
|
|
* @param string $url |
|
45
|
|
|
* @param mixed[]|null $data |
|
46
|
|
|
* @param SignatureDataFormatter $requestSignatureDataFormatter |
|
47
|
|
|
* @param SignatureDataFormatter $responseSignatureDataFormatter |
|
48
|
|
|
* @param \Closure|null $responseValidityCallback |
|
49
|
|
|
* @param ResponseExtensionHandler[] $extensions |
|
50
|
|
|
* @return Response |
|
51
|
|
|
* |
|
52
|
|
|
* @throws PrivateKeyFileException |
|
53
|
|
|
* @throws SigningFailedException |
|
54
|
|
|
* @throws PublicKeyFileException |
|
55
|
|
|
* @throws VerificationFailedException |
|
56
|
|
|
* @throws RequestException |
|
57
|
|
|
* @throws ApiClientDriverException |
|
58
|
|
|
* @throws InvalidSignatureException |
|
59
|
|
|
*/ |
|
60
|
|
View Code Duplication |
public function get( |
|
|
|
|
|
|
61
|
|
|
string $url, |
|
62
|
|
|
array $data = [], |
|
63
|
|
|
SignatureDataFormatter $requestSignatureDataFormatter, |
|
64
|
|
|
SignatureDataFormatter $responseSignatureDataFormatter, |
|
65
|
|
|
\Closure $responseValidityCallback = null, |
|
66
|
|
|
array $extensions = [] |
|
67
|
|
|
): Response |
|
68
|
|
|
{ |
|
69
|
|
|
return $this->request( |
|
70
|
|
|
new HttpMethod(HttpMethod::GET), |
|
71
|
|
|
$url, |
|
72
|
|
|
$this->prepareData($data, $requestSignatureDataFormatter), |
|
73
|
|
|
null, |
|
74
|
|
|
$responseSignatureDataFormatter, |
|
75
|
|
|
$responseValidityCallback, |
|
76
|
|
|
$extensions |
|
77
|
|
|
); |
|
78
|
|
|
} |
|
79
|
|
|
|
|
80
|
|
|
/** |
|
81
|
|
|
* @param string $url |
|
82
|
|
|
* @param mixed[]|null $data |
|
83
|
|
|
* @param SignatureDataFormatter $requestSignatureDataFormatter |
|
84
|
|
|
* @param SignatureDataFormatter $responseSignatureDataFormatter |
|
85
|
|
|
* @param ResponseExtensionHandler[] $extensions |
|
86
|
|
|
* @return Response |
|
87
|
|
|
* |
|
88
|
|
|
* @throws PrivateKeyFileException |
|
89
|
|
|
* @throws SigningFailedException |
|
90
|
|
|
* @throws PublicKeyFileException |
|
91
|
|
|
* @throws VerificationFailedException |
|
92
|
|
|
* @throws RequestException |
|
93
|
|
|
* @throws ApiClientDriverException |
|
94
|
|
|
* @throws InvalidSignatureException |
|
95
|
|
|
*/ |
|
96
|
|
View Code Duplication |
public function post( |
|
|
|
|
|
|
97
|
|
|
string $url, |
|
98
|
|
|
array $data = [], |
|
99
|
|
|
SignatureDataFormatter $requestSignatureDataFormatter, |
|
100
|
|
|
SignatureDataFormatter $responseSignatureDataFormatter, |
|
101
|
|
|
array $extensions = [] |
|
102
|
|
|
): Response |
|
103
|
|
|
{ |
|
104
|
|
|
return $this->request( |
|
105
|
|
|
new HttpMethod(HttpMethod::POST), |
|
106
|
|
|
$url, |
|
107
|
|
|
[], |
|
108
|
|
|
$this->prepareData($data, $requestSignatureDataFormatter), |
|
109
|
|
|
$responseSignatureDataFormatter, |
|
110
|
|
|
null, |
|
111
|
|
|
$extensions |
|
112
|
|
|
); |
|
113
|
|
|
} |
|
114
|
|
|
|
|
115
|
|
|
/** |
|
116
|
|
|
* @param string $url |
|
117
|
|
|
* @param mixed[]|null $data |
|
118
|
|
|
* @param SignatureDataFormatter $requestSignatureDataFormatter |
|
119
|
|
|
* @param SignatureDataFormatter $responseSignatureDataFormatter |
|
120
|
|
|
* @param ResponseExtensionHandler[] $extensions |
|
121
|
|
|
* @return Response |
|
122
|
|
|
* |
|
123
|
|
|
* @throws PrivateKeyFileException |
|
124
|
|
|
* @throws SigningFailedException |
|
125
|
|
|
* @throws PublicKeyFileException |
|
126
|
|
|
* @throws VerificationFailedException |
|
127
|
|
|
* @throws RequestException |
|
128
|
|
|
* @throws ApiClientDriverException |
|
129
|
|
|
* @throws InvalidSignatureException |
|
130
|
|
|
*/ |
|
131
|
|
View Code Duplication |
public function put( |
|
|
|
|
|
|
132
|
|
|
string $url, |
|
133
|
|
|
array $data = [], |
|
134
|
|
|
SignatureDataFormatter $requestSignatureDataFormatter, |
|
135
|
|
|
SignatureDataFormatter $responseSignatureDataFormatter, |
|
136
|
|
|
array $extensions = [] |
|
137
|
|
|
): Response |
|
138
|
|
|
{ |
|
139
|
|
|
return $this->request( |
|
140
|
|
|
new HttpMethod(HttpMethod::PUT), |
|
141
|
|
|
$url, |
|
142
|
|
|
[], |
|
143
|
|
|
$this->prepareData($data, $requestSignatureDataFormatter), |
|
144
|
|
|
$responseSignatureDataFormatter, |
|
145
|
|
|
null, |
|
146
|
|
|
$extensions |
|
147
|
|
|
); |
|
148
|
|
|
} |
|
149
|
|
|
|
|
150
|
|
|
/** |
|
151
|
|
|
* @param HttpMethod $method |
|
152
|
|
|
* @param string $url |
|
153
|
|
|
* @param string[] $queries |
|
154
|
|
|
* @param mixed[]|null $data |
|
155
|
|
|
* @param SignatureDataFormatter $responseSignatureDataFormatter |
|
156
|
|
|
* @param \Closure|null $responseValidityCallback |
|
157
|
|
|
* @param ResponseExtensionHandler[] $extensions |
|
158
|
|
|
* @return Response |
|
159
|
|
|
* |
|
160
|
|
|
* @throws PrivateKeyFileException |
|
161
|
|
|
* @throws SigningFailedException |
|
162
|
|
|
* @throws PublicKeyFileException |
|
163
|
|
|
* @throws VerificationFailedException |
|
164
|
|
|
* @throws RequestException |
|
165
|
|
|
* @throws ApiClientDriverException |
|
166
|
|
|
* @throws InvalidSignatureException |
|
167
|
|
|
*/ |
|
168
|
|
|
public function request( |
|
169
|
|
|
HttpMethod $method, |
|
170
|
|
|
string $url, |
|
171
|
|
|
array $queries = [], |
|
172
|
|
|
array $data = null, |
|
173
|
|
|
SignatureDataFormatter $responseSignatureDataFormatter, |
|
174
|
|
|
\Closure $responseValidityCallback = null, |
|
175
|
|
|
array $extensions |
|
176
|
|
|
): Response |
|
177
|
|
|
{ |
|
178
|
|
|
foreach ($queries as $key => $value) { |
|
179
|
|
|
if (strpos($url, '{' . $key . '}') !== false) { |
|
180
|
|
|
$url = str_replace('{' . $key . '}', urlencode((string) $value), $url); |
|
181
|
|
|
unset($queries[$key]); |
|
182
|
|
|
} |
|
183
|
|
|
} |
|
184
|
|
|
|
|
185
|
|
|
if ($queries !== []) { |
|
186
|
|
|
throw new \InvalidArgumentException('Arguments are missing URL placeholders: ' . json_encode($queries)); |
|
187
|
|
|
} |
|
188
|
|
|
|
|
189
|
|
|
$response = $this->driver->request( |
|
190
|
|
|
$method, |
|
191
|
|
|
$this->apiUrl . '/' . $url, |
|
192
|
|
|
$data |
|
193
|
|
|
); |
|
194
|
|
|
|
|
195
|
|
|
if ($responseValidityCallback !== null) { |
|
196
|
|
|
$responseValidityCallback($response); |
|
197
|
|
|
} |
|
198
|
|
|
|
|
199
|
|
|
if ($response->getResponseCode()->equalsValue(ResponseCode::S200_OK)) { |
|
200
|
|
|
$decodedExtensions = []; |
|
201
|
|
|
if ($extensions !== [] && array_key_exists('extensions', $response->getData())) { |
|
202
|
|
|
foreach ($response->getData()['extensions'] as $extensionData) { |
|
203
|
|
|
$name = $extensionData['extension']; |
|
204
|
|
|
if (isset($extensions[$name])) { |
|
205
|
|
|
/** @var ResponseExtensionHandler $handler */ |
|
206
|
|
|
$handler = $extensions[$name]; |
|
207
|
|
|
unset($extensionData['extension']); |
|
208
|
|
|
$decodedExtensions[$name] = $handler->createResponse($this->decodeData($extensionData, $handler->getSignatureDataFormatter())); |
|
209
|
|
|
} |
|
210
|
|
|
} |
|
211
|
|
|
} |
|
212
|
|
|
$responseData = $this->decodeData($response->getData(), $responseSignatureDataFormatter); |
|
213
|
|
|
unset($responseData['extensions']); |
|
214
|
|
|
|
|
215
|
|
|
return new Response( |
|
216
|
|
|
$response->getResponseCode(), |
|
217
|
|
|
$responseData, |
|
218
|
|
|
$response->getHeaders(), |
|
219
|
|
|
$decodedExtensions |
|
220
|
|
|
); |
|
221
|
|
|
|
|
222
|
|
|
} elseif ($response->getResponseCode()->equalsValue(ResponseCode::S303_SEE_OTHER)) { |
|
223
|
|
|
return new Response( |
|
224
|
|
|
$response->getResponseCode(), |
|
225
|
|
|
null, |
|
226
|
|
|
$response->getHeaders() |
|
227
|
|
|
); |
|
228
|
|
|
|
|
229
|
|
|
} elseif ($response->getResponseCode()->equalsValue(ResponseCode::S400_BAD_REQUEST)) { |
|
230
|
|
|
throw new BadRequestException($response); |
|
231
|
|
|
|
|
232
|
|
|
} elseif ($response->getResponseCode()->equalsValue(ResponseCode::S403_FORBIDDEN)) { |
|
233
|
|
|
throw new ForbiddenException($response); |
|
234
|
|
|
|
|
235
|
|
|
} elseif ($response->getResponseCode()->equalsValue(ResponseCode::S404_NOT_FOUND)) { |
|
236
|
|
|
throw new NotFoundException($response); |
|
237
|
|
|
|
|
238
|
|
|
} elseif ($response->getResponseCode()->equalsValue(ResponseCode::S405_METHOD_NOT_ALLOWED)) { |
|
239
|
|
|
throw new MethodNotAllowedException($response); |
|
240
|
|
|
|
|
241
|
|
|
} elseif ($response->getResponseCode()->equalsValue(ResponseCode::S429_TOO_MANY_REQUESTS)) { |
|
242
|
|
|
throw new TooManyRequestsException($response); |
|
243
|
|
|
|
|
244
|
|
|
} elseif ($response->getResponseCode()->equalsValue(ResponseCode::S503_SERVICE_UNAVAILABLE)) { |
|
245
|
|
|
throw new ServiceUnavailableException($response); |
|
246
|
|
|
} |
|
247
|
|
|
|
|
248
|
|
|
throw new InternalErrorException($response); |
|
249
|
|
|
} |
|
250
|
|
|
|
|
251
|
|
|
/** |
|
252
|
|
|
* @param mixed[] $data |
|
253
|
|
|
* @param SignatureDataFormatter $responseSignatureDataFormatter |
|
254
|
|
|
* @return Response |
|
255
|
|
|
* |
|
256
|
|
|
* @throws InvalidSignatureException |
|
257
|
|
|
* @throws PrivateKeyFileException |
|
258
|
|
|
* @throws SigningFailedException |
|
259
|
|
|
* @throws PublicKeyFileException |
|
260
|
|
|
* @throws VerificationFailedException |
|
261
|
|
|
*/ |
|
262
|
|
|
public function createResponseByData(array $data, SignatureDataFormatter $responseSignatureDataFormatter): Response |
|
263
|
|
|
{ |
|
264
|
|
|
$response = new Response( |
|
265
|
|
|
new ResponseCode(ResponseCode::S200_OK), |
|
266
|
|
|
$data |
|
267
|
|
|
); |
|
268
|
|
|
|
|
269
|
|
|
return new Response( |
|
270
|
|
|
$response->getResponseCode(), |
|
271
|
|
|
$this->decodeData($data, $responseSignatureDataFormatter), |
|
272
|
|
|
$response->getHeaders() |
|
273
|
|
|
); |
|
274
|
|
|
} |
|
275
|
|
|
|
|
276
|
|
|
/** |
|
277
|
|
|
* @param mixed[] $data |
|
278
|
|
|
* @param SignatureDataFormatter $signatureDataFormatter |
|
279
|
|
|
* @return mixed[] |
|
280
|
|
|
* |
|
281
|
|
|
* @throws PrivateKeyFileException |
|
282
|
|
|
* @throws SigningFailedException |
|
283
|
|
|
*/ |
|
284
|
|
|
private function prepareData(array $data, SignatureDataFormatter $signatureDataFormatter): array |
|
285
|
|
|
{ |
|
286
|
|
|
$data['dttm'] = (new DateTimeImmutable())->format('YmdHis'); |
|
287
|
|
|
$data['signature'] = $this->cryptoService->signData($data, $signatureDataFormatter); |
|
288
|
|
|
|
|
289
|
|
|
return $data; |
|
290
|
|
|
} |
|
291
|
|
|
|
|
292
|
|
|
/** |
|
293
|
|
|
* @param array $responseData |
|
294
|
|
|
* @param SignatureDataFormatter $signatureDataFormatter |
|
295
|
|
|
* @return mixed[] |
|
296
|
|
|
* |
|
297
|
|
|
* @throws InvalidSignatureException |
|
298
|
|
|
* @throws PublicKeyFileException |
|
299
|
|
|
* @throws VerificationFailedException |
|
300
|
|
|
*/ |
|
301
|
|
|
private function decodeData(array $responseData, SignatureDataFormatter $signatureDataFormatter): array |
|
302
|
|
|
{ |
|
303
|
|
|
if (!array_key_exists('signature', $responseData)) { |
|
304
|
|
|
throw new InvalidSignatureException($responseData); |
|
305
|
|
|
} |
|
306
|
|
|
|
|
307
|
|
|
$signature = $responseData['signature']; |
|
308
|
|
|
unset($responseData['signature']); |
|
309
|
|
|
|
|
310
|
|
|
if (!$this->cryptoService->verifyData($responseData, $signature, $signatureDataFormatter)) { |
|
311
|
|
|
throw new InvalidSignatureException($responseData); |
|
312
|
|
|
} |
|
313
|
|
|
|
|
314
|
|
|
return $responseData; |
|
315
|
|
|
} |
|
316
|
|
|
|
|
317
|
|
|
} |
|
318
|
|
|
|
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.