1
|
|
|
<?php |
2
|
|
|
/* |
3
|
|
|
* This file is part of the FreshSinchBundle |
4
|
|
|
* |
5
|
|
|
* (c) Artem Genvald <[email protected]> |
6
|
|
|
* |
7
|
|
|
* For the full copyright and license information, please view the LICENSE |
8
|
|
|
* file that was distributed with this source code. |
9
|
|
|
*/ |
10
|
|
|
|
11
|
|
|
namespace Fresh\SinchBundle\Service; |
12
|
|
|
|
13
|
|
|
use Fresh\SinchBundle\Exception\BadRequest\SinchInvalidRequestException; |
14
|
|
|
use Fresh\SinchBundle\Exception\BadRequest\SinchMissingParameterException; |
15
|
|
|
use Fresh\SinchBundle\Exception\InternalServerError\SinchForbiddenRequestException; |
16
|
|
|
use Fresh\SinchBundle\Exception\InternalServerError\SinchInternalErrorException; |
17
|
|
|
use Fresh\SinchBundle\Exception\InternalServerError\SinchInvalidAuthorizationSchemeException; |
18
|
|
|
use Fresh\SinchBundle\Exception\InternalServerError\SinchNoVerifiedPhoneNumberException; |
19
|
|
|
use Fresh\SinchBundle\Exception\InternalServerError\SinchParameterValidationException; |
20
|
|
|
use Fresh\SinchBundle\Exception\PaymentRequired\SinchPaymentRequiredException; |
21
|
|
|
use Fresh\SinchBundle\Exception\Unauthorized\SinchIllegalAuthorizationHeaderException; |
22
|
|
|
use Fresh\SinchBundle\Sms\SmsStatus; |
23
|
|
|
use GuzzleHttp\Client; |
24
|
|
|
use GuzzleHttp\Exception\ClientException; |
25
|
|
|
use GuzzleHttp\Exception\GuzzleException; |
26
|
|
|
use Symfony\Component\HttpFoundation\Response; |
27
|
|
|
|
28
|
|
|
/** |
29
|
|
|
* SinchService |
30
|
|
|
* |
31
|
|
|
* @author Artem Genvald <[email protected]> |
32
|
|
|
*/ |
33
|
|
|
class SinchService |
34
|
|
|
{ |
35
|
|
|
/** |
36
|
|
|
* @var Client $guzzleHTTPClient Guzzle HTTP client |
37
|
|
|
*/ |
38
|
|
|
private $guzzleHTTPClient; |
39
|
|
|
|
40
|
|
|
/** |
41
|
|
|
* @var string $host Host |
42
|
|
|
*/ |
43
|
|
|
private $host; |
44
|
|
|
|
45
|
|
|
/** |
46
|
|
|
* @var string $key Key |
47
|
|
|
*/ |
48
|
|
|
private $key; |
49
|
|
|
|
50
|
|
|
/** |
51
|
|
|
* @var string $secret Secret |
52
|
|
|
*/ |
53
|
|
|
private $secret; |
54
|
|
|
|
55
|
|
|
/** |
56
|
|
|
* @var string $from From |
57
|
|
|
*/ |
58
|
|
|
private $from; |
59
|
|
|
|
60
|
|
|
/** |
61
|
|
|
* Constructor |
62
|
|
|
* |
63
|
|
|
* @param string $host Host |
64
|
|
|
* @param string $key Key |
65
|
|
|
* @param string $secret Secret |
66
|
|
|
* @param string|null $from From |
67
|
|
|
*/ |
68
|
|
|
public function __construct($host, $key, $secret, $from = null) |
69
|
|
|
{ |
70
|
|
|
$this->host = $host; |
71
|
|
|
$this->key = $key; |
72
|
|
|
$this->secret = $secret; |
73
|
|
|
$this->from = $from; |
74
|
|
|
|
75
|
|
|
$this->guzzleHTTPClient = new Client([ |
76
|
|
|
'base_uri' => rtrim($this->host, '/'), |
77
|
|
|
]); |
78
|
|
|
} |
79
|
|
|
|
80
|
|
|
// region Public API |
81
|
|
|
|
82
|
|
|
/** |
83
|
|
|
* Send SMS |
84
|
|
|
* |
85
|
|
|
* @param string $phoneNumber Phone number |
86
|
|
|
* @param string $messageText Message text |
87
|
|
|
* @param string|null $from From |
88
|
|
|
* |
89
|
|
|
* @return int Message ID |
90
|
|
|
* |
91
|
|
|
* @throws GuzzleException |
92
|
|
|
*/ |
93
|
|
|
public function sendSMS($phoneNumber, $messageText, $from = null) |
94
|
|
|
{ |
95
|
|
|
$uri = '/v1/sms/'.$phoneNumber; // @todo validate phone number |
96
|
|
|
|
97
|
|
|
$body = [ |
98
|
|
|
'auth' => [$this->key, $this->secret], |
99
|
|
|
'headers' => ['X-Timestamp' => (new \DateTime('now'))->format('c')], // ISO 8601 date format |
100
|
|
|
'json' => ['message' => $messageText], |
101
|
|
|
]; |
102
|
|
|
|
103
|
|
|
if (null !== $from) { |
104
|
|
|
$body['json']['from'] = $from; |
105
|
|
|
} elseif (null !== $this->from) { |
106
|
|
|
$body['json']['from'] = $this->from; |
107
|
|
|
} |
108
|
|
|
|
109
|
|
|
try { |
110
|
|
|
$response = $this->guzzleHTTPClient->post($uri, $body); |
111
|
|
|
} catch (ClientException $e) { |
112
|
|
|
throw $this->createAppropriateSinchException($e); |
113
|
|
|
} |
114
|
|
|
|
115
|
|
|
$messageId = null; |
116
|
|
|
if (Response::HTTP_OK === $response->getStatusCode() && $response->hasHeader('Content-Type') && |
117
|
|
|
'application/json; charset=utf-8' === $response->getHeaderLine('Content-Type') |
118
|
|
|
) { |
119
|
|
|
$content = $response->getBody()->getContents(); |
120
|
|
|
$content = json_decode($content, true); |
121
|
|
|
|
122
|
|
|
if (isset($content['messageId']) && array_key_exists('messageId', $content)) { |
123
|
|
|
$messageId = $content['messageId']; |
124
|
|
|
} |
125
|
|
|
}; |
126
|
|
|
|
127
|
|
|
return $messageId; |
128
|
|
|
} |
129
|
|
|
|
130
|
|
|
/** |
131
|
|
|
* Get status of sent SMS |
132
|
|
|
* |
133
|
|
|
* Available SMS statuses: Successful, Pending, Faulted, Unknown |
134
|
|
|
* |
135
|
|
|
* @param int $messageId Message ID |
136
|
|
|
* |
137
|
|
|
* @return string SMS status |
138
|
|
|
* |
139
|
|
|
* @throws GuzzleException |
140
|
|
|
*/ |
141
|
|
|
public function getStatusOfSMS($messageId) |
142
|
|
|
{ |
143
|
|
|
$response = $this->sendRequestToCheckStatusOfSMS($messageId); |
144
|
|
|
$result = ''; |
145
|
|
|
|
146
|
|
|
if (isset($response['status']) && array_key_exists('status', $response)) { |
147
|
|
|
$result = $response['status']; |
148
|
|
|
} |
149
|
|
|
|
150
|
|
|
return $result; |
151
|
|
|
} |
152
|
|
|
|
153
|
|
|
// endregion |
154
|
|
|
|
155
|
|
|
// region Check status helper methods |
156
|
|
|
|
157
|
|
|
/** |
158
|
|
|
* Returns true if SMS with some ID was sent successfully, otherwise returns false |
159
|
|
|
* |
160
|
|
|
* @param int $messageId Message ID |
161
|
|
|
* |
162
|
|
|
* @return bool True if SMS was sent successfully, otherwise - false |
163
|
|
|
*/ |
164
|
|
View Code Duplication |
public function smsIsSentSuccessfully($messageId) |
|
|
|
|
165
|
|
|
{ |
166
|
|
|
$response = $this->sendRequestToCheckStatusOfSMS($messageId); |
167
|
|
|
|
168
|
|
|
$result = false; |
169
|
|
|
if (isset($response['status']) && SmsStatus::SUCCESSFUL === $response['status']) { |
170
|
|
|
$result = true; |
171
|
|
|
} |
172
|
|
|
|
173
|
|
|
return $result; |
174
|
|
|
} |
175
|
|
|
|
176
|
|
|
/** |
177
|
|
|
* Returns true if SMS with some ID is still pending, otherwise returns false |
178
|
|
|
* |
179
|
|
|
* @param int $messageId Message ID |
180
|
|
|
* |
181
|
|
|
* @return bool True if SMS is still pending, otherwise - false |
182
|
|
|
*/ |
183
|
|
View Code Duplication |
public function smsIsPending($messageId) |
|
|
|
|
184
|
|
|
{ |
185
|
|
|
$response = $this->sendRequestToCheckStatusOfSMS($messageId); |
186
|
|
|
|
187
|
|
|
$result = false; |
188
|
|
|
if (isset($response['status']) && SmsStatus::PENDING === $response['status']) { |
189
|
|
|
$result = true; |
190
|
|
|
} |
191
|
|
|
|
192
|
|
|
return $result; |
193
|
|
|
} |
194
|
|
|
|
195
|
|
|
/** |
196
|
|
|
* Returns true if SMS with some ID was faulted, otherwise returns false |
197
|
|
|
* |
198
|
|
|
* @param int $messageId Message ID |
199
|
|
|
* |
200
|
|
|
* @return bool True if SMS was faulted, otherwise - false |
201
|
|
|
*/ |
202
|
|
View Code Duplication |
public function smsIsFaulted($messageId) |
|
|
|
|
203
|
|
|
{ |
204
|
|
|
$response = $this->sendRequestToCheckStatusOfSMS($messageId); |
205
|
|
|
|
206
|
|
|
$result = false; |
207
|
|
|
if (isset($response['status']) && SmsStatus::FAULTED === $response['status']) { |
208
|
|
|
$result = true; |
209
|
|
|
} |
210
|
|
|
|
211
|
|
|
return $result; |
212
|
|
|
} |
213
|
|
|
|
214
|
|
|
/** |
215
|
|
|
* Returns true if SMS with some ID in unknown status, otherwise returns false |
216
|
|
|
* |
217
|
|
|
* @param int $messageId Message ID |
218
|
|
|
* |
219
|
|
|
* @return bool True if SMS in unknown status, otherwise - false |
220
|
|
|
*/ |
221
|
|
View Code Duplication |
public function smsInUnknownStatus($messageId) |
|
|
|
|
222
|
|
|
{ |
223
|
|
|
$response = $this->sendRequestToCheckStatusOfSMS($messageId); |
224
|
|
|
|
225
|
|
|
$result = false; |
226
|
|
|
if (isset($response['status']) && SmsStatus::UNKNOWN === $response['status']) { |
227
|
|
|
$result = true; |
228
|
|
|
} |
229
|
|
|
|
230
|
|
|
return $result; |
231
|
|
|
} |
232
|
|
|
|
233
|
|
|
// endregion |
234
|
|
|
|
235
|
|
|
// region Private functions |
236
|
|
|
|
237
|
|
|
/** |
238
|
|
|
* Send request to check status of SMS |
239
|
|
|
* |
240
|
|
|
* @param int $messageId Message ID |
241
|
|
|
* |
242
|
|
|
* @return array|null |
243
|
|
|
* |
244
|
|
|
* @throws SinchPaymentRequiredException When run out of money |
245
|
|
|
*/ |
246
|
|
|
private function sendRequestToCheckStatusOfSMS($messageId) |
247
|
|
|
{ |
248
|
|
|
$uri = '/v1/message/status/'.$messageId; |
249
|
|
|
|
250
|
|
|
$body = [ |
251
|
|
|
'auth' => [$this->key, $this->secret], |
252
|
|
|
'headers' => ['X-Timestamp' => (new \DateTime('now'))->format('c')], |
253
|
|
|
]; |
254
|
|
|
|
255
|
|
|
try { |
256
|
|
|
$response = $this->guzzleHTTPClient->get($uri, $body); |
257
|
|
|
} catch (ClientException $e) { |
258
|
|
|
throw $this->createAppropriateSinchException($e); |
259
|
|
|
} |
260
|
|
|
|
261
|
|
|
$result = null; |
262
|
|
|
|
263
|
|
|
if (Response::HTTP_OK === $response->getStatusCode() && $response->hasHeader('Content-Type') && |
264
|
|
|
'application/json; charset=utf-8' === $response->getHeaderLine('Content-Type') |
265
|
|
|
) { |
266
|
|
|
$content = $response->getBody()->getContents(); |
267
|
|
|
$result = json_decode($content, true); |
268
|
|
|
}; |
269
|
|
|
|
270
|
|
|
return $result; |
271
|
|
|
} |
272
|
|
|
|
273
|
|
|
/** |
274
|
|
|
* Create appropriate Sinch exception |
275
|
|
|
* |
276
|
|
|
* @param ClientException $e Exception |
277
|
|
|
* |
278
|
|
|
* @return \Exception|\Fresh\SinchBundle\Exception\SinchException |
279
|
|
|
*/ |
280
|
|
|
private function createAppropriateSinchException(ClientException $e) |
281
|
|
|
{ |
282
|
|
|
$response = json_decode($e->getResponse()->getBody()->getContents(), true); |
283
|
|
|
$responseStatusCode = $e->getCode(); |
284
|
|
|
|
285
|
|
|
$errorCode = (int) $response['errorCode']; |
286
|
|
|
$errorMessage = $response['message']; |
287
|
|
|
|
288
|
|
|
$exception = null; |
289
|
|
|
|
290
|
|
|
switch ($responseStatusCode) { |
291
|
|
|
case Response::HTTP_BAD_REQUEST: |
292
|
|
|
$exception = $this->getSinchExceptionForBadRequest($errorCode, $errorMessage); |
293
|
|
|
break; |
294
|
|
|
case Response::HTTP_UNAUTHORIZED: |
295
|
|
|
$exception = $this->getSinchExceptionForUnauthorized($errorCode, $errorMessage); |
296
|
|
|
break; |
297
|
|
|
case Response::HTTP_PAYMENT_REQUIRED: |
298
|
|
|
$exception = $this->getSinchExceptionForPaymentRequired($errorCode, $errorMessage); |
299
|
|
|
break; |
300
|
|
|
case Response::HTTP_FORBIDDEN: |
301
|
|
|
$exception = $this->getSinchExceptionForForbidden($errorCode, $errorMessage); |
302
|
|
|
break; |
303
|
|
|
case Response::HTTP_INTERNAL_SERVER_ERROR: |
304
|
|
|
$exception = $this->getSinchExceptionForInternalServerError($errorCode, $errorMessage); |
305
|
|
|
break; |
306
|
|
|
} |
307
|
|
|
|
308
|
|
|
if (null === $exception) { |
309
|
|
|
$exception = new \Exception('Unknown Sinch Error Code'); |
310
|
|
|
} |
311
|
|
|
|
312
|
|
|
return $exception; |
313
|
|
|
} |
314
|
|
|
|
315
|
|
|
/** |
316
|
|
|
* Get Sinch exception for bad request |
317
|
|
|
* |
318
|
|
|
* @param int $errorCode Sinch error code |
319
|
|
|
* @param string $errorMessage Sinch error message |
320
|
|
|
* |
321
|
|
|
* @return \Fresh\SinchBundle\Exception\SinchException|null |
322
|
|
|
*/ |
323
|
|
View Code Duplication |
private function getSinchExceptionForBadRequest($errorCode, $errorMessage) |
|
|
|
|
324
|
|
|
{ |
325
|
|
|
$exception = null; |
326
|
|
|
|
327
|
|
|
switch ($errorCode) { |
328
|
|
|
case SinchErrorCode::PARAMETER_VALIDATION: |
329
|
|
|
$exception = new SinchParameterValidationException($errorMessage); |
330
|
|
|
break; |
331
|
|
|
case SinchErrorCode::MISSING_PARAMETER: |
332
|
|
|
$exception = new SinchMissingParameterException($errorMessage); |
333
|
|
|
break; |
334
|
|
|
case SinchErrorCode::INVALID_REQUEST: |
335
|
|
|
$exception = new SinchInvalidRequestException($errorMessage); |
336
|
|
|
break; |
337
|
|
|
} |
338
|
|
|
|
339
|
|
|
return $exception; |
340
|
|
|
} |
341
|
|
|
|
342
|
|
|
/** |
343
|
|
|
* Get Sinch exception for unauthorized |
344
|
|
|
* |
345
|
|
|
* @param int $errorCode Sinch error code |
346
|
|
|
* @param string $errorMessage Sinch error message |
347
|
|
|
* |
348
|
|
|
* @return \Fresh\SinchBundle\Exception\SinchException|null |
349
|
|
|
*/ |
350
|
|
View Code Duplication |
private function getSinchExceptionForUnauthorized($errorCode, $errorMessage) |
|
|
|
|
351
|
|
|
{ |
352
|
|
|
$exception = null; |
353
|
|
|
|
354
|
|
|
if (SinchErrorCode::ILLEGAL_AUTHORIZATION_HEADER === $errorCode) { |
355
|
|
|
$exception = new SinchIllegalAuthorizationHeaderException($errorMessage); |
356
|
|
|
} |
357
|
|
|
|
358
|
|
|
return $exception; |
359
|
|
|
} |
360
|
|
|
|
361
|
|
|
/** |
362
|
|
|
* Get Sinch exception for payment required |
363
|
|
|
* |
364
|
|
|
* Sinch returns 402 code when application run out of money |
365
|
|
|
* |
366
|
|
|
* @param int $errorCode Sinch error code |
367
|
|
|
* @param string $errorMessage Sinch error message |
368
|
|
|
* |
369
|
|
|
* @return \Fresh\SinchBundle\Exception\SinchException|null |
370
|
|
|
*/ |
371
|
|
View Code Duplication |
private function getSinchExceptionForPaymentRequired($errorCode, $errorMessage) |
|
|
|
|
372
|
|
|
{ |
373
|
|
|
$exception = null; |
374
|
|
|
|
375
|
|
|
if (SinchErrorCode::THERE_IS_NOT_ENOUGH_FUNDS_TO_SEND_THE_MESSAGE === $errorCode) { |
376
|
|
|
$exception = new SinchPaymentRequiredException($errorMessage); |
377
|
|
|
} |
378
|
|
|
|
379
|
|
|
return $exception; |
380
|
|
|
} |
381
|
|
|
|
382
|
|
|
/** |
383
|
|
|
* Get Sinch exception for forbidden |
384
|
|
|
* |
385
|
|
|
* @param int $errorCode Sinch error code |
386
|
|
|
* @param string $errorMessage Sinch error message |
387
|
|
|
* |
388
|
|
|
* @return \Fresh\SinchBundle\Exception\SinchException|null |
389
|
|
|
*/ |
390
|
|
View Code Duplication |
private function getSinchExceptionForForbidden($errorCode, $errorMessage) |
|
|
|
|
391
|
|
|
{ |
392
|
|
|
$exception = null; |
393
|
|
|
|
394
|
|
|
switch ($errorCode) { |
395
|
|
|
case SinchErrorCode::FORBIDDEN_REQUEST: |
396
|
|
|
$exception = new SinchForbiddenRequestException($errorMessage); |
397
|
|
|
break; |
398
|
|
|
case SinchErrorCode::INVALID_AUTHORIZATION_SCHEME_FOR_CALLING_THE_METHOD: |
399
|
|
|
$exception = new SinchInvalidAuthorizationSchemeException($errorMessage); |
400
|
|
|
break; |
401
|
|
|
case SinchErrorCode::NO_VERIFIED_PHONE_NUMBER_ON_YOUR_SINCH_ACCOUNT: |
402
|
|
|
case SinchErrorCode::SANDBOX_SMS_ONLY_ALLOWED_TO_BE_SENT_TO_VERIFIED_NUMBERS: |
403
|
|
|
$exception = new SinchNoVerifiedPhoneNumberException($errorMessage); |
404
|
|
|
break; |
405
|
|
|
} |
406
|
|
|
|
407
|
|
|
return $exception; |
408
|
|
|
} |
409
|
|
|
|
410
|
|
|
/** |
411
|
|
|
* Get Sinch exception for internal server error |
412
|
|
|
* |
413
|
|
|
* @param int $errorCode Sinch error code |
414
|
|
|
* @param string $errorMessage Sinch error message |
415
|
|
|
* |
416
|
|
|
* @return \Fresh\SinchBundle\Exception\SinchException|null |
417
|
|
|
*/ |
418
|
|
View Code Duplication |
private function getSinchExceptionForInternalServerError($errorCode, $errorMessage) |
|
|
|
|
419
|
|
|
{ |
420
|
|
|
$exception = null; |
421
|
|
|
|
422
|
|
|
if (SinchErrorCode::INTERNAL_ERROR === $errorCode) { |
423
|
|
|
$exception = new SinchInternalErrorException($errorMessage); |
424
|
|
|
} |
425
|
|
|
|
426
|
|
|
return $exception; |
427
|
|
|
} |
428
|
|
|
|
429
|
|
|
// endregion |
430
|
|
|
} |
431
|
|
|
|
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.