Issues (1)

src/Client.php (1 issue)

1
<?php
2
3
namespace Sarahman\SmsService;
4
5
use Exception;
6
use GuzzleHttp\Psr7\Response as GuzzleResponse;
7
use Illuminate\Support\Facades\Config;
8
use Illuminate\Support\Facades\Log;
9
use Illuminate\Support\Facades\Validator;
10
use Sarahman\HttpRequestApiLog\Traits\WritesHttpLogs;
11
use Sarahman\SmsService\Interfaces\NeedsAuthenticationInterface;
12
use Sarahman\SmsService\Interfaces\ProviderInterface;
13
14
class Client
15
{
16
    use WritesHttpLogs;
17
18
    const PROVIDER_BANGLALINK = Providers\Banglalink::class;
19
    const PROVIDER_BD_WEB_HOST_24 = Providers\BdWebHost24::class;
20
    const PROVIDER_BOOM_CAST = Providers\BoomCast::class;
21
    const PROVIDER_ELITBUZZ = Providers\Elitbuzz::class;
22
    const PROVIDER_GRAMEENPHONE = Providers\Grameenphone::class;
23
    const PROVIDER_NOVOCOM = Providers\Novocom::class;
24
    const PROVIDER_PAYSTATION = Providers\Paystation::class;
25
    const PROVIDER_ROBI = Providers\Robi::class;
26
    const PROVIDER_SSL = Providers\Ssl::class;
27
    const PROVIDER_SSL_PLUS = Providers\SslPlus::class;
28
    const PROVIDER_VALUE_FIRST = Providers\ValueFirst::class;
29
30
    private $provider;
31
32
    public function __construct(ProviderInterface $provider)
33
    {
34
        $this->provider = $provider;
35
        $this->enableLogging = Config::get('sms-service-with-bd-providers::config.enable_api_call_logging', false);
36
    }
37
38
    /**
39
     * Return a SMS provider according to the given provider name.
40
     *
41
     * @param string $providerName
42
     * @param array  $config
43
     * @param string $url
44
     *
45
     * @return ProviderInterface
46
     */
47
    public static function getProvider($providerName = self::PROVIDER_SSL, array $config = [], $url = null)
48
    {
49
        switch ($providerName) {
50
            case self::PROVIDER_BANGLALINK:
51
            case self::PROVIDER_BD_WEB_HOST_24:
52
            case self::PROVIDER_BOOM_CAST:
53
            case self::PROVIDER_ELITBUZZ:
54
            case self::PROVIDER_GRAMEENPHONE:
55
            case self::PROVIDER_NOVOCOM:
56
            case self::PROVIDER_PAYSTATION:
57
            case self::PROVIDER_ROBI:
58
            case self::PROVIDER_SSL:
59
            case self::PROVIDER_SSL_PLUS:
60
            case self::PROVIDER_VALUE_FIRST:
61
                return new $providerName($config, $url);
62
63
            default:
64
                throw new Exception('Invalid SMS provider name is given.');
65
        }
66
    }
67
68
    public function send($recipients, $message, array $params = [])
69
    {
70
        $log = ['sent' => [], 'failed' => []];
71
        is_array($recipients) || $recipients = [$recipients];
72
73
        foreach ($recipients as $recipient) {
74
            $options = ['url' => $this->provider->getUrl()];
75
76
            try {
77
                if (!$data = $this->provider->mapParams($recipient, $message, $params)) {
78
                    throw new Exception(json_encode('Failed to map the params.'), 422);
79
                }
80
81
                $data = array_merge($this->provider->getConfig(), $data);
82
                $validator = Validator::make($data, $this->provider->getValidationRules());
83
84
                if ($validator->fails()) {
85
                    throw new Exception(json_encode($validator->messages()->all()), 422);
86
                }
87
88
                $options += $this->prepareOptionsForProvider($data);
89
                $response = $this->provider->parseResponse($this->executeWithCurl($this->prepareCurlOptions($options)));
90
91
                if (!$response->getStatus()) {
92
                    throw new Exception($response->getResponseString(), 500);
93
                }
94
95
                $log['sent'][$recipient] = $response->toArray();
96
97
                $this->log('POST', $options['url'], $options, new GuzzleResponse(200, [], $response->getResponseString()));
98
            } catch (Exception $e) {
99
                $errorCode = $e->getCode() >= 100 ? $e->getCode() : 500;
100
                $errorMessage = 422 != $errorCode ? $e->getMessage() : json_decode($e->getMessage(), true);
101
                $log['failed'][$recipient] = (new Response(false, $errorMessage))->toArray();
102
103
                $this->log('POST', $options['url'], $options, new GuzzleResponse($errorCode, [], $errorMessage));
104
            }
105
        }
106
107
        return $this->getSummaryWithLogs($log);
108
    }
109
110
    public function sendWithFallback($recipients, $message, array $params = [])
111
    {
112
        $log = ['sent' => [], 'failed' => []];
113
        is_array($recipients) || $recipients = [$recipients];
114
115
        foreach ($recipients as $recipient) {
116
            $options = ['url' => $this->provider->getUrl()];
117
118
            try {
119
                if (!$data = $this->provider->mapParams($recipient, $message, $params)) {
120
                    throw new Exception(json_encode('Failed to map the params.'), 422);
121
                }
122
123
                $data = array_merge($this->provider->getConfig(), $data);
124
                $validator = Validator::make($data, $this->provider->getValidationRules());
125
126
                if ($validator->fails()) {
127
                    throw new Exception(json_encode($validator->messages()->all()), 422);
128
                }
129
130
                $options += $this->prepareOptionsForProvider($data);
131
                $curlOptions = $this->prepareCurlOptions($options);
132
133
                try {
134
                    $response = $this->executeWithCurl($curlOptions);
135
                } catch (Exception $e) {
136
                    $log['failed'][$recipient] = (new Response(false, $e->getMessage()))->toArray();
137
                    $response = '';
138
                }
139
140
                $response = $this->provider->parseResponse($response);
141
142
                if (!$response->getStatus()) {
143
                    $this->log('POST', $options['url'], $options, new GuzzleResponse(500, [], $response->getResponseString()));
144
145
                    // Resend sms
146
                    Log::info('SMS sending failed response!');
147
148
                    try {
149
                        $response = $this->provider->parseResponse($this->executeWithCurl($curlOptions));
150
                        Log::info('Second try of sending SMS', $response);
151
152
                        if (!$response->getStatus()) {
153
                            throw new Exception($response->getResponseString(), 500);
154
                        }
155
                    } catch (Exception $e) {
156
                        Log::error('Curl error response: '.$e->getMessage());
157
158
                        throw $e;
159
                    }
160
                }
161
162
                $log['sent'][$recipient] = $response->toArray();
163
164
                $this->log('POST', $options['url'], $options, new GuzzleResponse(200, [], $response->getResponseString()));
165
            } catch (Exception $e) {
166
                $errorCode = $e->getCode() >= 100 ? $e->getCode() : 500;
167
                $errorMessage = 422 != $errorCode ? $e->getMessage() : json_decode($e->getMessage(), true);
168
                $log['failed'][$recipient] = (new Response(false, $errorMessage))->toArray();
169
170
                $this->log('POST', $options['url'], $options, new GuzzleResponse($errorCode, [], $errorMessage));
171
            }
172
        }
173
174
        return $this->getSummaryWithLogs($log);
175
    }
176
177
    /**
178
     * Prepare the options array according to the given provider data.
179
     *
180
     * @param array $data
181
     *
182
     * @return array
183
     */
184
    private function prepareOptionsForProvider(array $data)
185
    {
186
        $options = [
187
            'timeout' => 30,
188
        ];
189
190
        switch(get_class($this->provider)) {
191
            case self::PROVIDER_GRAMEENPHONE:
192
            case self::PROVIDER_NOVOCOM:
193
                $options += [
194
                    'httpheader' => ['Content-Type: application/json'],
195
                    'post'       => 1,
196
                    'postfields' => json_encode($data),
197
                ];
198
                break;
199
200
            case self::PROVIDER_PAYSTATION:
201
                $options += [
202
                    'httpheader' => [
203
                        'Content-Type: application/json',
204
                        'Accept: application/json',
205
                        'user_id:'.$data['user_id'],
206
                        'password:'.$data['password'],
207
                    ],
208
                    'post'       => 1,
209
                    'postfields' => json_encode($data),
210
                ];
211
                break;
212
213
            case self::PROVIDER_SSL_PLUS:
214
                $encodedData = json_encode($data);
215
                $options += [
216
                    'httpheader'    => [
217
                        'Content-Type: application/json',
218
                        'Content-Length: '.strlen($encodedData),
219
                        'Accept: application/json',
220
                    ],
221
                    'customrequest' => 'POST',
222
                    'post'          => 1,
223
                    'postfields'    => $encodedData,
224
                ];
225
                break;
226
227
            default:
228
                $options += [
229
                    'post'       => count($data),
230
                    'postfields' => http_build_query($data),
231
                ];
232
        }
233
234
        if ($this->provider instanceof NeedsAuthenticationInterface) {
235
            $options['httpheader'][] = 'Authorization: Bearer '.$this->provider->getAccessToken();
0 ignored issues
show
The method getAccessToken() does not exist on Sarahman\SmsService\Interfaces\ProviderInterface. It seems like you code against a sub-type of Sarahman\SmsService\Interfaces\ProviderInterface such as Sarahman\SmsService\Providers\ValueFirst. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

235
            $options['httpheader'][] = 'Authorization: Bearer '.$this->provider->/** @scrutinizer ignore-call */ getAccessToken();
Loading history...
236
        }
237
238
        return $options;
239
    }
240
241
    /**
242
     * Prepare the curl options for the curl request.
243
     *
244
     * @param array $options
245
     *
246
     * @return array
247
     */
248
    private function prepareCurlOptions(array $options)
249
    {
250
        $curlOptions = [];
251
        isset($options['returntransfer']) || $options['returntransfer'] = true;
252
253
        foreach ($options as $key => $value) {
254
            $option = 'CURLOPT_'.strtoupper($key);
255
            $curlOptions[constant($option)] = $value;
256
        }
257
258
        return $curlOptions;
259
    }
260
261
    /**
262
     * Execute the Curl request according to the given curl options.
263
     *
264
     * @param array $curlOptions
265
     *
266
     * @return string
267
     */
268
    private function executeWithCurl(array $curlOptions)
269
    {
270
        $ch = curl_init();
271
272
        curl_setopt_array($ch, $curlOptions);
273
274
        $response = curl_exec($ch);
275
276
        if ($response != true) {
277
            $errorNumber = curl_errno($ch);
278
            $eMsg = 'cURL Error # '.$errorNumber.' | cURL Error Message: '.curl_error($ch);
279
280
            curl_close($ch);
281
282
            if (60 == $errorNumber) {
283
                $curlOptions[constant('CURLOPT_SSL_VERIFYPEER')] = false;
284
285
                return $this->executeWithCurl($curlOptions);
286
            }
287
288
            throw new Exception($eMsg);
289
        }
290
291
        curl_close($ch);
292
293
        return (string) $response;
294
    }
295
296
    private function getSummaryWithLogs(array $log)
297
    {
298
        $sent = count($log['sent']);
299
        $failed = count($log['failed']);
300
301
        return [
302
            'summary' => [
303
                'sent'   => $sent,
304
                'failed' => $failed,
305
                'total'  => $sent + $failed,
306
            ],
307
            'log'     => $log,
308
        ];
309
    }
310
}
311