1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace Covery\Client; |
4
|
|
|
|
5
|
|
|
use Covery\Client\Envelopes\ValidatorV1; |
6
|
|
|
use Covery\Client\Requests\Decision; |
7
|
|
|
use Covery\Client\Requests\Event; |
8
|
|
|
use Covery\Client\Requests\KycProof; |
9
|
|
|
use Covery\Client\Requests\Ping; |
10
|
|
|
use Covery\Client\Requests\Postback; |
11
|
|
|
use Psr\Http\Message\RequestInterface; |
12
|
|
|
use Psr\Http\Message\ResponseInterface; |
13
|
|
|
use Psr\Log\LoggerInterface; |
14
|
|
|
use Psr\Log\NullLogger; |
15
|
|
|
|
16
|
|
|
class PublicAPIClient |
17
|
|
|
{ |
18
|
|
|
/** |
19
|
|
|
* @var CredentialsInterface |
20
|
|
|
*/ |
21
|
|
|
private $credentials; |
22
|
|
|
|
23
|
|
|
/** |
24
|
|
|
* @var TransportInterface |
25
|
|
|
*/ |
26
|
|
|
private $transport; |
27
|
|
|
|
28
|
|
|
/** |
29
|
|
|
* @var LoggerInterface |
30
|
|
|
*/ |
31
|
|
|
private $logger; |
32
|
|
|
|
33
|
|
|
/** |
34
|
|
|
* @var ValidatorV1 |
35
|
|
|
*/ |
36
|
|
|
private $validator; |
37
|
|
|
|
38
|
|
|
/** |
39
|
|
|
* Client constructor. |
40
|
|
|
* @param CredentialsInterface $credentials |
41
|
|
|
* @param TransportInterface $transport |
42
|
|
|
* @param LoggerInterface|null $logger |
43
|
|
|
*/ |
44
|
|
|
public function __construct( |
45
|
|
|
CredentialsInterface $credentials, |
46
|
|
|
TransportInterface $transport, |
47
|
|
|
LoggerInterface $logger = null |
48
|
|
|
) { |
49
|
|
|
$this->credentials = $credentials; |
50
|
|
|
$this->transport = $transport; |
51
|
|
|
$this->logger = $logger === null ? new NullLogger() : $logger; |
52
|
|
|
$this->validator = new ValidatorV1(); |
53
|
|
|
} |
54
|
|
|
|
55
|
|
|
/** |
56
|
|
|
* Sends PSR-7 compatible request to Covery and returns |
57
|
|
|
* |
58
|
|
|
* @param RequestInterface $request |
59
|
|
|
* @return string |
60
|
|
|
* @throws IoException |
61
|
|
|
*/ |
62
|
|
|
public function send(RequestInterface $request) |
63
|
|
|
{ |
64
|
|
|
$request = $this->prepareRequest($request); |
65
|
|
|
try { |
66
|
|
|
$this->logger->info('Sending request to ' . $request->getUri()); |
67
|
|
|
$before = microtime(true); |
68
|
|
|
$response = $this->transport->send($request); |
69
|
|
|
$this->logger->info(sprintf('Request done in %.2f', microtime(true) - $before)); |
70
|
|
|
} catch (\Exception $inner) { |
71
|
|
|
$this->logger->error($inner->getMessage(), ['exception' => $inner]); |
72
|
|
|
// Wrapping exception |
73
|
|
|
throw new IoException('Error sending request', 0, $inner); |
74
|
|
|
} |
75
|
|
|
$code = $response->getStatusCode(); |
76
|
|
|
$this->logger->debug('Received status code ' . $code); |
77
|
|
|
|
78
|
|
|
if ($code >= 400) { |
79
|
|
|
$this->handleNot200($response); |
80
|
|
|
} |
81
|
|
|
|
82
|
|
|
return $response->getBody()->getContents(); |
83
|
|
|
} |
84
|
|
|
|
85
|
|
|
/** |
86
|
|
|
* Utility method, that prepares and signs request |
87
|
|
|
* |
88
|
|
|
* @param RequestInterface $request |
89
|
|
|
* @return RequestInterface |
90
|
|
|
*/ |
91
|
|
|
private function prepareRequest(RequestInterface $request) |
92
|
|
|
{ |
93
|
|
|
// Checking hostname presence |
94
|
|
|
$uri = $request->getUri(); |
95
|
|
|
if ($uri->getHost() == '') { |
96
|
|
|
$request = $request->withUri( |
97
|
|
|
$uri->withHost(TransportInterface::DEFAULT_HOST)->withScheme(TransportInterface::DEFAULT_SCHEME) |
98
|
|
|
); |
99
|
|
|
} |
100
|
|
|
|
101
|
|
|
return $this->credentials->signRequest($request); |
102
|
|
|
} |
103
|
|
|
|
104
|
|
|
/** |
105
|
|
|
* Utility function, that handles error response from Covery |
106
|
|
|
* |
107
|
|
|
* @param ResponseInterface $response |
108
|
|
|
* @throws Exception |
109
|
|
|
*/ |
110
|
|
|
private function handleNot200(ResponseInterface $response) |
111
|
|
|
{ |
112
|
|
|
// Analyzing response |
113
|
|
|
if ($response->hasHeader('X-Maxwell-Status') && $response->hasHeader('X-Maxwell-Error-Message')) { |
114
|
|
|
// Extended data available |
115
|
|
|
$message = $response->getHeaderLine('X-Maxwell-Error-Message'); |
116
|
|
|
$type = $response->getHeaderLine('X-Maxwell-Error-Type'); |
117
|
|
|
if (strpos($type, 'AuthorizationRequiredException') !== false) { |
118
|
|
|
$this->logger->error('Authentication failure ' . $message); |
119
|
|
|
throw new AuthException($message, $response->getStatusCode()); |
120
|
|
|
} |
121
|
|
|
|
122
|
|
|
switch ($message) { |
123
|
|
|
case 'Empty auth token': |
124
|
|
|
case 'Empty signature': |
125
|
|
|
case 'Empty nonce': |
126
|
|
|
$this->logger->error('Authentication failure ' . $message); |
127
|
|
|
throw new AuthException($message, $response->getStatusCode()); |
128
|
|
|
} |
129
|
|
|
|
130
|
|
|
$this->logger->error('Covery error ' . $message); |
131
|
|
|
throw new DeliveredException($message, $response->getStatusCode()); |
132
|
|
|
} elseif ($response->hasHeader('X-General-Failure')) { |
133
|
|
|
// Remote fatal error |
134
|
|
|
throw new DeliveredException('Antifraud fatal error', $response->getStatusCode()); |
135
|
|
|
} |
136
|
|
|
|
137
|
|
|
throw new Exception("Communication failed with status code {$response->getStatusCode()}"); |
138
|
|
|
} |
139
|
|
|
|
140
|
|
|
/** |
141
|
|
|
* Utility method, that reads JSON data |
142
|
|
|
* |
143
|
|
|
* @param $string |
144
|
|
|
* @return mixed|null |
145
|
|
|
* @throws Exception |
146
|
|
|
*/ |
147
|
|
|
private function readJson($string) |
148
|
|
|
{ |
149
|
|
|
if (!is_string($string)) { |
150
|
|
|
throw new Exception("Unable to read JSON - not a string received"); |
151
|
|
|
} |
152
|
|
|
if (strlen($string) === 0) { |
153
|
|
|
return null; |
154
|
|
|
} |
155
|
|
|
|
156
|
|
|
$data = json_decode($string, true); |
157
|
|
|
if ($data === null) { |
158
|
|
|
$message = 'Unable to decode JSON'; |
159
|
|
|
if (function_exists('json_last_error_msg')) { |
160
|
|
|
$message = json_last_error_msg(); |
161
|
|
|
} |
162
|
|
|
|
163
|
|
|
throw new Exception($message); |
164
|
|
|
} |
165
|
|
|
|
166
|
|
|
return $data; |
167
|
|
|
} |
168
|
|
|
|
169
|
|
|
/** |
170
|
|
|
* Sends request to Covery and returns access level, associated with |
171
|
|
|
* used credentials |
172
|
|
|
* |
173
|
|
|
* This method can be used for Covery health check and availability |
174
|
|
|
* On any problem (network, credentials, server side) this method |
175
|
|
|
* will throw an exception |
176
|
|
|
* |
177
|
|
|
* @return string |
178
|
|
|
* @throws Exception |
179
|
|
|
*/ |
180
|
|
|
public function ping() |
181
|
|
|
{ |
182
|
|
|
$data = $this->readJson($this->send(new Ping())); |
183
|
|
|
if (!is_array($data) || !isset($data['level'])) { |
184
|
|
|
throw new Exception("Malformed response"); |
185
|
|
|
} |
186
|
|
|
|
187
|
|
|
return $data['level']; |
188
|
|
|
} |
189
|
|
|
|
190
|
|
|
/** |
191
|
|
|
* Sends envelope to Covery and returns it's ID on Covery side |
192
|
|
|
* Before sending, validation is performed |
193
|
|
|
* |
194
|
|
|
* @param EnvelopeInterface $envelope |
195
|
|
|
* @return int |
196
|
|
|
* @throws Exception |
197
|
|
|
*/ |
198
|
|
View Code Duplication |
public function sendEvent(EnvelopeInterface $envelope) |
|
|
|
|
199
|
|
|
{ |
200
|
|
|
// Validating |
201
|
|
|
$this->validator->validate($envelope); |
202
|
|
|
|
203
|
|
|
// Sending |
204
|
|
|
$data = $this->readJson($this->send(new Event($envelope))); |
205
|
|
|
|
206
|
|
|
if (!is_array($data) || !isset($data['requestId']) || !is_int($data['requestId'])) { |
207
|
|
|
throw new Exception("Malformed response"); |
208
|
|
|
} |
209
|
|
|
|
210
|
|
|
return $data['requestId']; |
211
|
|
|
} |
212
|
|
|
|
213
|
|
|
/** |
214
|
|
|
* Sends postback envelope to Covery and returns it's ID on Covery side |
215
|
|
|
* Before sending, validation is performed |
216
|
|
|
* |
217
|
|
|
* @param EnvelopeInterface $envelope |
218
|
|
|
* @return int |
219
|
|
|
* @throws Exception |
220
|
|
|
*/ |
221
|
|
View Code Duplication |
public function sendPostback(EnvelopeInterface $envelope) |
|
|
|
|
222
|
|
|
{ |
223
|
|
|
// Validating |
224
|
|
|
$this->validator->validate($envelope); |
225
|
|
|
|
226
|
|
|
// Sending |
227
|
|
|
$data = $this->readJson($this->send(new Postback($envelope))); |
228
|
|
|
|
229
|
|
|
if (!is_array($data) || !isset($data['requestId']) || empty($data['requestId']) || !is_int($data['requestId'])) { |
230
|
|
|
throw new Exception("Malformed response"); |
231
|
|
|
} |
232
|
|
|
|
233
|
|
|
return $data['requestId']; |
234
|
|
|
} |
235
|
|
|
|
236
|
|
|
/** |
237
|
|
|
* Sends envelope to Covery for analysis |
238
|
|
|
* |
239
|
|
|
* @param EnvelopeInterface $envelope |
240
|
|
|
* @return Result |
241
|
|
|
* @throws Exception |
242
|
|
|
*/ |
243
|
|
|
public function makeDecision(EnvelopeInterface $envelope) |
244
|
|
|
{ |
245
|
|
|
// Validating |
246
|
|
|
$this->validator->validate($envelope); |
247
|
|
|
|
248
|
|
|
// Sending |
249
|
|
|
$data = $this->readJson($this->send(new Decision($envelope))); |
250
|
|
|
|
251
|
|
|
if (!is_array($data)) { |
252
|
|
|
throw new Exception("Malformed response"); |
253
|
|
|
} |
254
|
|
|
|
255
|
|
|
try { |
256
|
|
|
return new Result( |
257
|
|
|
$data[ResultBaseField::REQUEST_ID], |
258
|
|
|
$data[ResultBaseField::TYPE], |
259
|
|
|
$data[ResultBaseField::CREATED_AT], |
260
|
|
|
$data[ResultBaseField::SEQUENCE_ID], |
261
|
|
|
$data[ResultBaseField::MERCHANT_USER_ID], |
262
|
|
|
$data[ResultBaseField::SCORE], |
263
|
|
|
$data[ResultBaseField::ACCEPT], |
264
|
|
|
$data[ResultBaseField::REJECT], |
265
|
|
|
$data[ResultBaseField::MANUAL], |
266
|
|
|
isset($data[ResultBaseField::REASON]) ? $data[ResultBaseField::REASON] : null, |
267
|
|
|
isset($data[ResultBaseField::ACTION]) ? $data[ResultBaseField::ACTION] : null, |
268
|
|
|
array_filter($data, function ($field) { |
269
|
|
|
return !in_array($field, ResultBaseField::getAll()); |
270
|
|
|
}, ARRAY_FILTER_USE_KEY) |
271
|
|
|
); |
272
|
|
|
} catch (\Exception $error) { |
273
|
|
|
throw new Exception('Malformed response', 0, $error); |
274
|
|
|
} |
275
|
|
|
} |
276
|
|
|
|
277
|
|
|
/** |
278
|
|
|
* Sends kycProof envelope to Covery and returns KycProofResult on Covery side |
279
|
|
|
* |
280
|
|
|
* @param EnvelopeInterface $envelope |
281
|
|
|
* @return KycProofResult |
282
|
|
|
* @throws EnvelopeValidationException |
283
|
|
|
* @throws Exception |
284
|
|
|
* @throws IoException |
285
|
|
|
*/ |
286
|
|
|
public function sendKycProof(EnvelopeInterface $envelope) |
287
|
|
|
{ |
288
|
|
|
// Validating |
289
|
|
|
$this->validator->validate($envelope); |
290
|
|
|
|
291
|
|
|
// Sending |
292
|
|
|
$data = $this->readJson($this->send(new KycProof($envelope))); |
293
|
|
|
|
294
|
|
|
if (!is_array($data)) { |
295
|
|
|
throw new Exception("Malformed response"); |
296
|
|
|
} |
297
|
|
|
|
298
|
|
|
try { |
299
|
|
|
return new KycProofResult( |
300
|
|
|
$data[KycProofResultBaseField::REQUEST_ID], |
301
|
|
|
$data[KycProofResultBaseField::TYPE], |
302
|
|
|
$data[KycProofResultBaseField::CREATED_AT], |
303
|
|
|
isset($data[KycProofResultBaseField::VERIFICATION_VIDEO]) ? $data[KycProofResultBaseField::VERIFICATION_VIDEO] : null, |
304
|
|
|
isset($data[KycProofResultBaseField::FACE_PROOF]) ? $data[KycProofResultBaseField::FACE_PROOF] : null, |
305
|
|
|
isset($data[KycProofResultBaseField::DOCUMENT_PROOF]) ? $data[KycProofResultBaseField::DOCUMENT_PROOF] : null, |
306
|
|
|
isset($data[KycProofResultBaseField::DOCUMENT_TWO_PROOF]) ? $data[KycProofResultBaseField::DOCUMENT_TWO_PROOF] : null, |
307
|
|
|
isset($data[KycProofResultBaseField::CONSENT_PROOF]) ? $data[KycProofResultBaseField::CONSENT_PROOF] : null, |
308
|
|
|
array_filter($data, function ($field) { |
|
|
|
|
309
|
|
|
return !in_array($field, KycProofResultBaseField::getAll()); |
310
|
|
|
}, ARRAY_FILTER_USE_KEY) |
311
|
|
|
); |
312
|
|
|
} catch (\Exception $error) { |
313
|
|
|
throw new Exception('Malformed response', 0, $error); |
314
|
|
|
} |
315
|
|
|
} |
316
|
|
|
} |
317
|
|
|
|
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.