├── .editorconfig
├── .github
├── FUNDING.yml
└── workflows
│ └── tests.yml
├── .php-cs-fixer.dist.php
├── LICENSE
├── README.md
├── composer.json
├── psalm.xml
└── src
├── Contracts
├── GatewayInterface.php
├── MessageInterface.php
├── PhoneNumberInterface.php
└── StrategyInterface.php
├── EasySms.php
├── Exceptions
├── Exception.php
├── GatewayErrorException.php
├── InvalidArgumentException.php
└── NoGatewayAvailableException.php
├── Gateways
├── AliyunGateway.php
├── AliyunIntlGateway.php
├── AliyunrestGateway.php
├── BaiduGateway.php
├── ChuanglanGateway.php
├── Chuanglanv1Gateway.php
├── CtyunGateway.php
├── ErrorlogGateway.php
├── Gateway.php
├── HuaweiGateway.php
├── HuaxinGateway.php
├── HuyiGateway.php
├── JuheGateway.php
├── KingttoGateway.php
├── LuosimaoGateway.php
├── MaapGateway.php
├── ModuyunGateway.php
├── NowcnGateway.php
├── QcloudGateway.php
├── QiniuGateway.php
├── RongcloudGateway.php
├── RongheyunGateway.php
├── SendcloudGateway.php
├── SmsbaoGateway.php
├── SubmailGateway.php
├── TianyiwuxianGateway.php
├── TiniyoGateway.php
├── TinreeGateway.php
├── TwilioGateway.php
├── UcloudGateway.php
├── Ue35Gateway.php
├── VolcengineGateway.php
├── YidongmasblackGateway.php
├── YunpianGateway.php
├── YuntongxunGateway.php
├── YunxinGateway.php
├── YunzhixunGateway.php
└── ZzyunGateway.php
├── Message.php
├── Messenger.php
├── PhoneNumber.php
├── Strategies
├── OrderStrategy.php
└── RandomStrategy.php
├── Support
└── Config.php
└── Traits
└── HasHttpRequest.php
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | indent_style = space
5 | indent_size = 4
6 | end_of_line = lf
7 | charset = utf-8
8 | trim_trailing_whitespace = true
9 | insert_final_newline = false
10 |
11 | [*.{vue,js,scss}]
12 | charset = utf-8
13 | indent_style = space
14 | indent_size = 2
15 | end_of_line = lf
16 | insert_final_newline = true
17 | trim_trailing_whitespace = true
18 |
19 | [*.md]
20 | trim_trailing_whitespace = false
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: [overtrue]
4 |
--------------------------------------------------------------------------------
/.github/workflows/tests.yml:
--------------------------------------------------------------------------------
1 | name: Tests
2 |
3 | on:
4 | push:
5 | branches: [master]
6 | pull_request:
7 |
8 | jobs:
9 | phpunit:
10 | strategy:
11 | matrix:
12 | php_version: [8.0, 8.1, 8.2, 8.3]
13 | runs-on: ubuntu-latest
14 | steps:
15 | - uses: actions/checkout@v2
16 | - name: Setup PHP environment
17 | uses: shivammathur/setup-php@v2
18 | with:
19 | php-version: ${{ matrix.php_version }}
20 | coverage: xdebug
21 | - name: Install dependencies
22 | run: composer install
23 | - name: PHPUnit check
24 | run: ./vendor/bin/phpunit --coverage-text
25 |
--------------------------------------------------------------------------------
/.php-cs-fixer.dist.php:
--------------------------------------------------------------------------------
1 | setRules([
5 | '@Symfony' => true,
6 | ])
7 | ->setFinder(
8 | PhpCsFixer\Finder::create()
9 | ->exclude('vendor')
10 | ->in([__DIR__.'/src/', __DIR__.'/tests/'])
11 | )
12 | ;
13 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 overtrue
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
Easy SMS
2 |
3 | :calling: 一款满足你的多种发送需求的短信发送组件
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | ## 特点
18 |
19 | 1. 支持目前市面多家服务商
20 | 1. 一套写法兼容所有平台
21 | 1. 简单配置即可灵活增减服务商
22 | 1. 内置多种服务商轮询策略、支持自定义轮询策略
23 | 1. 统一的返回值格式,便于日志与监控
24 | 1. 自动轮询选择可用的服务商
25 | 1. 更多等你去发现与改进...
26 |
27 | ## 平台支持
28 |
29 | - [腾讯云 SMS](https://cloud.tencent.com/product/sms)
30 | - [Ucloud](https://www.ucloud.cn)
31 | - [七牛云](https://www.qiniu.com/)
32 | - [SendCloud](http://www.sendcloud.net/)
33 | - [阿里云](https://www.aliyun.com/)
34 | - [云片](https://www.yunpian.com)
35 | - [Submail](https://www.mysubmail.com)
36 | - [螺丝帽](https://luosimao.com/)
37 | - [容联云通讯](http://www.yuntongxun.com)
38 | - [互亿无线](http://www.ihuyi.com)
39 | - [聚合数据](https://www.juhe.cn)
40 | - [百度云](https://cloud.baidu.com/)
41 | - [华信短信平台](http://www.ipyy.com/)
42 | - [253云通讯(创蓝)](https://www.253.com/)
43 | - [创蓝云智](https://www.chuanglan.com/)
44 | - [融云](http://www.rongcloud.cn)
45 | - [天毅无线](http://www.85hu.com/)
46 | - [华为云](https://www.huaweicloud.com/product/msgsms.html)
47 | - [网易云信](https://yunxin.163.com/sms)
48 | - [云之讯](https://www.ucpaas.com/index.html)
49 | - [凯信通](http://www.kingtto.cn/)
50 | - [UE35.net](http://uesms.ue35.cn/)
51 | - [短信宝](http://www.smsbao.com/)
52 | - [Tiniyo](https://tiniyo.com/)
53 | - [摩杜云](https://www.moduyun.com/)
54 | - [融合云(助通)](https://www.ztinfo.cn/products/sms)
55 | - [蜘蛛云](https://zzyun.com/)
56 | - [融合云信](https://maap.wo.cn/)
57 | - [天瑞云](http://cms.tinree.com/)
58 | - [时代互联](https://www.now.cn/)
59 | - [火山引擎](https://console.volcengine.com/sms/)
60 | - [移动云MAS(黑名单模式)](https://mas.10086.cn)
61 | - [电信天翼云](https://www.ctyun.cn/document/10020426/10021544)
62 |
63 | ## 环境需求
64 |
65 | - PHP >= 5.6
66 |
67 | ## 安装
68 |
69 | ```shell
70 | composer require "overtrue/easy-sms"
71 | ```
72 |
73 | **For Laravel notification**
74 |
75 | 如果你喜欢使用 [Laravel Notification](https://laravel.com/docs/5.8/notifications), 可以考虑直接使用朋友封装的拓展包:
76 |
77 |
78 |
79 | ## 使用
80 |
81 | ```php
82 | use Overtrue\EasySms\EasySms;
83 |
84 | $config = [
85 | // HTTP 请求的超时时间(秒)
86 | 'timeout' => 5.0,
87 |
88 | // 默认发送配置
89 | 'default' => [
90 | // 网关调用策略,默认:顺序调用
91 | 'strategy' => \Overtrue\EasySms\Strategies\OrderStrategy::class,
92 |
93 | // 默认可用的发送网关
94 | 'gateways' => [
95 | 'yunpian', 'aliyun',
96 | ],
97 | ],
98 | // 可用的网关配置
99 | 'gateways' => [
100 | 'errorlog' => [
101 | 'file' => '/tmp/easy-sms.log',
102 | ],
103 | 'yunpian' => [
104 | 'api_key' => '824f0ff2f71cab52936axxxxxxxxxx',
105 | ],
106 | 'aliyun' => [
107 | 'access_key_id' => '',
108 | 'access_key_secret' => '',
109 | 'sign_name' => '',
110 | ],
111 | //...
112 | ],
113 | ];
114 |
115 | $easySms = new EasySms($config);
116 |
117 | $easySms->send(13188888888, [
118 | 'content' => '您的验证码为: 6379',
119 | 'template' => 'SMS_001',
120 | 'data' => [
121 | 'code' => 6379
122 | ],
123 | ]);
124 | ```
125 |
126 | ## 短信内容
127 |
128 | 由于使用多网关发送,所以一条短信要支持多平台发送,每家的发送方式不一样,但是我们抽象定义了以下公用属性:
129 |
130 | - `content` 文字内容,使用在像云片类似的以文字内容发送的平台
131 | - `template` 模板 ID,使用在以模板ID来发送短信的平台
132 | - `data` 模板变量,使用在以模板ID来发送短信的平台
133 |
134 | 所以,在使用过程中你可以根据所要使用的平台定义发送的内容。
135 |
136 | ```php
137 | $easySms->send(13188888888, [
138 | 'content' => '您的验证码为: 6379',
139 | 'template' => 'SMS_001',
140 | 'data' => [
141 | 'code' => 6379
142 | ],
143 | ]);
144 | ```
145 |
146 | 你也可以使用闭包来返回对应的值:
147 |
148 | ```php
149 | $easySms->send(13188888888, [
150 | 'content' => function($gateway){
151 | return '您的验证码为: 6379';
152 | },
153 | 'template' => function($gateway){
154 | return 'SMS_001';
155 | },
156 | 'data' => function($gateway){
157 | return [
158 | 'code' => 6379
159 | ];
160 | },
161 | ]);
162 | ```
163 |
164 | 你可以根据 `$gateway` 参数类型来判断返回值,例如:
165 |
166 | ```php
167 | $easySms->send(13188888888, [
168 | 'content' => function($gateway){
169 | if ($gateway->getName() == 'yunpian') {
170 | return '云片专用验证码:1235';
171 | }
172 | return '您的验证码为: 6379';
173 | },
174 | 'template' => function($gateway){
175 | if ($gateway->getName() == 'aliyun') {
176 | return 'TP2818';
177 | }
178 | return 'SMS_001';
179 | },
180 | 'data' => function($gateway){
181 | return [
182 | 'code' => 6379
183 | ];
184 | },
185 | ]);
186 | ```
187 |
188 | ## 发送网关
189 |
190 | 默认使用 `default` 中的设置来发送,如果某一条短信你想要覆盖默认的设置。在 `send` 方法中使用第三个参数即可:
191 |
192 | ```php
193 | $easySms->send(13188888888, [
194 | 'content' => '您的验证码为: 6379',
195 | 'template' => 'SMS_001',
196 | 'data' => [
197 | 'code' => 6379
198 | ],
199 | ], ['yunpian', 'juhe']); // 这里的网关配置将会覆盖全局默认值
200 | ```
201 |
202 | ## 返回值
203 |
204 | 由于使用多网关发送,所以返回值为一个数组,结构如下:
205 |
206 | ```php
207 | [
208 | 'yunpian' => [
209 | 'gateway' => 'yunpian',
210 | 'status' => 'success',
211 | 'result' => [...] // 平台返回值
212 | ],
213 | 'juhe' => [
214 | 'gateway' => 'juhe',
215 | 'status' => 'failure',
216 | 'exception' => \Overtrue\EasySms\Exceptions\GatewayErrorException 对象
217 | ],
218 | //...
219 | ]
220 | ```
221 |
222 | 如果所选网关列表均发送失败时,将会抛出 `Overtrue\EasySms\Exceptions\NoGatewayAvailableException` 异常,你可以使用 `$e->results` 获取发送结果。
223 |
224 | 你也可以使用 `$e` 提供的更多便捷方法:
225 |
226 | ```php
227 | $e->getResults(); // 返回所有 API 的结果,结构同上
228 | $e->getExceptions(); // 返回所有调用异常列表
229 | $e->getException($gateway); // 返回指定网关名称的异常对象
230 | $e->getLastException(); // 获取最后一个失败的异常对象
231 | ```
232 |
233 | ## 自定义网关
234 |
235 | 本拓展已经支持用户自定义网关,你可以很方便的配置即可当成与其它拓展一样的使用:
236 |
237 | ```php
238 | $config = [
239 | ...
240 | 'default' => [
241 | 'gateways' => [
242 | 'mygateway', // 配置你的网站到可用的网关列表
243 | ],
244 | ],
245 | 'gateways' => [
246 | 'mygateway' => [...], // 你网关所需要的参数,如果没有可以不配置
247 | ],
248 | ];
249 |
250 | $easySms = new EasySms($config);
251 |
252 | // 注册
253 | $easySms->extend('mygateway', function($gatewayConfig){
254 | // $gatewayConfig 来自配置文件里的 `gateways.mygateway`
255 | return new MyGateway($gatewayConfig);
256 | });
257 |
258 | $easySms->send(13188888888, [
259 | 'content' => '您的验证码为: 6379',
260 | 'template' => 'SMS_001',
261 | 'data' => [
262 | 'code' => 6379
263 | ],
264 | ]);
265 | ```
266 |
267 | ## 国际短信
268 |
269 | 国际短信与国内短信的区别是号码前面需要加国际码,但是由于各平台对国际号码的写法不一致,所以在发送国际短信的时候有一点区别:
270 |
271 | ```php
272 | use Overtrue\EasySms\PhoneNumber;
273 |
274 | // 发送到国际码为 31 的国际号码
275 | $number = new PhoneNumber(13188888888, 31);
276 |
277 | $easySms->send($number, [
278 | 'content' => '您的验证码为: 6379',
279 | 'template' => 'SMS_001',
280 | 'data' => [
281 | 'code' => 6379
282 | ],
283 | ]);
284 | ```
285 |
286 | ## 定义短信
287 |
288 | 你可以根据发送场景的不同,定义不同的短信类,从而实现一处定义多处调用,你可以继承 `Overtrue\EasySms\Message` 来定义短信模型:
289 |
290 | ```php
291 | order = $order;
306 | }
307 |
308 | // 定义直接使用内容发送平台的内容
309 | public function getContent(GatewayInterface $gateway = null)
310 | {
311 | return sprintf('您的订单:%s, 已经完成付款', $this->order->no);
312 | }
313 |
314 | // 定义使用模板发送方式平台所需要的模板 ID
315 | public function getTemplate(GatewayInterface $gateway = null)
316 | {
317 | return 'SMS_003';
318 | }
319 |
320 | // 模板参数
321 | public function getData(GatewayInterface $gateway = null)
322 | {
323 | return [
324 | 'order_no' => $this->order->no
325 | ];
326 | }
327 | }
328 | ```
329 |
330 | > 更多自定义方式请参考:[`Overtrue\EasySms\Message`](Overtrue\EasySms\Message;)
331 |
332 | 发送自定义短信:
333 |
334 | ```php
335 | $order = ...;
336 | $message = new OrderPaidMessage($order);
337 |
338 | $easySms->send(13188888888, $message);
339 | ```
340 |
341 | ## 各平台配置说明
342 |
343 | ### [阿里云](https://www.aliyun.com/)
344 |
345 | 短信内容使用 `template` + `data`
346 |
347 | ```php
348 | 'aliyun' => [
349 | 'access_key_id' => '',
350 | 'access_key_secret' => '',
351 | 'sign_name' => '',
352 | ],
353 | ```
354 |
355 | ### [阿里云Rest](https://www.aliyun.com/)
356 |
357 | 短信内容使用 `template` + `data`
358 |
359 | ```php
360 | 'aliyunrest' => [
361 | 'app_key' => '',
362 | 'app_secret_key' => '',
363 | 'sign_name' => '',
364 | ],
365 | ```
366 |
367 | ### [阿里云国际](https://www.alibabacloud.com/help/zh/doc-detail/160524.html)
368 |
369 | 短信内容使用 `template` + `data`
370 |
371 | ```php
372 | 'aliyunintl' => [
373 | 'access_key_id' => '',
374 | 'access_key_secret' => '',
375 | 'sign_name' => '',
376 | ],
377 | ```
378 |
379 | 发送示例:
380 |
381 | ```php
382 | use Overtrue\EasySms\PhoneNumber;
383 |
384 | $easySms = new EasySms($config);
385 | $phone_number = new PhoneNumber(18888888888, 86);
386 |
387 | $easySms->send($phone_number, [
388 | 'content' => '您好:先生/女士!您的验证码为${code},有效时间是5分钟,请及时验证。',
389 | 'template' => 'SMS_00000001', // 模板ID
390 | 'data' => [
391 | "code" => 521410,
392 | ],
393 | ]);
394 | ```
395 |
396 | ### [云片](https://www.yunpian.com)
397 |
398 | 短信内容使用 `content`
399 |
400 | ```php
401 | 'yunpian' => [
402 | 'api_key' => '',
403 | 'signature' => '【默认签名】', // 内容中无签名时使用
404 | ],
405 | ```
406 |
407 | ### [Submail](https://www.mysubmail.com)
408 |
409 | 短信内容使用 `data`
410 |
411 | ```php
412 | 'submail' => [
413 | 'app_id' => '',
414 | 'app_key' => '',
415 | 'project' => '', // 默认 project,可在发送时 data 中指定
416 | ],
417 | ```
418 |
419 | ### [螺丝帽](https://luosimao.com/)
420 |
421 | 短信内容使用 `content`
422 |
423 | ```php
424 | 'luosimao' => [
425 | 'api_key' => '',
426 | ],
427 | ```
428 |
429 | ### [容联云通讯](http://www.yuntongxun.com)
430 |
431 | 短信内容使用 `template` + `data`
432 |
433 | ```php
434 | 'yuntongxun' => [
435 | 'app_id' => '',
436 | 'account_sid' => '',
437 | 'account_token' => '',
438 | 'is_sub_account' => false,
439 | ],
440 | ```
441 |
442 | ### [互亿无线](http://www.ihuyi.com)
443 |
444 | 短信内容使用 `content`
445 |
446 | ```php
447 | 'huyi' => [
448 | 'api_id' => '',
449 | 'api_key' => '',
450 | 'signature' => '',
451 | ],
452 | ```
453 |
454 | ### [聚合数据](https://www.juhe.cn)
455 |
456 | 短信内容使用 `template` + `data`
457 |
458 | ```php
459 | 'juhe' => [
460 | 'app_key' => '',
461 | ],
462 | ```
463 |
464 | ### [SendCloud](http://www.sendcloud.net/)
465 |
466 | 短信内容使用 `template` + `data`
467 |
468 | ```php
469 | 'sendcloud' => [
470 | 'sms_user' => '',
471 | 'sms_key' => '',
472 | 'timestamp' => false, // 是否启用时间戳
473 | ],
474 | ```
475 |
476 | ### [百度云](https://cloud.baidu.com/)
477 |
478 | 短信内容使用 `template` + `data`
479 |
480 | ```php
481 | 'baidu' => [
482 | 'ak' => '',
483 | 'sk' => '',
484 | 'invoke_id' => '',
485 | 'domain' => '',
486 | ],
487 | ```
488 |
489 | ### [华信短信平台](http://www.ipyy.com/)
490 |
491 | 短信内容使用 `content`
492 |
493 | ```php
494 | 'huaxin' => [
495 | 'user_id' => '',
496 | 'password' => '',
497 | 'account' => '',
498 | 'ip' => '',
499 | 'ext_no' => '',
500 | ],
501 | ```
502 |
503 | ### [253云通讯(创蓝)](https://www.253.com/)
504 |
505 | 短信内容使用 `content`
506 |
507 | ```php
508 | 'chuanglan' => [
509 | 'account' => '',
510 | 'password' => '',
511 |
512 | // 国际短信时必填
513 | 'intel_account' => '',
514 | 'intel_password' => '',
515 |
516 | // \Overtrue\EasySms\Gateways\ChuanglanGateway::CHANNEL_VALIDATE_CODE => 验证码通道(默认)
517 | // \Overtrue\EasySms\Gateways\ChuanglanGateway::CHANNEL_PROMOTION_CODE => 会员营销通道
518 | 'channel' => \Overtrue\EasySms\Gateways\ChuanglanGateway::CHANNEL_VALIDATE_CODE,
519 |
520 | // 会员营销通道 特定参数。创蓝规定:api提交营销短信的时候,需要自己加短信的签名及退订信息
521 | 'sign' => '【通讯云】',
522 | 'unsubscribe' => '回TD退订',
523 | ],
524 | ```
525 |
526 | ### [创蓝云智](https://www.chuanglan.com/)
527 |
528 | 普通短信发送内容使用 `content`
529 |
530 | ```php
531 | 'chuanglanv1' => [
532 | 'account' => '',
533 | 'password' => '',
534 | 'needstatus' => false,
535 | 'channel' => \Overtrue\EasySms\Gateways\Chuanglanv1Gateway::CHANNEL_NORMAL_CODE,
536 | ],
537 | ```
538 |
539 | 发送示例:
540 |
541 | ```php
542 | $easySms->send(18888888888, [
543 | 'content' => xxxxxxx
544 | ]);
545 | ```
546 |
547 | 变量短信发送内容使用 `template` + `data`
548 |
549 | ```php
550 | 'chuanglanv1' => [
551 | 'account' => '',
552 | 'password' => '',
553 | 'needstatus' => false,
554 | 'channel' => \Overtrue\EasySms\Gateways\Chuanglanv1Gateway::CHANNEL_VARIABLE_CODE,
555 | ],
556 | ```
557 |
558 | 发送示例:
559 |
560 | ```php
561 | $easySms->send(18888888888, [
562 | 'template' => xxxxxx, // 模板内容
563 | 'data' => 'phone":"15800000000,1234;15300000000,4321',
564 | ]);
565 | ```
566 |
567 | ### [融云](http://www.rongcloud.cn)
568 |
569 | 短信分为两大类,验证类和通知类短信。 发送验证类短信使用 `template` + `data`
570 |
571 | ```php
572 | 'rongcloud' => [
573 | 'app_key' => '',
574 | 'app_secret' => '',
575 | ]
576 | ```
577 |
578 | ### [天毅无线](http://www.85hu.com/)
579 |
580 | 短信内容使用 `content`
581 |
582 | ```php
583 | 'tianyiwuxian' => [
584 | 'username' => '', //用户名
585 | 'password' => '', //密码
586 | 'gwid' => '', //网关ID
587 | ]
588 | ```
589 |
590 | ### [twilio](https://www.twilio.com)
591 |
592 | 短信使用 `content`
593 | 发送对象需要 使用`+`添加区号
594 |
595 | ```php
596 | 'twilio' => [
597 | 'account_sid' => '', // sid
598 | 'from' => '', // 发送的号码 可以在控制台购买
599 | 'token' => '', // apitoken
600 | ],
601 | ```
602 |
603 | ### [tiniyo](https://www.tiniyo.com)
604 |
605 | 短信使用 `content`
606 | 发送对象需要 使用`+`添加区号
607 |
608 | ```php
609 | 'tiniyo' => [
610 | 'account_sid' => '', // auth_id from https://tiniyo.com
611 | 'from' => '', // 发送的号码 可以在控制台购买
612 | 'token' => '', // auth_secret from https://tiniyo.com
613 | ],
614 | ```
615 |
616 | ### [腾讯云 SMS](https://cloud.tencent.com/product/sms)
617 |
618 | 短信内容使用 `template` + `data`
619 |
620 | ```php
621 | 'qcloud' => [
622 | 'sdk_app_id' => '', // 短信应用的 SDK APP ID
623 | 'secret_id' => '', // SECRET ID
624 | 'secret_key' => '', // SECRET KEY
625 | 'sign_name' => '腾讯CoDesign', // 短信签名
626 | ],
627 | ```
628 |
629 | 发送示例:
630 |
631 | ```php
632 | $easySms->send(18888888888, [
633 | 'template' => 101234, // 模板ID
634 | 'data' => [
635 | "a", 'b', 'c', 'd', //按占位顺序给值
636 | ],
637 | ]);
638 | ```
639 |
640 | ### [华为云 SMS](https://www.huaweicloud.com/product/msgsms.html)
641 |
642 | 短信内容使用 `template` + `data`
643 |
644 | ```php
645 | 'huawei' => [
646 | 'endpoint' => '', // APP接入地址
647 | 'app_key' => '', // APP KEY
648 | 'app_secret' => '', // APP SECRET
649 | 'from' => [
650 | 'default' => '1069012345', // 默认使用签名通道号
651 | 'custom' => 'csms12345', // 其他签名通道号 可以在 data 中定义 from 来指定
652 | 'abc' => 'csms67890', // 其他签名通道号
653 | ...
654 | ],
655 | 'callback' => '' // 短信状态回调地址
656 | ],
657 | ```
658 |
659 | 使用默认签名通道 `default`
660 |
661 | ```php
662 | $easySms->send(13188888888, [
663 | 'template' => 'SMS_001',
664 | 'data' => [
665 | 6379
666 | ],
667 | ]);
668 | ```
669 |
670 | 使用指定签名通道
671 |
672 | ```php
673 | $easySms->send(13188888888, [
674 | 'template' => 'SMS_001',
675 | 'data' => [
676 | 6379,
677 | 'from' => 'custom' // 对应 config 中的 from 数组中 custom
678 | ],
679 | ]);
680 | ```
681 |
682 | ### [网易云信](https://yunxin.163.com/sms)
683 |
684 | 短信内容使用 `template` + `data`
685 |
686 | ```php
687 | 'yunxin' => [
688 | 'app_key' => '',
689 | 'app_secret' => '',
690 | 'code_length' => 4, // 随机验证码长度,范围 4~10,默认为 4
691 | 'need_up' => false, // 是否需要支持短信上行
692 | ],
693 | ```
694 |
695 | ```php
696 | $easySms->send(18888888888, [
697 | 'template' => 'SMS_001', // 不填则使用默认模板
698 | 'data' => [
699 | 'code' => 8946, // 如果设置了该参数,则 code_length 参数无效
700 | 'action' => 'sendCode', // 默认为 `sendCode`,校验短信验证码使用 `verifyCode`
701 | ],
702 | ]);
703 | ```
704 |
705 | 通知模板短信
706 |
707 | ```php
708 | $easySms->send(18888888888, [
709 | 'template' => 'templateid', // 模板编号(由客户顾问配置之后告知开发者)
710 | 'data' => [
711 | 'action' => 'sendTemplate', // 默认为 `sendCode`,校验短信验证码使用 `verifyCode`
712 | 'params' => [1,2,3], //短信参数列表,用于依次填充模板
713 | ],
714 | ]);
715 | ```
716 |
717 | ### [云之讯](https://www.ucpaas.com/index.html)
718 |
719 | 短信内容使用 `template` + `data`
720 |
721 | ```php
722 | 'yunzhixun' => [
723 | 'sid' => '',
724 | 'token' => '',
725 | 'app_id' => '',
726 | ],
727 | ```
728 |
729 | ```php
730 | $easySms->send(18888888888, [
731 | 'template' => 'SMS_001',
732 | 'data' => [
733 | 'params' => '8946,3', // 模板参数,多个参数使用 `,` 分割,模板无参数时可为空
734 | 'uid' => 'hexianghui', // 用户 ID,随状态报告返回,可为空
735 | 'mobiles' => '18888888888,188888888889', // 批量发送短信,手机号使用 `,` 分割,不使用批量发送请不要设置该参数
736 | ],
737 | ]);
738 | ```
739 |
740 | ### [凯信通](http://www.kingtto.cn/)
741 |
742 | 短信内容使用 `content`
743 |
744 | ```php
745 | 'kingtto' => [
746 | 'userid' => '',
747 | 'account' => '',
748 | 'password' => '',
749 | ],
750 | ```
751 |
752 | ```php
753 | $easySms->send(18888888888, [
754 | 'content' => '您的验证码为: 6379',
755 | ]);
756 | ```
757 |
758 | ### [七牛云](https://www.qiniu.com/)
759 |
760 | 短信内容使用 `template` + `data`
761 |
762 | ```php
763 | 'qiniu' => [
764 | 'secret_key' => '',
765 | 'access_key' => '',
766 | ],
767 | ```
768 |
769 | ```php
770 | $easySms->send(18888888888, [
771 | 'template' => '1231234123412341234',
772 | 'data' => [
773 | 'code' => 1234,
774 | ],
775 | ]);
776 | ```
777 |
778 | ### [Ucloud](https://www.ucloud.cn/)
779 |
780 | 短信使用 `template` + `data`
781 |
782 | ```php
783 | 'ucloud' => [
784 | 'private_key' => '', //私钥
785 | 'public_key' => '', //公钥
786 | 'sig_content' => '', // 短信签名,
787 | 'project_id' => '', //项目ID,子账号才需要该参数
788 | ],
789 | ```
790 |
791 | ```php
792 | $easySms->send(18888888888, [
793 | 'template' => 'UTAXXXXX', //短信模板
794 | 'data' => [
795 | 'code' => 1234, //模板参数,模板没有参数不用则填写,有多个参数请用数组,[1111,1111]
796 | 'mobiles' =>'', //同时发送多个手机短信,请用数组[xxx,xxx]
797 | ],
798 | ]);
799 |
800 | ```
801 |
802 | ### [短信宝](http://www.smsbao.com/)
803 |
804 | 短信使用 `content`
805 |
806 | ```php
807 | 'smsbao' => [
808 | 'user' => '', //账号
809 | 'password' => '' //密码
810 | ],
811 | ```
812 |
813 | ```php
814 | $easySms->send(18888888888, [
815 | 'content' => '您的验证码为: 6379', //短信模板
816 | ]);
817 |
818 | ```
819 |
820 | ### [摩杜云](https://www.moduyun.com/)
821 |
822 | 短信使用 `template` + `data`
823 |
824 | ```php
825 | 'moduyun' => [
826 | 'accesskey' => '', //必填 ACCESS KEY
827 | 'secretkey' => '', //必填 SECRET KEY
828 | 'signId' => '', //选填 短信签名,如果使用默认签名,该字段可缺省
829 | 'type' => 0, //选填 0:普通短信;1:营销短信
830 | ],
831 | ```
832 |
833 | ```php
834 | $easySms->send(18888888888, [
835 | 'template' => '5a95****b953', //短信模板
836 | 'data' => [
837 | 1234, //模板参数,对应模板的{1}
838 | 30 //模板参数,对应模板的{2}
839 | //...
840 | ],
841 | ]);
842 |
843 | ```
844 |
845 | ### [融合云(助通)](https://www.ztinfo.cn/products/sms)
846 |
847 | 短信使用 `template` + `data`
848 |
849 | ```php
850 | 'rongheyun' => [
851 | 'username' => '', //必填 用户名
852 | 'password' => '', //必填 密码
853 | 'signature'=> '', //必填 已报备的签名
854 | ],
855 | ```
856 |
857 | ```php
858 | $easySms->send(18888888888, [
859 | 'template' => '31874', //短信模板
860 | 'data' => [
861 | 'valid_code' => '888888', //模板参数,对应模板的{valid_code}
862 | //...
863 | ],
864 | ]);
865 |
866 | ```
867 |
868 | ### [蜘蛛云](https://zzyun.com/)
869 |
870 | 短信使用 `template` + `data`
871 |
872 | ```php
873 | 'zzyun' => [
874 | 'user_id' => '', //必填 会员ID
875 | 'secret' => '', //必填 接口密钥
876 | 'sign_name'=> '', //必填 短信签名
877 | ],
878 | ```
879 |
880 | ```php
881 | $easySms->send(18888888888, [
882 | 'template' => 'SMS_210317****', //短信模板
883 | 'data' => [
884 | 'code' => '888888', //模板参数,对应模板的{code}
885 | //...
886 | ],
887 | ]);
888 |
889 | ```
890 |
891 | ### [融合云信](https://maap.wo.cn/)
892 |
893 | 短信使用 `template` + `data`
894 |
895 | ```php
896 | 'maap' => [
897 | 'cpcode' => '', //必填 商户编码
898 | 'key' => '', //必填 接口密钥
899 | 'excode'=> '', //选填 扩展名
900 | ],
901 | ```
902 |
903 | ```php
904 | $easySms->send(18888888888, [
905 | 'template' => '356120', //短信模板
906 | 'data' => [
907 | '123465'
908 | ],//模板参数
909 | ]);
910 |
911 | ```
912 |
913 | ### [天瑞云](http://cms.tinree.com/)
914 |
915 | 短信内容使用 `template` + `data`
916 |
917 | ```php
918 | 'tinree' => [
919 | 'accesskey' => '', // 平台分配给用户的accesskey
920 | 'secret' => '', // 平台分配给用户的secret
921 | 'sign' => '', // 平台上申请的接口短信签名或者签名ID
922 | ],
923 | ```
924 |
925 | 发送示例:
926 |
927 | ```php
928 | $easySms->send(18888888888, [
929 | 'template' => '123456', // 模板ID
930 | 'data' => [
931 | "a", 'b', 'c', //按模板变量占位顺序
932 | ],
933 | ]);
934 | ```
935 |
936 | ### [时代互联](https://www.now.cn/)
937 |
938 | 短信使用 `content`
939 |
940 | ```php
941 | 'nowcn' => [
942 | 'key' => '', //用户ID
943 | 'secret' => '', //开发密钥
944 | 'api_type' => '', // 短信通道,
945 | ],
946 | ```
947 |
948 | 发送示例:
949 |
950 | ```php
951 | $easySms->send(18888888888, [
952 | 'content' => '您的验证码为: 6379',
953 | ]);
954 | ```
955 |
956 | ### [火山引擎](https://console.volcengine.com/sms/)
957 |
958 | 短信内容使用 `template` + `data`
959 |
960 | ```php
961 | 'volcengine' => [
962 | 'access_key_id' => '', // 平台分配给用户的access_key_id
963 | 'access_key_secret' => '', // 平台分配给用户的access_key_secret
964 | 'region_id' => 'cn-north-1', // 国内节点 cn-north-1,国外节点 ap-singapore-1,不填或填错,默认使用国内节点
965 | 'sign_name' => '', // 平台上申请的接口短信签名或者签名ID,可不填,发送短信时data中指定
966 | 'sms_account' => '', // 消息组帐号,火山短信页面右上角,短信应用括号中的字符串,可不填,发送短信时data中指定
967 | ],
968 | ```
969 |
970 | 发送示例1:
971 |
972 | ```php
973 | $easySms->send(18888888888, [
974 | 'template' => 'SMS_123456', // 模板ID
975 | 'data' => [
976 | "code" => 1234 // 模板变量
977 | ],
978 | ]);
979 | ```
980 |
981 | 发送示例2:
982 |
983 | ```php
984 | $easySms->send(18888888888, [
985 | 'template' => 'SMS_123456', // 模板ID
986 | 'data' => [
987 | "template_param" => ["code" => 1234], // 模板变量参数
988 | "sign_name" => "yoursignname", // 签名,覆盖配置文件中的sign_name
989 | "sms_account" => "yoursmsaccount", // 消息组帐号,覆盖配置文件中的sms_account
990 | "phone_numbers" => "18888888888,18888888889", // 手机号,批量发送,英文的逗号连接多个手机号,覆盖发送方法中的填入的手机号
991 | ],
992 | ]);
993 | ```
994 |
995 | ### [移动云MAS(黑名单模式)](https://mas.10086.cn/)
996 |
997 | 短信内容使用 `template` + `data`
998 |
999 | ```php
1000 | 'yidongmasblack' => [
1001 | 'ecName' => '', // 机构名称
1002 | 'secretKey' => '', // 密钥
1003 | 'apId' => '', // 应用ID
1004 | 'sign' => '', // 签名
1005 | 'addSerial' => '', // 通道号默认空
1006 | ],
1007 | ```
1008 |
1009 | 发送示例:
1010 |
1011 | ```php
1012 | $easySms->send(18888888888, [
1013 | 'content' => '您的验证码为: 6379',
1014 | ]);
1015 | ```
1016 |
1017 | ### [电信天翼云](https://www.ctyun.cn/)
1018 |
1019 | 短信使用 `content`
1020 |
1021 | ```php
1022 | 'ctyun' => [
1023 | 'access_key' => '', //用户access
1024 | 'secret_key' => '', //开发密钥secret
1025 | 'sign' => '验证码测试', // 短信下发签名,
1026 | ],
1027 | ```
1028 |
1029 | 发送示例:
1030 |
1031 | ```php
1032 | $easySms->send(18888888888, [
1033 | 'content' => $content,
1034 | 'template' => 'SMS64124870510', // 模板ID
1035 | 'data' => [
1036 | "code" => 123456,
1037 | ],
1038 | ]);
1039 | ```
1040 |
1041 | ## :heart: 支持我
1042 |
1043 | [](https://github.com/sponsors/overtrue)
1044 |
1045 | 如果你喜欢我的项目并想支持它,[点击这里 :heart:](https://github.com/sponsors/overtrue)
1046 |
1047 | ## Project supported by JetBrains
1048 |
1049 | Many thanks to Jetbrains for kindly providing a license for me to work on this and other open-source projects.
1050 |
1051 | [](https://www.jetbrains.com/?from=https://github.com/overtrue)
1052 |
1053 | ## PHP 扩展包开发
1054 |
1055 | > 想知道如何从零开始构建 PHP 扩展包?
1056 | >
1057 | > 请关注我的实战课程,我会在此课程中分享一些扩展开发经验 —— [《PHP 扩展包实战教程 - 从入门到发布》](https://learnku.com/courses/creating-package)
1058 |
1059 | ## License
1060 |
1061 | MIT
1062 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "overtrue/easy-sms",
3 | "description": "The easiest way to send short message.",
4 | "type": "library",
5 | "require": {
6 | "guzzlehttp/guzzle": "^6.2 || ^7.0",
7 | "php": ">=8.0",
8 | "ext-json": "*"
9 | },
10 | "require-dev": {
11 | "phpunit/phpunit": "^9.5.8",
12 | "mockery/mockery": "^1.4.2",
13 | "jetbrains/phpstorm-attributes": "^1.0",
14 | "friendsofphp/php-cs-fixer": "^3.54"
15 | },
16 | "autoload": {
17 | "psr-4": {
18 | "Overtrue\\EasySms\\": "src"
19 | }
20 | },
21 | "autoload-dev": {
22 | "psr-4": {
23 | "Overtrue\\EasySms\\Tests\\": "tests"
24 | }
25 | },
26 | "license": "MIT",
27 | "authors": [
28 | {
29 | "name": "overtrue",
30 | "email": "i@overtrue.me"
31 | }
32 | ],
33 | "scripts": {
34 | "phpstan": "phpstan analyse",
35 | "check-style": "php-cs-fixer fix --using-cache=no --diff --config=.php-cs-fixer.dist.php --dry-run --allow-risky=yes --ansi",
36 | "fix-style": "php-cs-fixer fix --using-cache=no --config=.php-cs-fixer.dist.php --allow-risky=yes --ansi",
37 | "test": "phpunit --colors",
38 | "psalm": "psalm --show-info=true --no-cache",
39 | "psalm-fix": "psalm --no-cache --alter --issues=MissingReturnType,MissingParamType"
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/psalm.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/src/Contracts/GatewayInterface.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * This source file is subject to the MIT license that is bundled
9 | * with this source code in the file LICENSE.
10 | */
11 |
12 | namespace Overtrue\EasySms\Contracts;
13 |
14 | use Overtrue\EasySms\Support\Config;
15 |
16 | /**
17 | * Class GatewayInterface.
18 | */
19 | interface GatewayInterface
20 | {
21 | /**
22 | * Get gateway name.
23 | *
24 | * @return string
25 | */
26 | public function getName();
27 |
28 | /**
29 | * Send a short message.
30 | *
31 | * @return array
32 | */
33 | public function send(PhoneNumberInterface $to, MessageInterface $message, Config $config);
34 | }
35 |
--------------------------------------------------------------------------------
/src/Contracts/MessageInterface.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * This source file is subject to the MIT license that is bundled
9 | * with this source code in the file LICENSE.
10 | */
11 |
12 | namespace Overtrue\EasySms\Contracts;
13 |
14 | /**
15 | * Interface MessageInterface.
16 | */
17 | interface MessageInterface
18 | {
19 | public const TEXT_MESSAGE = 'text';
20 |
21 | public const VOICE_MESSAGE = 'voice';
22 |
23 | /**
24 | * Return the message type.
25 | */
26 | public function getMessageType(): string;
27 |
28 | /**
29 | * Return message content.
30 | */
31 | public function getContent(?GatewayInterface $gateway = null): ?string;
32 |
33 | /**
34 | * Return the template id of message.
35 | */
36 | public function getTemplate(?GatewayInterface $gateway = null): ?string;
37 |
38 | /**
39 | * Return the template data of message.
40 | */
41 | public function getData(?GatewayInterface $gateway = null): array;
42 |
43 | /**
44 | * Return message supported gateways.
45 | */
46 | public function getGateways(): array;
47 | }
48 |
--------------------------------------------------------------------------------
/src/Contracts/PhoneNumberInterface.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * This source file is subject to the MIT license that is bundled
9 | * with this source code in the file LICENSE.
10 | */
11 |
12 | namespace Overtrue\EasySms\Contracts;
13 |
14 | /**
15 | * Interface PhoneNumberInterface.
16 | *
17 | * @author overtrue
18 | */
19 | interface PhoneNumberInterface extends \JsonSerializable
20 | {
21 | /**
22 | * 86.
23 | */
24 | public function getIDDCode(): ?int;
25 |
26 | /**
27 | * 18888888888.
28 | */
29 | public function getNumber(): int|string;
30 |
31 | /**
32 | * +8618888888888.
33 | */
34 | public function getUniversalNumber(): string;
35 |
36 | /**
37 | * 008618888888888.
38 | */
39 | public function getZeroPrefixedNumber(): string;
40 |
41 | /**
42 | * @return string
43 | */
44 | public function __toString();
45 | }
46 |
--------------------------------------------------------------------------------
/src/Contracts/StrategyInterface.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * This source file is subject to the MIT license that is bundled
9 | * with this source code in the file LICENSE.
10 | */
11 |
12 | namespace Overtrue\EasySms\Contracts;
13 |
14 | /**
15 | * Interface StrategyInterface.
16 | */
17 | interface StrategyInterface
18 | {
19 | /**
20 | * Apply the strategy and return result.
21 | *
22 | * @return array
23 | */
24 | public function apply(array $gateways);
25 | }
26 |
--------------------------------------------------------------------------------
/src/EasySms.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * This source file is subject to the MIT license that is bundled
9 | * with this source code in the file LICENSE.
10 | */
11 |
12 | namespace Overtrue\EasySms;
13 |
14 | use Closure;
15 | use Overtrue\EasySms\Contracts\GatewayInterface;
16 | use Overtrue\EasySms\Contracts\MessageInterface;
17 | use Overtrue\EasySms\Contracts\PhoneNumberInterface;
18 | use Overtrue\EasySms\Contracts\StrategyInterface;
19 | use Overtrue\EasySms\Exceptions\InvalidArgumentException;
20 | use Overtrue\EasySms\Exceptions\NoGatewayAvailableException;
21 | use Overtrue\EasySms\Gateways\Gateway;
22 | use Overtrue\EasySms\Strategies\OrderStrategy;
23 | use Overtrue\EasySms\Support\Config;
24 |
25 | /**
26 | * Class EasySms.
27 | */
28 | class EasySms
29 | {
30 | protected Config $config;
31 |
32 | protected string $defaultGateway;
33 |
34 | protected array $customCreators = [];
35 |
36 | protected array $gateways = [];
37 |
38 | protected Messenger $messenger;
39 |
40 | protected array $strategies = [];
41 |
42 | /**
43 | * Constructor.
44 | */
45 | public function __construct(array $config)
46 | {
47 | $this->config = new Config($config);
48 | }
49 |
50 | /**
51 | * Send a message.
52 | *
53 | * @throws NoGatewayAvailableException
54 | * @throws InvalidArgumentException
55 | */
56 | public function send(array|string $to, MessageInterface|array $message, array $gateways = []): array
57 | {
58 | $to = $this->formatPhoneNumber($to);
59 | $message = $this->formatMessage($message);
60 | $gateways = empty($gateways) ? $message->getGateways() : $gateways;
61 |
62 | if (empty($gateways)) {
63 | $gateways = $this->config->get('default.gateways', []);
64 | }
65 |
66 | return $this->getMessenger()->send($to, $message, $this->formatGateways($gateways));
67 | }
68 |
69 | /**
70 | * Create a gateway.
71 | *
72 | * @throws InvalidArgumentException
73 | */
74 | public function gateway(?string $name): GatewayInterface
75 | {
76 | if (!isset($this->gateways[$name])) {
77 | $this->gateways[$name] = $this->createGateway($name);
78 | }
79 |
80 | return $this->gateways[$name];
81 | }
82 |
83 | /**
84 | * Get a strategy instance.
85 | *
86 | * @throws InvalidArgumentException
87 | */
88 | public function strategy(?string $strategy = null): StrategyInterface
89 | {
90 | if (\is_null($strategy)) {
91 | $strategy = $this->config->get('default.strategy', OrderStrategy::class);
92 | }
93 |
94 | if (!\class_exists($strategy)) {
95 | $strategy = __NAMESPACE__ . '\Strategies\\' . \ucfirst($strategy);
96 | }
97 |
98 | if (!\class_exists($strategy)) {
99 | throw new InvalidArgumentException("Unsupported strategy \"{$strategy}\"");
100 | }
101 |
102 | if (empty($this->strategies[$strategy]) || !($this->strategies[$strategy] instanceof StrategyInterface)) {
103 | $this->strategies[$strategy] = new $strategy($this);
104 | }
105 |
106 | return $this->strategies[$strategy];
107 | }
108 |
109 | /**
110 | * Register a custom driver creator Closure.
111 | *
112 | * @return $this
113 | */
114 | public function extend(string $name, \Closure $callback): static
115 | {
116 | $this->customCreators[$name] = $callback;
117 |
118 | return $this;
119 | }
120 |
121 | public function getConfig(): Config
122 | {
123 | return $this->config;
124 | }
125 |
126 | public function getMessenger(): Messenger
127 | {
128 | return $this->messenger ??= new Messenger($this);
129 | }
130 |
131 | /**
132 | * Create a new driver instance.
133 | *
134 | * @throws \InvalidArgumentException
135 | * @throws InvalidArgumentException
136 | */
137 | protected function createGateway(string $name): GatewayInterface
138 | {
139 | $config = $this->config->get("gateways.{$name}", []);
140 |
141 | if (!isset($config['timeout'])) {
142 | $config['timeout'] = $this->config->get('timeout', Gateway::DEFAULT_TIMEOUT);
143 | }
144 |
145 | $config['options'] = $this->config->get('options', []);
146 |
147 | if (isset($this->customCreators[$name])) {
148 | $gateway = $this->callCustomCreator($name, $config);
149 | } else {
150 | $className = $this->formatGatewayClassName($name);
151 | $gateway = $this->makeGateway($className, $config);
152 | }
153 |
154 | if (!($gateway instanceof GatewayInterface)) {
155 | throw new InvalidArgumentException(\sprintf('Gateway "%s" must implement interface %s.', $name, GatewayInterface::class));
156 | }
157 |
158 | return $gateway;
159 | }
160 |
161 | /**
162 | * Make gateway instance.
163 | *
164 | * @throws InvalidArgumentException
165 | */
166 | protected function makeGateway(string $gateway, array $config): GatewayInterface
167 | {
168 | if (!\class_exists($gateway) || !\in_array(GatewayInterface::class, \class_implements($gateway))) {
169 | throw new InvalidArgumentException(\sprintf('Class "%s" is a invalid easy-sms gateway.', $gateway));
170 | }
171 |
172 | return new $gateway($config);
173 | }
174 |
175 | /**
176 | * Format gateway name.
177 | */
178 | protected function formatGatewayClassName(string $name): string
179 | {
180 | if (\class_exists($name) && \in_array(GatewayInterface::class, \class_implements($name))) {
181 | return $name;
182 | }
183 |
184 | $name = \ucfirst(\str_replace(['-', '_', ''], '', $name));
185 |
186 | return __NAMESPACE__ . "\\Gateways\\{$name}Gateway";
187 | }
188 |
189 | /**
190 | * Call a custom gateway creator.
191 | */
192 | protected function callCustomCreator(string $gateway, array $config): mixed
193 | {
194 | return \call_user_func($this->customCreators[$gateway], $config);
195 | }
196 |
197 | protected function formatPhoneNumber(PhoneNumberInterface|string $number): PhoneNumberInterface
198 | {
199 | if ($number instanceof PhoneNumberInterface) {
200 | return $number;
201 | }
202 |
203 | return new PhoneNumber(\trim($number));
204 | }
205 |
206 | protected function formatMessage(MessageInterface|array|string $message): MessageInterface
207 | {
208 | if (!($message instanceof MessageInterface)) {
209 | if (!\is_array($message)) {
210 | $message = [
211 | 'content' => $message,
212 | 'template' => $message,
213 | ];
214 | }
215 |
216 | $message = new Message($message);
217 | }
218 |
219 | return $message;
220 | }
221 |
222 | /**
223 | * @throws InvalidArgumentException
224 | */
225 | protected function formatGateways(array $gateways): array
226 | {
227 | $formatted = [];
228 |
229 | foreach ($gateways as $gateway => $setting) {
230 | if (\is_int($gateway) && \is_string($setting)) {
231 | $gateway = $setting;
232 | $setting = [];
233 | }
234 |
235 | $formatted[$gateway] = $setting;
236 | $globalSettings = $this->config->get("gateways.{$gateway}", []);
237 |
238 | if (\is_string($gateway) && !empty($globalSettings) && \is_array($setting)) {
239 | $formatted[$gateway] = new Config(\array_merge($globalSettings, $setting));
240 | }
241 | }
242 |
243 | $result = [];
244 |
245 | foreach ($this->strategy()->apply($formatted) as $name) {
246 | $result[$name] = $formatted[$name];
247 | }
248 |
249 | return $result;
250 | }
251 | }
--------------------------------------------------------------------------------
/src/Exceptions/Exception.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * This source file is subject to the MIT license that is bundled
9 | * with this source code in the file LICENSE.
10 | */
11 |
12 | namespace Overtrue\EasySms\Exceptions;
13 |
14 | /**
15 | * Class Exception.
16 | *
17 | * @author overtrue
18 | */
19 | class Exception extends \Exception
20 | {
21 | }
22 |
--------------------------------------------------------------------------------
/src/Exceptions/GatewayErrorException.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * This source file is subject to the MIT license that is bundled
9 | * with this source code in the file LICENSE.
10 | */
11 |
12 | namespace Overtrue\EasySms\Exceptions;
13 |
14 | /**
15 | * Class GatewayErrorException.
16 | */
17 | class GatewayErrorException extends Exception
18 | {
19 | /**
20 | * @var array
21 | */
22 | public $raw = [];
23 |
24 | /**
25 | * GatewayErrorException constructor.
26 | *
27 | * @param string $message
28 | * @param int $code
29 | */
30 | public function __construct($message, $code, array $raw = [])
31 | {
32 | parent::__construct($message, intval($code));
33 |
34 | $this->raw = $raw;
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/Exceptions/InvalidArgumentException.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * This source file is subject to the MIT license that is bundled
9 | * with this source code in the file LICENSE.
10 | */
11 |
12 | namespace Overtrue\EasySms\Exceptions;
13 |
14 | /**
15 | * Class InvalidArgumentException.
16 | */
17 | class InvalidArgumentException extends Exception
18 | {
19 | }
20 |
--------------------------------------------------------------------------------
/src/Exceptions/NoGatewayAvailableException.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * This source file is subject to the MIT license that is bundled
9 | * with this source code in the file LICENSE.
10 | */
11 |
12 | namespace Overtrue\EasySms\Exceptions;
13 |
14 | /**
15 | * Class NoGatewayAvailableException.
16 | *
17 | * @author overtrue
18 | */
19 | class NoGatewayAvailableException extends Exception
20 | {
21 | /**
22 | * @var array
23 | */
24 | public $results = [];
25 |
26 | /**
27 | * @var array
28 | */
29 | public $exceptions = [];
30 |
31 | /**
32 | * NoGatewayAvailableException constructor.
33 | *
34 | * @param int $code
35 | */
36 | public function __construct(array $results = [], $code = 0, ?\Throwable $previous = null)
37 | {
38 | $this->results = $results;
39 | $this->exceptions = \array_column($results, 'exception', 'gateway');
40 |
41 | parent::__construct('All the gateways have failed. You can get error details by `$exception->getExceptions()`', $code, $previous);
42 | }
43 |
44 | /**
45 | * @return array
46 | */
47 | public function getResults()
48 | {
49 | return $this->results;
50 | }
51 |
52 | /**
53 | * @param string $gateway
54 | *
55 | * @return mixed|null
56 | */
57 | public function getException($gateway)
58 | {
59 | return isset($this->exceptions[$gateway]) ? $this->exceptions[$gateway] : null;
60 | }
61 |
62 | /**
63 | * @return array
64 | */
65 | public function getExceptions()
66 | {
67 | return $this->exceptions;
68 | }
69 |
70 | public function getLastException()
71 | {
72 | return end($this->exceptions);
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/src/Gateways/AliyunGateway.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * This source file is subject to the MIT license that is bundled
9 | * with this source code in the file LICENSE.
10 | */
11 |
12 | namespace Overtrue\EasySms\Gateways;
13 |
14 | use Overtrue\EasySms\Contracts\MessageInterface;
15 | use Overtrue\EasySms\Contracts\PhoneNumberInterface;
16 | use Overtrue\EasySms\Exceptions\GatewayErrorException;
17 | use Overtrue\EasySms\Support\Config;
18 | use Overtrue\EasySms\Traits\HasHttpRequest;
19 |
20 | /**
21 | * Class AliyunGateway.
22 | *
23 | * @author carson
24 | *
25 | * @see https://help.aliyun.com/document_detail/55451.html
26 | */
27 | class AliyunGateway extends Gateway
28 | {
29 | use HasHttpRequest;
30 |
31 | public const ENDPOINT_URL = 'http://dysmsapi.aliyuncs.com';
32 |
33 | public const ENDPOINT_METHOD = 'SendSms';
34 |
35 | public const ENDPOINT_VERSION = '2017-05-25';
36 |
37 | public const ENDPOINT_FORMAT = 'JSON';
38 |
39 | public const ENDPOINT_REGION_ID = 'cn-hangzhou';
40 |
41 | public const ENDPOINT_SIGNATURE_METHOD = 'HMAC-SHA1';
42 |
43 | public const ENDPOINT_SIGNATURE_VERSION = '1.0';
44 |
45 | /**
46 | * @return array
47 | *
48 | * @throws GatewayErrorException ;
49 | */
50 | public function send(PhoneNumberInterface $to, MessageInterface $message, Config $config)
51 | {
52 | $data = $message->getData($this);
53 |
54 | $signName = !empty($data['sign_name']) ? $data['sign_name'] : $config->get('sign_name');
55 |
56 | unset($data['sign_name']);
57 |
58 | $params = [
59 | 'RegionId' => self::ENDPOINT_REGION_ID,
60 | 'AccessKeyId' => $config->get('access_key_id'),
61 | 'Format' => self::ENDPOINT_FORMAT,
62 | 'SignatureMethod' => self::ENDPOINT_SIGNATURE_METHOD,
63 | 'SignatureVersion' => self::ENDPOINT_SIGNATURE_VERSION,
64 | 'SignatureNonce' => uniqid(),
65 | 'Timestamp' => gmdate('Y-m-d\TH:i:s\Z'),
66 | 'Action' => self::ENDPOINT_METHOD,
67 | 'Version' => self::ENDPOINT_VERSION,
68 | 'PhoneNumbers' => !\is_null($to->getIDDCode()) ? strval($to->getZeroPrefixedNumber()) : $to->getNumber(),
69 | 'SignName' => $signName,
70 | 'TemplateCode' => $message->getTemplate($this),
71 | 'TemplateParam' => json_encode($data, JSON_FORCE_OBJECT),
72 | ];
73 |
74 | $params['Signature'] = $this->generateSign($params);
75 |
76 | $result = $this->get(self::ENDPOINT_URL, $params);
77 |
78 | if (!empty($result['Code']) && 'OK' != $result['Code']) {
79 | throw new GatewayErrorException($result['Message'], $result['Code'], $result);
80 | }
81 |
82 | return $result;
83 | }
84 |
85 | /**
86 | * Generate Sign.
87 | *
88 | * @param array $params
89 | *
90 | * @return string
91 | *
92 | * @see https://help.aliyun.com/document_detail/101343.html
93 | */
94 | protected function generateSign($params)
95 | {
96 | ksort($params);
97 | $accessKeySecret = $this->config->get('access_key_secret');
98 | $stringToSign = 'GET&%2F&'.urlencode(http_build_query($params, '', '&', PHP_QUERY_RFC3986));
99 | $stringToSign = str_replace('%7E', '~', $stringToSign);
100 |
101 | return base64_encode(hash_hmac('sha1', $stringToSign, $accessKeySecret.'&', true));
102 | }
103 | }
104 |
--------------------------------------------------------------------------------
/src/Gateways/AliyunIntlGateway.php:
--------------------------------------------------------------------------------
1 | getData($this);
42 |
43 | $signName = !empty($data['sign_name']) ? $data['sign_name'] : $config->get('sign_name');
44 |
45 | unset($data['sign_name']);
46 |
47 | $params = [
48 | 'RegionId' => self::ENDPOINT_REGION_ID,
49 | 'AccessKeyId' => $config->get('access_key_id'),
50 | 'Format' => self::ENDPOINT_FORMAT,
51 | 'SignatureMethod' => self::ENDPOINT_SIGNATURE_METHOD,
52 | 'SignatureVersion' => self::ENDPOINT_SIGNATURE_VERSION,
53 | 'SignatureNonce' => uniqid('', true),
54 | 'Timestamp' => gmdate('Y-m-d\TH:i:s\Z'),
55 | 'Version' => self::ENDPOINT_VERSION,
56 | 'To' => !\is_null($to->getIDDCode()) ? (int) $to->getZeroPrefixedNumber() : $to->getNumber(),
57 | 'Action' => self::ENDPOINT_ACTION,
58 | 'From' => $signName,
59 | 'TemplateCode' => $message->getTemplate($this),
60 | 'TemplateParam' => json_encode($data, JSON_FORCE_OBJECT),
61 | ];
62 |
63 | $params['Signature'] = $this->generateSign($params);
64 |
65 | $result = $this->get(self::ENDPOINT_URL, $params);
66 |
67 | if ('OK' !== $result['ResponseCode']) {
68 | throw new GatewayErrorException($result['ResponseDescription'], $result['ResponseCode'], $result);
69 | }
70 |
71 | return $result;
72 | }
73 |
74 | /**
75 | * Generate sign.
76 | *
77 | * @return string
78 | */
79 | protected function generateSign(array $params)
80 | {
81 | ksort($params);
82 | $accessKeySecret = $this->config->get('access_key_secret');
83 | $stringToSign = 'GET&%2F&'.urlencode(http_build_query($params, '', '&', PHP_QUERY_RFC3986));
84 |
85 | return base64_encode(hash_hmac('sha1', $stringToSign, $accessKeySecret.'&', true));
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/src/Gateways/AliyunrestGateway.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * This source file is subject to the MIT license that is bundled
9 | * with this source code in the file LICENSE.
10 | */
11 |
12 | namespace Overtrue\EasySms\Gateways;
13 |
14 | use Overtrue\EasySms\Contracts\MessageInterface;
15 | use Overtrue\EasySms\Contracts\PhoneNumberInterface;
16 | use Overtrue\EasySms\Exceptions\GatewayErrorException;
17 | use Overtrue\EasySms\Support\Config;
18 | use Overtrue\EasySms\Traits\HasHttpRequest;
19 |
20 | /**
21 | * Class AliyunrestGateway.
22 | */
23 | class AliyunrestGateway extends Gateway
24 | {
25 | use HasHttpRequest;
26 |
27 | public const ENDPOINT_URL = 'http://gw.api.taobao.com/router/rest';
28 |
29 | public const ENDPOINT_VERSION = '2.0';
30 |
31 | public const ENDPOINT_FORMAT = 'json';
32 |
33 | public const ENDPOINT_METHOD = 'alibaba.aliqin.fc.sms.num.send';
34 |
35 | public const ENDPOINT_SIGNATURE_METHOD = 'md5';
36 |
37 | public const ENDPOINT_PARTNER_ID = 'EasySms';
38 |
39 | /**
40 | * @return array|void
41 | */
42 | public function send(PhoneNumberInterface $to, MessageInterface $message, Config $config)
43 | {
44 | $urlParams = [
45 | 'app_key' => $config->get('app_key'),
46 | 'v' => self::ENDPOINT_VERSION,
47 | 'format' => self::ENDPOINT_FORMAT,
48 | 'sign_method' => self::ENDPOINT_SIGNATURE_METHOD,
49 | 'method' => self::ENDPOINT_METHOD,
50 | 'timestamp' => date('Y-m-d H:i:s'),
51 | 'partner_id' => self::ENDPOINT_PARTNER_ID,
52 | ];
53 |
54 | $params = [
55 | 'extend' => '',
56 | 'sms_type' => 'normal',
57 | 'sms_free_sign_name' => $config->get('sign_name'),
58 | 'sms_param' => json_encode($message->getData($this)),
59 | 'rec_num' => !\is_null($to->getIDDCode()) ? strval($to->getZeroPrefixedNumber()) : $to->getNumber(),
60 | 'sms_template_code' => $message->getTemplate($this),
61 | ];
62 | $urlParams['sign'] = $this->generateSign(array_merge($params, $urlParams));
63 |
64 | $result = $this->post($this->getEndpointUrl($urlParams), $params);
65 |
66 | if (isset($result['error_response']) && 0 != $result['error_response']['code']) {
67 | throw new GatewayErrorException($result['error_response']['msg'], $result['error_response']['code'], $result);
68 | }
69 |
70 | return $result;
71 | }
72 |
73 | /**
74 | * @param array $params
75 | *
76 | * @return string
77 | */
78 | protected function getEndpointUrl($params)
79 | {
80 | return self::ENDPOINT_URL.'?'.http_build_query($params);
81 | }
82 |
83 | /**
84 | * @param array $params
85 | *
86 | * @return string
87 | */
88 | protected function generateSign($params)
89 | {
90 | ksort($params);
91 |
92 | $stringToBeSigned = $this->config->get('app_secret_key');
93 | foreach ($params as $k => $v) {
94 | if (!is_array($v) && '@' != substr($v, 0, 1)) {
95 | $stringToBeSigned .= "$k$v";
96 | }
97 | }
98 | unset($k, $v);
99 | $stringToBeSigned .= $this->config->get('app_secret_key');
100 |
101 | return strtoupper(md5($stringToBeSigned));
102 | }
103 | }
104 |
--------------------------------------------------------------------------------
/src/Gateways/BaiduGateway.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * This source file is subject to the MIT license that is bundled
9 | * with this source code in the file LICENSE.
10 | */
11 |
12 | namespace Overtrue\EasySms\Gateways;
13 |
14 | use Overtrue\EasySms\Contracts\MessageInterface;
15 | use Overtrue\EasySms\Contracts\PhoneNumberInterface;
16 | use Overtrue\EasySms\Exceptions\GatewayErrorException;
17 | use Overtrue\EasySms\Support\Config;
18 | use Overtrue\EasySms\Traits\HasHttpRequest;
19 |
20 | /**
21 | * Class BaiduGateway.
22 | *
23 | * @see https://cloud.baidu.com/doc/SMS/index.html
24 | */
25 | class BaiduGateway extends Gateway
26 | {
27 | use HasHttpRequest;
28 |
29 | public const ENDPOINT_HOST = 'smsv3.bj.baidubce.com';
30 |
31 | public const ENDPOINT_URI = '/api/v3/sendSms';
32 |
33 | public const BCE_AUTH_VERSION = 'bce-auth-v1';
34 |
35 | public const DEFAULT_EXPIRATION_IN_SECONDS = 1800; // 签名有效期默认1800秒
36 |
37 | public const SUCCESS_CODE = 1000;
38 |
39 | /**
40 | * Send message.
41 | *
42 | * @return array
43 | *
44 | * @throws GatewayErrorException ;
45 | */
46 | public function send(PhoneNumberInterface $to, MessageInterface $message, Config $config)
47 | {
48 | $params = [
49 | 'signatureId' => $config->get('invoke_id'),
50 | 'mobile' => $to->getNumber(),
51 | 'template' => $message->getTemplate($this),
52 | 'contentVar' => $message->getData($this),
53 | ];
54 | if (!empty($params['contentVar']['custom'])) {
55 | // 用户自定义参数,格式为字符串,状态回调时会回传该值
56 | $params['custom'] = $params['contentVar']['custom'];
57 | unset($params['contentVar']['custom']);
58 | }
59 | if (!empty($params['contentVar']['userExtId'])) {
60 | // 通道自定义扩展码,上行回调时会回传该值,其格式为纯数字串。默认为不开通,请求时无需设置该参数。如需开通请联系客服申请
61 | $params['userExtId'] = $params['contentVar']['userExtId'];
62 | unset($params['contentVar']['userExtId']);
63 | }
64 |
65 | $datetime = gmdate('Y-m-d\TH:i:s\Z');
66 |
67 | $headers = [
68 | 'host' => self::ENDPOINT_HOST,
69 | 'content-type' => 'application/json',
70 | 'x-bce-date' => $datetime,
71 | ];
72 | // 获得需要签名的数据
73 | $signHeaders = $this->getHeadersToSign($headers, ['host', 'x-bce-date']);
74 |
75 | $headers['Authorization'] = $this->generateSign($signHeaders, $datetime, $config);
76 |
77 | $result = $this->request('post', self::buildEndpoint($config), ['headers' => $headers, 'json' => $params]);
78 |
79 | if (self::SUCCESS_CODE != $result['code']) {
80 | throw new GatewayErrorException($result['message'], $result['code'], $result);
81 | }
82 |
83 | return $result;
84 | }
85 |
86 | /**
87 | * Build endpoint url.
88 | *
89 | * @return string
90 | */
91 | protected function buildEndpoint(Config $config)
92 | {
93 | return 'http://'.$config->get('domain', self::ENDPOINT_HOST).self::ENDPOINT_URI;
94 | }
95 |
96 | /**
97 | * Generate Authorization header.
98 | *
99 | * @param int $datetime
100 | *
101 | * @return string
102 | */
103 | protected function generateSign(array $signHeaders, $datetime, Config $config)
104 | {
105 | // 生成 authString
106 | $authString = self::BCE_AUTH_VERSION.'/'.$config->get('ak').'/'
107 | .$datetime.'/'.self::DEFAULT_EXPIRATION_IN_SECONDS;
108 |
109 | // 使用 sk 和 authString 生成 signKey
110 | $signingKey = hash_hmac('sha256', $authString, $config->get('sk'));
111 | // 生成标准化 URI
112 | // 根据 RFC 3986,除了:1.大小写英文字符 2.阿拉伯数字 3.点'.'、波浪线'~'、减号'-'以及下划线'_' 以外都要编码
113 | $canonicalURI = str_replace('%2F', '/', rawurlencode(self::ENDPOINT_URI));
114 |
115 | // 生成标准化 QueryString
116 | $canonicalQueryString = ''; // 此 api 不需要此项。返回空字符串
117 |
118 | // 整理 headersToSign,以 ';' 号连接
119 | $signedHeaders = empty($signHeaders) ? '' : strtolower(trim(implode(';', array_keys($signHeaders))));
120 |
121 | // 生成标准化 header
122 | $canonicalHeader = $this->getCanonicalHeaders($signHeaders);
123 |
124 | // 组成标准请求串
125 | $canonicalRequest = "POST\n{$canonicalURI}\n{$canonicalQueryString}\n{$canonicalHeader}";
126 |
127 | // 使用 signKey 和标准请求串完成签名
128 | $signature = hash_hmac('sha256', $canonicalRequest, $signingKey);
129 |
130 | // 组成最终签名串
131 | return "{$authString}/{$signedHeaders}/{$signature}";
132 | }
133 |
134 | /**
135 | * 生成标准化 http 请求头串.
136 | *
137 | * @return string
138 | */
139 | protected function getCanonicalHeaders(array $headers)
140 | {
141 | $headerStrings = [];
142 | foreach ($headers as $name => $value) {
143 | // trim后再encode,之后使用':'号连接起来
144 | $headerStrings[] = rawurlencode(strtolower(trim($name))).':'.rawurlencode(trim($value));
145 | }
146 |
147 | sort($headerStrings);
148 |
149 | return implode("\n", $headerStrings);
150 | }
151 |
152 | /**
153 | * 根据 指定的 keys 过滤应该参与签名的 header.
154 | *
155 | * @return array
156 | */
157 | protected function getHeadersToSign(array $headers, array $keys)
158 | {
159 | return array_intersect_key($headers, array_flip($keys));
160 | }
161 | }
162 |
--------------------------------------------------------------------------------
/src/Gateways/ChuanglanGateway.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * This source file is subject to the MIT license that is bundled
9 | * with this source code in the file LICENSE.
10 | */
11 |
12 | namespace Overtrue\EasySms\Gateways;
13 |
14 | use Overtrue\EasySms\Contracts\MessageInterface;
15 | use Overtrue\EasySms\Contracts\PhoneNumberInterface;
16 | use Overtrue\EasySms\Exceptions\GatewayErrorException;
17 | use Overtrue\EasySms\Exceptions\InvalidArgumentException;
18 | use Overtrue\EasySms\Support\Config;
19 | use Overtrue\EasySms\Traits\HasHttpRequest;
20 |
21 | /**
22 | * Class ChuanglanGateway.
23 | *
24 | * @see https://zz.253.com/v5.html#/api_doc
25 | */
26 | class ChuanglanGateway extends Gateway
27 | {
28 | use HasHttpRequest;
29 |
30 | /**
31 | * URL模板
32 | */
33 | public const ENDPOINT_URL_TEMPLATE = 'https://%s.253.com/msg/send/json';
34 |
35 | /**
36 | * 国际短信
37 | */
38 | public const INT_URL = 'http://intapi.253.com/send/json';
39 |
40 | /**
41 | * 验证码渠道code.
42 | */
43 | public const CHANNEL_VALIDATE_CODE = 'smsbj1';
44 |
45 | /**
46 | * 会员营销渠道code.
47 | */
48 | public const CHANNEL_PROMOTION_CODE = 'smssh1';
49 |
50 | /**
51 | * @return array
52 | *
53 | * @throws GatewayErrorException
54 | * @throws InvalidArgumentException
55 | */
56 | public function send(PhoneNumberInterface $to, MessageInterface $message, Config $config)
57 | {
58 | $IDDCode = !empty($to->getIDDCode()) ? $to->getIDDCode() : 86;
59 |
60 | $params = [
61 | 'account' => $config->get('account'),
62 | 'password' => $config->get('password'),
63 | 'phone' => $to->getNumber(),
64 | 'msg' => $this->wrapChannelContent($message->getContent($this), $config, $IDDCode),
65 | ];
66 |
67 | if (86 != $IDDCode) {
68 | $params['mobile'] = $to->getIDDCode().$to->getNumber();
69 | $params['account'] = $config->get('intel_account') ?: $config->get('account');
70 | $params['password'] = $config->get('intel_password') ?: $config->get('password');
71 | }
72 |
73 | $result = $this->postJson($this->buildEndpoint($config, $IDDCode), $params);
74 |
75 | if (!isset($result['code']) || '0' != $result['code']) {
76 | throw new GatewayErrorException(json_encode($result, JSON_UNESCAPED_UNICODE), isset($result['code']) ? $result['code'] : 0, $result);
77 | }
78 |
79 | return $result;
80 | }
81 |
82 | /**
83 | * @param int $IDDCode
84 | *
85 | * @return string
86 | *
87 | * @throws InvalidArgumentException
88 | */
89 | protected function buildEndpoint(Config $config, $IDDCode = 86)
90 | {
91 | $channel = $this->getChannel($config, $IDDCode);
92 |
93 | if (self::INT_URL === $channel) {
94 | return $channel;
95 | }
96 |
97 | return sprintf(self::ENDPOINT_URL_TEMPLATE, $channel);
98 | }
99 |
100 | /**
101 | * @param int $IDDCode
102 | *
103 | * @throws InvalidArgumentException
104 | */
105 | protected function getChannel(Config $config, $IDDCode)
106 | {
107 | if (86 != $IDDCode) {
108 | return self::INT_URL;
109 | }
110 | $channel = $config->get('channel', self::CHANNEL_VALIDATE_CODE);
111 |
112 | if (!in_array($channel, [self::CHANNEL_VALIDATE_CODE, self::CHANNEL_PROMOTION_CODE])) {
113 | throw new InvalidArgumentException('Invalid channel for ChuanglanGateway.');
114 | }
115 |
116 | return $channel;
117 | }
118 |
119 | /**
120 | * @param string $content
121 | * @param int $IDDCode
122 | *
123 | * @return string|string
124 | *
125 | * @throws InvalidArgumentException
126 | */
127 | protected function wrapChannelContent($content, Config $config, $IDDCode)
128 | {
129 | $channel = $this->getChannel($config, $IDDCode);
130 |
131 | if (self::CHANNEL_PROMOTION_CODE == $channel) {
132 | $sign = (string) $config->get('sign', '');
133 | if (empty($sign)) {
134 | throw new InvalidArgumentException('Invalid sign for ChuanglanGateway when using promotion channel');
135 | }
136 |
137 | $unsubscribe = (string) $config->get('unsubscribe', '');
138 | if (empty($unsubscribe)) {
139 | throw new InvalidArgumentException('Invalid unsubscribe for ChuanglanGateway when using promotion channel');
140 | }
141 |
142 | $content = $sign.$content.$unsubscribe;
143 | }
144 |
145 | return $content;
146 | }
147 | }
148 |
--------------------------------------------------------------------------------
/src/Gateways/Chuanglanv1Gateway.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * This source file is subject to the MIT license that is bundled
9 | * with this source code in the file LICENSE.
10 | */
11 |
12 | namespace Overtrue\EasySms\Gateways;
13 |
14 | use Overtrue\EasySms\Contracts\MessageInterface;
15 | use Overtrue\EasySms\Contracts\PhoneNumberInterface;
16 | use Overtrue\EasySms\Exceptions\GatewayErrorException;
17 | use Overtrue\EasySms\Exceptions\InvalidArgumentException;
18 | use Overtrue\EasySms\Support\Config;
19 | use Overtrue\EasySms\Traits\HasHttpRequest;
20 |
21 | /**
22 | * Class ChuanglanGateway.
23 | *
24 | * @see https://www.chuanglan.com/document/6110e57909fd9600010209de/62b3dc1d272e290001af3e75
25 | */
26 | class Chuanglanv1Gateway extends Gateway
27 | {
28 | use HasHttpRequest;
29 |
30 | /**
31 | * 国际短信
32 | */
33 | public const INT_URL = 'http://intapi.253.com/send/json';
34 |
35 | /**
36 | * URL模板
37 | */
38 | public const ENDPOINT_URL_TEMPLATE = 'https://smssh1.253.com/msg/%s/json';
39 |
40 | /**
41 | * 支持单发、群发短信
42 | */
43 | public const CHANNEL_NORMAL_CODE = 'v1/send';
44 |
45 | /**
46 | * 单号码对应单内容批量下发.
47 | */
48 | public const CHANNEL_VARIABLE_CODE = 'variable';
49 |
50 | /**
51 | * @return array
52 | *
53 | * @throws GatewayErrorException
54 | * @throws InvalidArgumentException
55 | */
56 | public function send(PhoneNumberInterface $to, MessageInterface $message, Config $config)
57 | {
58 | $IDDCode = !empty($to->getIDDCode()) ? $to->getIDDCode() : 86;
59 |
60 | $params = [
61 | 'account' => $config->get('account'),
62 | 'password' => $config->get('password'),
63 | 'report' => $config->get('needstatus') ?? false,
64 | ];
65 |
66 | if (86 != $IDDCode) {
67 | $params['mobile'] = $to->getIDDCode().$to->getNumber();
68 | $params['account'] = $config->get('intel_account') ?: $config->get('account');
69 | $params['password'] = $config->get('intel_password') ?: $config->get('password');
70 | }
71 |
72 | if (self::CHANNEL_VARIABLE_CODE == $this->getChannel($config, $IDDCode)) {
73 | $params['params'] = $message->getData($this);
74 | $params['msg'] = $this->wrapChannelContent($message->getTemplate($this), $config, $IDDCode);
75 | } else {
76 | $params['phone'] = $to->getNumber();
77 | $params['msg'] = $this->wrapChannelContent($message->getContent($this), $config, $IDDCode);
78 | }
79 |
80 | $result = $this->postJson($this->buildEndpoint($config, $IDDCode), $params);
81 |
82 | if (!isset($result['code']) || '0' != $result['code']) {
83 | throw new GatewayErrorException(json_encode($result, JSON_UNESCAPED_UNICODE), isset($result['code']) ? $result['code'] : 0, $result);
84 | }
85 |
86 | return $result;
87 | }
88 |
89 | /**
90 | * @param int $IDDCode
91 | *
92 | * @return string
93 | *
94 | * @throws InvalidArgumentException
95 | */
96 | protected function buildEndpoint(Config $config, $IDDCode = 86)
97 | {
98 | $channel = $this->getChannel($config, $IDDCode);
99 |
100 | if (self::INT_URL === $channel) {
101 | return $channel;
102 | }
103 |
104 | return sprintf(self::ENDPOINT_URL_TEMPLATE, $channel);
105 | }
106 |
107 | /**
108 | * @param int $IDDCode
109 | *
110 | * @throws InvalidArgumentException
111 | */
112 | protected function getChannel(Config $config, $IDDCode)
113 | {
114 | if (86 != $IDDCode) {
115 | return self::INT_URL;
116 | }
117 | $channel = $config->get('channel', self::CHANNEL_NORMAL_CODE);
118 |
119 | if (!in_array($channel, [self::CHANNEL_NORMAL_CODE, self::CHANNEL_VARIABLE_CODE])) {
120 | throw new InvalidArgumentException('Invalid channel for ChuanglanGateway.');
121 | }
122 |
123 | return $channel;
124 | }
125 |
126 | /**
127 | * @param string $content
128 | * @param int $IDDCode
129 | *
130 | * @return string|string
131 | *
132 | * @throws InvalidArgumentException
133 | */
134 | protected function wrapChannelContent($content, Config $config, $IDDCode)
135 | {
136 | return $content;
137 | }
138 | }
139 |
--------------------------------------------------------------------------------
/src/Gateways/CtyunGateway.php:
--------------------------------------------------------------------------------
1 | getData($this);
34 | $endpoint = self::ENDPOINT_HOST.'/sms/api/v1';
35 |
36 | return $this->execute($endpoint, [
37 | 'phoneNumber' => (string) $to,
38 | 'templateCode' => $this->config->get('template_code'),
39 | 'templateParam' => '{"code":"'.$data['code'].'"}',
40 | 'signName' => $this->config->get('sign_name'),
41 | 'action' => 'SendSms',
42 | ]);
43 | }
44 |
45 | /**
46 | * @return array
47 | *
48 | * @throws GatewayErrorException
49 | */
50 | protected function execute(string $url, array $data)
51 | {
52 | $uuid = date('ymdHis', time()).substr(microtime(), 2, 6).sprintf('%03d', rand(0, 999));
53 | $time = date('Ymd', time()).'T'.date('His').'Z';
54 | $timeDate = substr($time, 0, 8);
55 |
56 | $body = bin2hex(hash('sha256', json_encode($data), true));
57 | $query = '';
58 | $strSignature = 'ctyun-eop-request-id:'.$uuid."\n".'eop-date:'.$time."\n\n".$query."\n".$body;
59 |
60 | $kTime = $this->sha256($time, $this->config->get('secret_key'));
61 | $kAk = $this->sha256($this->config->get('access_key'), $kTime);
62 |
63 | $kDate = $this->sha256($timeDate, $kAk);
64 |
65 | $signature = base64_encode($this->sha256($strSignature, $kDate));
66 | $headers['Content-Type'] = 'application/json';
67 | $headers['ctyun-eop-request-id'] = $uuid;
68 | $headers['Eop-Authorization'] = $this->config->get('access_key').' Headers=ctyun-eop-request-id;eop-date Signature='.$signature;
69 | $headers['eop-date'] = $time;
70 |
71 | $result = $this->postJson($url, $data, $headers);
72 | if (self::SUCCESS_CODE !== $result['code']) {
73 | throw new GatewayErrorException($result['message'], $result['code'], $result);
74 | }
75 |
76 | return $result;
77 | }
78 |
79 | public function sha256($str, $pass): string
80 | {
81 | return hash_hmac('sha256', $str, $pass, true);
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/src/Gateways/ErrorlogGateway.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * This source file is subject to the MIT license that is bundled
9 | * with this source code in the file LICENSE.
10 | */
11 |
12 | namespace Overtrue\EasySms\Gateways;
13 |
14 | use Overtrue\EasySms\Contracts\MessageInterface;
15 | use Overtrue\EasySms\Contracts\PhoneNumberInterface;
16 | use Overtrue\EasySms\Support\Config;
17 |
18 | /**
19 | * Class ErrorlogGateway.
20 | */
21 | class ErrorlogGateway extends Gateway
22 | {
23 | /**
24 | * @return array
25 | */
26 | public function send(PhoneNumberInterface|array $to, MessageInterface $message, Config $config)
27 | {
28 | if (is_array($to)) {
29 | $to = implode(',', $to);
30 | }
31 |
32 | $message = sprintf(
33 | "[%s] to: %s | message: \"%s\" | template: \"%s\" | data: %s\n",
34 | date('Y-m-d H:i:s'),
35 | $to,
36 | $message->getContent($this),
37 | $message->getTemplate($this),
38 | json_encode($message->getData($this))
39 | );
40 |
41 | $file = $this->config->get('file', ini_get('error_log'));
42 | $status = error_log($message, 3, $file);
43 |
44 | return compact('status', 'file');
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/Gateways/Gateway.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * This source file is subject to the MIT license that is bundled
9 | * with this source code in the file LICENSE.
10 | */
11 |
12 | namespace Overtrue\EasySms\Gateways;
13 |
14 | use Overtrue\EasySms\Contracts\GatewayInterface;
15 | use Overtrue\EasySms\Support\Config;
16 |
17 | /**
18 | * Class Gateway.
19 | */
20 | abstract class Gateway implements GatewayInterface
21 | {
22 | public const DEFAULT_TIMEOUT = 5.0;
23 |
24 | /**
25 | * @var Config
26 | */
27 | protected $config;
28 |
29 | /**
30 | * @var array
31 | */
32 | protected $options;
33 |
34 | /**
35 | * @var float
36 | */
37 | protected $timeout;
38 |
39 | /**
40 | * Gateway constructor.
41 | */
42 | public function __construct(array $config)
43 | {
44 | $this->config = new Config($config);
45 | }
46 |
47 | /**
48 | * Return timeout.
49 | *
50 | * @return int|mixed
51 | */
52 | public function getTimeout()
53 | {
54 | return $this->timeout ?: $this->config->get('timeout', self::DEFAULT_TIMEOUT);
55 | }
56 |
57 | /**
58 | * Set timeout.
59 | *
60 | * @param int $timeout
61 | *
62 | * @return $this
63 | */
64 | public function setTimeout($timeout)
65 | {
66 | $this->timeout = floatval($timeout);
67 |
68 | return $this;
69 | }
70 |
71 | /**
72 | * @return Config
73 | */
74 | public function getConfig()
75 | {
76 | return $this->config;
77 | }
78 |
79 | /**
80 | * @return $this
81 | */
82 | public function setConfig(Config $config)
83 | {
84 | $this->config = $config;
85 |
86 | return $this;
87 | }
88 |
89 | /**
90 | * @return $this
91 | */
92 | public function setGuzzleOptions($options)
93 | {
94 | $this->options = $options;
95 |
96 | return $this;
97 | }
98 |
99 | /**
100 | * @return array
101 | */
102 | public function getGuzzleOptions()
103 | {
104 | return $this->options ?: $this->config->get('options', []);
105 | }
106 |
107 | public function getName()
108 | {
109 | return \strtolower(str_replace([__NAMESPACE__.'\\', 'Gateway'], '', \get_class($this)));
110 | }
111 | }
112 |
--------------------------------------------------------------------------------
/src/Gateways/HuaweiGateway.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * This source file is subject to the MIT license that is bundled
9 | * with this source code in the file LICENSE.
10 | */
11 |
12 | namespace Overtrue\EasySms\Gateways;
13 |
14 | use GuzzleHttp\Exception\RequestException;
15 | use Overtrue\EasySms\Contracts\MessageInterface;
16 | use Overtrue\EasySms\Contracts\PhoneNumberInterface;
17 | use Overtrue\EasySms\Exceptions\GatewayErrorException;
18 | use Overtrue\EasySms\Exceptions\InvalidArgumentException;
19 | use Overtrue\EasySms\Support\Config;
20 | use Overtrue\EasySms\Traits\HasHttpRequest;
21 |
22 | class HuaweiGateway extends Gateway
23 | {
24 | use HasHttpRequest;
25 |
26 | public const ENDPOINT_HOST = 'https://api.rtc.huaweicloud.com:10443';
27 |
28 | public const ENDPOINT_URI = '/sms/batchSendSms/v1';
29 |
30 | public const SUCCESS_CODE = '000000';
31 |
32 | /**
33 | * 发送信息.
34 | *
35 | * @return array
36 | *
37 | * @throws GatewayErrorException
38 | * @throws InvalidArgumentException
39 | */
40 | public function send(PhoneNumberInterface $to, MessageInterface $message, Config $config)
41 | {
42 | $appKey = $config->get('app_key');
43 | $appSecret = $config->get('app_secret');
44 | $channels = $config->get('from');
45 | $statusCallback = $config->get('callback', '');
46 |
47 | $endpoint = $this->getEndpoint($config);
48 | $headers = $this->getHeaders($appKey, $appSecret);
49 |
50 | $templateId = $message->getTemplate($this);
51 | $messageData = $message->getData($this);
52 |
53 | // 短信签名通道号码
54 | $from = 'default';
55 | if (isset($messageData['from'])) {
56 | $from = $messageData['from'];
57 | unset($messageData['from']);
58 | }
59 | $channel = isset($channels[$from]) ? $channels[$from] : '';
60 |
61 | if (empty($channel)) {
62 | throw new InvalidArgumentException("From Channel [{$from}] Not Exist");
63 | }
64 |
65 | $params = [
66 | 'from' => $channel,
67 | 'to' => $to->getUniversalNumber(),
68 | 'templateId' => $templateId,
69 | 'templateParas' => json_encode($messageData),
70 | 'statusCallback' => $statusCallback,
71 | ];
72 |
73 | try {
74 | $result = $this->request('post', $endpoint, [
75 | 'headers' => $headers,
76 | 'form_params' => $params,
77 | // 为防止因HTTPS证书认证失败造成API调用失败,需要先忽略证书信任问题
78 | 'verify' => false,
79 | ]);
80 | } catch (RequestException $e) {
81 | $result = $this->unwrapResponse($e->getResponse());
82 | }
83 |
84 | if (self::SUCCESS_CODE != $result['code']) {
85 | throw new GatewayErrorException($result['description'], ltrim($result['code'], 'E'), $result);
86 | }
87 |
88 | return $result;
89 | }
90 |
91 | /**
92 | * 构造 Endpoint.
93 | *
94 | * @return string
95 | */
96 | protected function getEndpoint(Config $config)
97 | {
98 | $endpoint = rtrim($config->get('endpoint', self::ENDPOINT_HOST), '/');
99 |
100 | return $endpoint.self::ENDPOINT_URI;
101 | }
102 |
103 | /**
104 | * 获取请求 Headers 参数.
105 | *
106 | * @param string $appKey
107 | * @param string $appSecret
108 | *
109 | * @return array
110 | */
111 | protected function getHeaders($appKey, $appSecret)
112 | {
113 | return [
114 | 'Content-Type' => 'application/x-www-form-urlencoded',
115 | 'Authorization' => 'WSSE realm="SDP",profile="UsernameToken",type="Appkey"',
116 | 'X-WSSE' => $this->buildWsseHeader($appKey, $appSecret),
117 | ];
118 | }
119 |
120 | /**
121 | * 构造X-WSSE参数值
122 | *
123 | * @param string $appKey
124 | * @param string $appSecret
125 | *
126 | * @return string
127 | */
128 | protected function buildWsseHeader($appKey, $appSecret)
129 | {
130 | $now = date('Y-m-d\TH:i:s\Z');
131 | $nonce = uniqid();
132 | $passwordDigest = base64_encode(hash('sha256', $nonce.$now.$appSecret));
133 |
134 | return sprintf(
135 | 'UsernameToken Username="%s",PasswordDigest="%s",Nonce="%s",Created="%s"',
136 | $appKey,
137 | $passwordDigest,
138 | $nonce,
139 | $now
140 | );
141 | }
142 | }
143 |
--------------------------------------------------------------------------------
/src/Gateways/HuaxinGateway.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * This source file is subject to the MIT license that is bundled
9 | * with this source code in the file LICENSE.
10 | */
11 |
12 | namespace Overtrue\EasySms\Gateways;
13 |
14 | use Overtrue\EasySms\Contracts\MessageInterface;
15 | use Overtrue\EasySms\Contracts\PhoneNumberInterface;
16 | use Overtrue\EasySms\Exceptions\GatewayErrorException;
17 | use Overtrue\EasySms\Support\Config;
18 | use Overtrue\EasySms\Traits\HasHttpRequest;
19 |
20 | /**
21 | * Class HuaxinGateway.
22 | *
23 | * @see http://www.ipyy.com/help/
24 | */
25 | class HuaxinGateway extends Gateway
26 | {
27 | use HasHttpRequest;
28 |
29 | public const ENDPOINT_TEMPLATE = 'http://%s/smsJson.aspx';
30 |
31 | /**
32 | * @return array
33 | *
34 | * @throws GatewayErrorException ;
35 | */
36 | public function send(PhoneNumberInterface $to, MessageInterface $message, Config $config)
37 | {
38 | $endpoint = $this->buildEndpoint($config->get('ip'));
39 |
40 | $result = $this->post($endpoint, [
41 | 'userid' => $config->get('user_id'),
42 | 'account' => $config->get('account'),
43 | 'password' => $config->get('password'),
44 | 'mobile' => $to->getNumber(),
45 | 'content' => $message->getContent($this),
46 | 'sendTime' => '',
47 | 'action' => 'send',
48 | 'extno' => $config->get('ext_no'),
49 | ]);
50 |
51 | if ('Success' !== $result['returnstatus']) {
52 | throw new GatewayErrorException($result['message'], 400, $result);
53 | }
54 |
55 | return $result;
56 | }
57 |
58 | /**
59 | * Build endpoint url.
60 | *
61 | * @param string $ip
62 | *
63 | * @return string
64 | */
65 | protected function buildEndpoint($ip)
66 | {
67 | return sprintf(self::ENDPOINT_TEMPLATE, $ip);
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/src/Gateways/HuyiGateway.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * This source file is subject to the MIT license that is bundled
9 | * with this source code in the file LICENSE.
10 | */
11 |
12 | namespace Overtrue\EasySms\Gateways;
13 |
14 | use Overtrue\EasySms\Contracts\MessageInterface;
15 | use Overtrue\EasySms\Contracts\PhoneNumberInterface;
16 | use Overtrue\EasySms\Exceptions\GatewayErrorException;
17 | use Overtrue\EasySms\Support\Config;
18 | use Overtrue\EasySms\Traits\HasHttpRequest;
19 |
20 | /**
21 | * Class HuyiGateway.
22 | *
23 | * @see http://www.ihuyi.com/api/sms.html
24 | */
25 | class HuyiGateway extends Gateway
26 | {
27 | use HasHttpRequest;
28 |
29 | public const ENDPOINT_URL = 'http://106.ihuyi.com/webservice/sms.php?method=Submit';
30 |
31 | public const ENDPOINT_FORMAT = 'json';
32 |
33 | public const SUCCESS_CODE = 2;
34 |
35 | /**
36 | * @return array
37 | *
38 | * @throws GatewayErrorException ;
39 | */
40 | public function send(PhoneNumberInterface $to, MessageInterface $message, Config $config)
41 | {
42 | $params = [
43 | 'account' => $config->get('api_id'),
44 | 'mobile' => $to->getIDDCode() ? \sprintf('%s %s', $to->getIDDCode(), $to->getNumber()) : $to->getNumber(),
45 | 'content' => $message->getContent($this),
46 | 'time' => time(),
47 | 'format' => self::ENDPOINT_FORMAT,
48 | 'sign' => $config->get('signature'),
49 | ];
50 |
51 | $params['password'] = $this->generateSign($params);
52 |
53 | $result = $this->post(self::ENDPOINT_URL, $params);
54 |
55 | if (self::SUCCESS_CODE != $result['code']) {
56 | throw new GatewayErrorException($result['msg'], $result['code'], $result);
57 | }
58 |
59 | return $result;
60 | }
61 |
62 | /**
63 | * Generate Sign.
64 | *
65 | * @param array $params
66 | *
67 | * @return string
68 | */
69 | protected function generateSign($params)
70 | {
71 | return md5($params['account'].$this->config->get('api_key').$params['mobile'].$params['content'].$params['time']);
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/src/Gateways/JuheGateway.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * This source file is subject to the MIT license that is bundled
9 | * with this source code in the file LICENSE.
10 | */
11 |
12 | namespace Overtrue\EasySms\Gateways;
13 |
14 | use Overtrue\EasySms\Contracts\MessageInterface;
15 | use Overtrue\EasySms\Contracts\PhoneNumberInterface;
16 | use Overtrue\EasySms\Exceptions\GatewayErrorException;
17 | use Overtrue\EasySms\Support\Config;
18 | use Overtrue\EasySms\Traits\HasHttpRequest;
19 |
20 | /**
21 | * Class JuheGateway.
22 | *
23 | * @see https://www.juhe.cn/docs/api/id/54
24 | */
25 | class JuheGateway extends Gateway
26 | {
27 | use HasHttpRequest;
28 |
29 | public const ENDPOINT_URL = 'http://v.juhe.cn/sms/send';
30 |
31 | public const ENDPOINT_FORMAT = 'json';
32 |
33 | /**
34 | * @return array
35 | *
36 | * @throws GatewayErrorException ;
37 | */
38 | public function send(PhoneNumberInterface $to, MessageInterface $message, Config $config)
39 | {
40 | $params = [
41 | 'mobile' => $to->getNumber(),
42 | 'tpl_id' => $message->getTemplate($this),
43 | 'tpl_value' => $this->formatTemplateVars($message->getData($this)),
44 | 'dtype' => self::ENDPOINT_FORMAT,
45 | 'key' => $config->get('app_key'),
46 | ];
47 |
48 | $result = $this->get(self::ENDPOINT_URL, $params);
49 |
50 | if ($result['error_code']) {
51 | throw new GatewayErrorException($result['reason'], $result['error_code'], $result);
52 | }
53 |
54 | return $result;
55 | }
56 |
57 | /**
58 | * @return string
59 | */
60 | protected function formatTemplateVars(array $vars)
61 | {
62 | $formatted = [];
63 |
64 | foreach ($vars as $key => $value) {
65 | $formatted[sprintf('#%s#', trim($key, '#'))] = $value;
66 | }
67 |
68 | return urldecode(http_build_query($formatted));
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/src/Gateways/KingttoGateway.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * This source file is subject to the MIT license that is bundled
9 | * with this source code in the file LICENSE.
10 | */
11 |
12 | namespace Overtrue\EasySms\Gateways;
13 |
14 | use Overtrue\EasySms\Contracts\MessageInterface;
15 | use Overtrue\EasySms\Contracts\PhoneNumberInterface;
16 | use Overtrue\EasySms\Exceptions\GatewayErrorException;
17 | use Overtrue\EasySms\Support\Config;
18 | use Overtrue\EasySms\Traits\HasHttpRequest;
19 |
20 | /**
21 | * Class KingttoGateWay.
22 | *
23 | * @see http://www.kingtto.cn/
24 | */
25 | class KingttoGateway extends Gateway
26 | {
27 | use HasHttpRequest;
28 |
29 | public const ENDPOINT_URL = 'http://101.201.41.194:9999/sms.aspx';
30 |
31 | public const ENDPOINT_METHOD = 'send';
32 |
33 | /**
34 | * @return \Psr\Http\Message\ResponseInterface|array|string
35 | *
36 | * @throws GatewayErrorException
37 | */
38 | public function send(PhoneNumberInterface $to, MessageInterface $message, Config $config)
39 | {
40 | $params = [
41 | 'action' => self::ENDPOINT_METHOD,
42 | 'userid' => $config->get('userid'),
43 | 'account' => $config->get('account'),
44 | 'password' => $config->get('password'),
45 | 'mobile' => $to->getNumber(),
46 | 'content' => $message->getContent(),
47 | ];
48 |
49 | $result = $this->post(self::ENDPOINT_URL, $params);
50 |
51 | if ('Success' != $result['returnstatus']) {
52 | throw new GatewayErrorException($result['message'], $result['remainpoint'], $result);
53 | }
54 |
55 | return $result;
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/src/Gateways/LuosimaoGateway.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * This source file is subject to the MIT license that is bundled
9 | * with this source code in the file LICENSE.
10 | */
11 |
12 | namespace Overtrue\EasySms\Gateways;
13 |
14 | use Overtrue\EasySms\Contracts\MessageInterface;
15 | use Overtrue\EasySms\Contracts\PhoneNumberInterface;
16 | use Overtrue\EasySms\Exceptions\GatewayErrorException;
17 | use Overtrue\EasySms\Support\Config;
18 | use Overtrue\EasySms\Traits\HasHttpRequest;
19 |
20 | /**
21 | * Class LuosimaoGateway.
22 | *
23 | * @see https://luosimao.com/docs/api/
24 | */
25 | class LuosimaoGateway extends Gateway
26 | {
27 | use HasHttpRequest;
28 |
29 | public const ENDPOINT_TEMPLATE = 'https://%s.luosimao.com/%s/%s.%s';
30 |
31 | public const ENDPOINT_VERSION = 'v1';
32 |
33 | public const ENDPOINT_FORMAT = 'json';
34 |
35 | /**
36 | * @return array
37 | *
38 | * @throws GatewayErrorException ;
39 | */
40 | public function send(PhoneNumberInterface $to, MessageInterface $message, Config $config)
41 | {
42 | $endpoint = $this->buildEndpoint('sms-api', 'send');
43 |
44 | $result = $this->post($endpoint, [
45 | 'mobile' => $to->getNumber(),
46 | 'message' => $message->getContent($this),
47 | ], [
48 | 'Authorization' => 'Basic '.base64_encode('api:key-'.$config->get('api_key')),
49 | ]);
50 |
51 | if ($result['error']) {
52 | throw new GatewayErrorException($result['msg'], $result['error'], $result);
53 | }
54 |
55 | return $result;
56 | }
57 |
58 | /**
59 | * Build endpoint url.
60 | *
61 | * @param string $type
62 | * @param string $function
63 | *
64 | * @return string
65 | */
66 | protected function buildEndpoint($type, $function)
67 | {
68 | return sprintf(self::ENDPOINT_TEMPLATE, $type, self::ENDPOINT_VERSION, $function, self::ENDPOINT_FORMAT);
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/src/Gateways/MaapGateway.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * This source file is subject to the MIT license that is bundled
9 | * with this source code in the file LICENSE.
10 | */
11 |
12 | namespace Overtrue\EasySms\Gateways;
13 |
14 | use Overtrue\EasySms\Contracts\MessageInterface;
15 | use Overtrue\EasySms\Contracts\PhoneNumberInterface;
16 | use Overtrue\EasySms\Exceptions\GatewayErrorException;
17 | use Overtrue\EasySms\Support\Config;
18 | use Overtrue\EasySms\Traits\HasHttpRequest;
19 |
20 | /**
21 | * Class MaapGateway.
22 | *
23 | * @see https://maap.wo.cn/
24 | */
25 | class MaapGateway extends Gateway
26 | {
27 | use HasHttpRequest;
28 |
29 | public const ENDPOINT_URL = 'http://rcsapi.wo.cn:8000/umcinterface/sendtempletmsg';
30 |
31 | /**
32 | * Send message.
33 | *
34 | * @return array
35 | *
36 | * @throws GatewayErrorException
37 | */
38 | public function send(PhoneNumberInterface $to, MessageInterface $message, Config $config)
39 | {
40 | $params = [
41 | 'cpcode' => $config->get('cpcode'),
42 | 'msg' => implode(',', $message->getData($this)),
43 | 'mobiles' => $to->getNumber(),
44 | 'excode' => $config->get('excode', ''),
45 | 'templetid' => $message->getTemplate($this),
46 | ];
47 | $params['sign'] = $this->generateSign($params, $config->get('key'));
48 |
49 | $result = $this->postJson(self::ENDPOINT_URL, $params);
50 |
51 | if (0 != $result['resultcode']) {
52 | throw new GatewayErrorException($result['resultmsg'], $result['resultcode'], $result);
53 | }
54 |
55 | return $result;
56 | }
57 |
58 | /**
59 | * Generate Sign.
60 | *
61 | * @param array $params
62 | * @param string $key 签名Key
63 | *
64 | * @return string
65 | */
66 | protected function generateSign($params, $key)
67 | {
68 | return md5($params['cpcode'].$params['msg'].$params['mobiles'].$params['excode'].$params['templetid'].$key);
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/src/Gateways/ModuyunGateway.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * This source file is subject to the MIT license that is bundled
9 | * with this source code in the file LICENSE.
10 | */
11 |
12 | namespace Overtrue\EasySms\Gateways;
13 |
14 | use Overtrue\EasySms\Contracts\MessageInterface;
15 | use Overtrue\EasySms\Contracts\PhoneNumberInterface;
16 | use Overtrue\EasySms\Exceptions\GatewayErrorException;
17 | use Overtrue\EasySms\Support\Config;
18 | use Overtrue\EasySms\Traits\HasHttpRequest;
19 |
20 | /**
21 | * Class ModuyunGateway.
22 | *
23 | * @see https://www.moduyun.com/doc/index.html#10002
24 | */
25 | class ModuyunGateway extends Gateway
26 | {
27 | use HasHttpRequest;
28 |
29 | public const ENDPOINT_URL = 'https://live.moduyun.com/sms/v2/sendsinglesms';
30 |
31 | /**
32 | * @return array
33 | *
34 | * @throws GatewayErrorException ;
35 | */
36 | public function send(PhoneNumberInterface $to, MessageInterface $message, Config $config)
37 | {
38 | $urlParams = [
39 | 'accesskey' => $config->get('accesskey'),
40 | 'random' => rand(100000, 999999),
41 | ];
42 |
43 | $params = [
44 | 'tel' => [
45 | 'mobile' => $to->getNumber(),
46 | 'nationcode' => $to->getIDDCode() ?: '86',
47 | ],
48 | 'signId' => $config->get('signId', ''),
49 | 'templateId' => $message->getTemplate($this),
50 | 'time' => time(),
51 | 'type' => $config->get('type', 0),
52 | 'params' => array_values($message->getData($this)),
53 | 'ext' => '',
54 | 'extend' => '',
55 | ];
56 | $params['sig'] = $this->generateSign($params, $urlParams['random']);
57 |
58 | $result = $this->postJson($this->getEndpointUrl($urlParams), $params);
59 | $result = is_string($result) ? json_decode($result, true) : $result;
60 | if (0 != $result['result']) {
61 | throw new GatewayErrorException($result['errmsg'], $result['result'], $result);
62 | }
63 |
64 | return $result;
65 | }
66 |
67 | /**
68 | * @param array $params
69 | *
70 | * @return string
71 | */
72 | protected function getEndpointUrl($params)
73 | {
74 | return self::ENDPOINT_URL.'?'.http_build_query($params);
75 | }
76 |
77 | /**
78 | * Generate Sign.
79 | *
80 | * @param array $params
81 | * @param string $random
82 | *
83 | * @return string
84 | */
85 | protected function generateSign($params, $random)
86 | {
87 | return hash('sha256', sprintf(
88 | 'secretkey=%s&random=%d&time=%d&mobile=%s',
89 | $this->config->get('secretkey'),
90 | $random,
91 | $params['time'],
92 | $params['tel']['mobile']
93 | ));
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/src/Gateways/NowcnGateway.php:
--------------------------------------------------------------------------------
1 | get('key')) {
22 | throw new GatewayErrorException('key not found', -2, []);
23 | }
24 | $params = [
25 | 'mobile' => $to->getNumber(),
26 | 'content' => $message->getContent($this),
27 | 'userId' => $config->get('key'),
28 | 'password' => $config->get('secret'),
29 | 'apiType' => $config->get('api_type'),
30 | ];
31 | $result = $this->get(self::ENDPOINT_URL, $params);
32 | $result = is_string($result) ? json_decode($result, true) : $result;
33 | if (self::SUCCESS_CODE != $result['code']) {
34 | throw new GatewayErrorException($result['msg'], $result['code'], $result);
35 | }
36 |
37 | return $result;
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/Gateways/QcloudGateway.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * This source file is subject to the MIT license that is bundled
9 | * with this source code in the file LICENSE.
10 | */
11 |
12 | namespace Overtrue\EasySms\Gateways;
13 |
14 | use GuzzleHttp\Psr7\Uri;
15 | use Overtrue\EasySms\Contracts\MessageInterface;
16 | use Overtrue\EasySms\Contracts\PhoneNumberInterface;
17 | use Overtrue\EasySms\Exceptions\GatewayErrorException;
18 | use Overtrue\EasySms\Support\Config;
19 | use Overtrue\EasySms\Traits\HasHttpRequest;
20 |
21 | /**
22 | * Class QcloudGateway.
23 | *
24 | * @see https://cloud.tencent.com/document/api/382/55981
25 | */
26 | class QcloudGateway extends Gateway
27 | {
28 | use HasHttpRequest;
29 |
30 | public const ENDPOINT_URL = 'https://sms.tencentcloudapi.com';
31 |
32 | public const ENDPOINT_SERVICE = 'sms';
33 |
34 | public const ENDPOINT_METHOD = 'SendSms';
35 |
36 | public const ENDPOINT_VERSION = '2021-01-11';
37 |
38 | public const ENDPOINT_REGION = 'ap-guangzhou';
39 |
40 | public const ENDPOINT_FORMAT = 'json';
41 |
42 | /**
43 | * @return array
44 | *
45 | * @throws GatewayErrorException ;
46 | */
47 | public function send(PhoneNumberInterface $to, MessageInterface $message, Config $config)
48 | {
49 | $data = $message->getData($this);
50 |
51 | $signName = !empty($data['sign_name']) ? $data['sign_name'] : $config->get('sign_name', '');
52 |
53 | unset($data['sign_name']);
54 |
55 | $phone = !\is_null($to->getIDDCode()) ? strval($to->getUniversalNumber()) : $to->getNumber();
56 | $params = [
57 | 'PhoneNumberSet' => [
58 | $phone,
59 | ],
60 | 'SmsSdkAppId' => $this->config->get('sdk_app_id'),
61 | 'SignName' => $signName,
62 | 'TemplateId' => (string) $message->getTemplate($this),
63 | 'TemplateParamSet' => array_map('strval', array_values($data)),
64 | ];
65 |
66 | $time = time();
67 |
68 | $endpoint = $this->config->get('endpoint', self::ENDPOINT_URL);
69 |
70 | $result = $this->request('post', $endpoint, [
71 | 'headers' => [
72 | 'Authorization' => $this->generateSign($params, $time),
73 | 'Host' => (new Uri($endpoint))->getHost(),
74 | 'Content-Type' => 'application/json; charset=utf-8',
75 | 'X-TC-Action' => self::ENDPOINT_METHOD,
76 | 'X-TC-Region' => $this->config->get('region', self::ENDPOINT_REGION),
77 | 'X-TC-Timestamp' => $time,
78 | 'X-TC-Version' => self::ENDPOINT_VERSION,
79 | ],
80 | 'json' => $params,
81 | ]);
82 |
83 | if (!empty($result['Response']['Error']['Code'])) {
84 | throw new GatewayErrorException($result['Response']['Error']['Message'], 400, $result);
85 | }
86 |
87 | if (!empty($result['Response']['SendStatusSet'])) {
88 | foreach ($result['Response']['SendStatusSet'] as $group) {
89 | if ('Ok' != $group['Code']) {
90 | throw new GatewayErrorException($group['Message'], 400, $result);
91 | }
92 | }
93 | }
94 |
95 | return $result;
96 | }
97 |
98 | /**
99 | * Generate Sign.
100 | *
101 | * @param array $params
102 | *
103 | * @return string
104 | */
105 | protected function generateSign($params, $timestamp)
106 | {
107 | $date = gmdate('Y-m-d', $timestamp);
108 | $secretKey = $this->config->get('secret_key');
109 | $secretId = $this->config->get('secret_id');
110 | $endpoint = $this->config->get('endpoint', self::ENDPOINT_URL);
111 | $host = (new Uri($endpoint))->getHost();
112 |
113 | $canonicalRequest = 'POST'."\n".
114 | '/'."\n".
115 | ''."\n".
116 | 'content-type:application/json; charset=utf-8'."\n".
117 | 'host:'.$host."\n\n".
118 | 'content-type;host'."\n".
119 | hash('SHA256', json_encode($params));
120 |
121 | $stringToSign =
122 | 'TC3-HMAC-SHA256'."\n".
123 | $timestamp."\n".
124 | $date.'/'.self::ENDPOINT_SERVICE.'/tc3_request'."\n".
125 | hash('SHA256', $canonicalRequest);
126 |
127 | $secretDate = hash_hmac('SHA256', $date, 'TC3'.$secretKey, true);
128 | $secretService = hash_hmac('SHA256', self::ENDPOINT_SERVICE, $secretDate, true);
129 | $secretSigning = hash_hmac('SHA256', 'tc3_request', $secretService, true);
130 | $signature = hash_hmac('SHA256', $stringToSign, $secretSigning);
131 |
132 | return 'TC3-HMAC-SHA256'
133 | .' Credential='.$secretId.'/'.$date.'/'.self::ENDPOINT_SERVICE.'/tc3_request'
134 | .', SignedHeaders=content-type;host, Signature='.$signature;
135 | }
136 | }
137 |
--------------------------------------------------------------------------------
/src/Gateways/QiniuGateway.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * This source file is subject to the MIT license that is bundled
9 | * with this source code in the file LICENSE.
10 | */
11 |
12 | namespace Overtrue\EasySms\Gateways;
13 |
14 | use Overtrue\EasySms\Contracts\MessageInterface;
15 | use Overtrue\EasySms\Contracts\PhoneNumberInterface;
16 | use Overtrue\EasySms\Exceptions\GatewayErrorException;
17 | use Overtrue\EasySms\Support\Config;
18 | use Overtrue\EasySms\Traits\HasHttpRequest;
19 |
20 | /**
21 | * Class QiniuGateway.
22 | *
23 | * @see https://developer.qiniu.com/sms/api/5897/sms-api-send-message
24 | */
25 | class QiniuGateway extends Gateway
26 | {
27 | use HasHttpRequest;
28 |
29 | public const ENDPOINT_TEMPLATE = 'https://%s.qiniuapi.com/%s/%s';
30 |
31 | public const ENDPOINT_VERSION = 'v1';
32 |
33 | /**
34 | * @return array
35 | *
36 | * @throws GatewayErrorException ;
37 | */
38 | public function send(PhoneNumberInterface $to, MessageInterface $message, Config $config)
39 | {
40 | $endpoint = $this->buildEndpoint('sms', 'message/single');
41 |
42 | $data = $message->getData($this);
43 |
44 | $params = [
45 | 'template_id' => $message->getTemplate($this),
46 | 'mobile' => $to->getNumber(),
47 | ];
48 |
49 | if (!empty($data)) {
50 | $params['parameters'] = $data;
51 | }
52 |
53 | $headers = [
54 | 'Content-Type' => 'application/json',
55 | ];
56 |
57 | $headers['Authorization'] = $this->generateSign($endpoint, 'POST', json_encode($params), $headers['Content-Type'], $config);
58 |
59 | $result = $this->postJson($endpoint, $params, $headers);
60 |
61 | if (isset($result['error'])) {
62 | throw new GatewayErrorException($result['message'], $result['error'], $result);
63 | }
64 |
65 | return $result;
66 | }
67 |
68 | /**
69 | * Build endpoint url.
70 | *
71 | * @param string $type
72 | * @param string $function
73 | *
74 | * @return string
75 | */
76 | protected function buildEndpoint($type, $function)
77 | {
78 | return sprintf(self::ENDPOINT_TEMPLATE, $type, self::ENDPOINT_VERSION, $function);
79 | }
80 |
81 | /**
82 | * Build endpoint url.
83 | *
84 | * @param string $url
85 | * @param string $method
86 | * @param string $body
87 | * @param string $contentType
88 | *
89 | * @return string
90 | */
91 | protected function generateSign($url, $method, $body, $contentType, Config $config)
92 | {
93 | $urlItems = parse_url($url);
94 | $host = $urlItems['host'];
95 | if (isset($urlItems['port'])) {
96 | $port = $urlItems['port'];
97 | } else {
98 | $port = '';
99 | }
100 | $path = $urlItems['path'];
101 | if (isset($urlItems['query'])) {
102 | $query = $urlItems['query'];
103 | } else {
104 | $query = '';
105 | }
106 | // write request uri
107 | $toSignStr = $method.' '.$path;
108 | if (!empty($query)) {
109 | $toSignStr .= '?'.$query;
110 | }
111 | // write host and port
112 | $toSignStr .= "\nHost: ".$host;
113 | if (!empty($port)) {
114 | $toSignStr .= ':'.$port;
115 | }
116 | // write content type
117 | if (!empty($contentType)) {
118 | $toSignStr .= "\nContent-Type: ".$contentType;
119 | }
120 | $toSignStr .= "\n\n";
121 | // write body
122 | if (!empty($body)) {
123 | $toSignStr .= $body;
124 | }
125 |
126 | $hmac = hash_hmac('sha1', $toSignStr, $config->get('secret_key'), true);
127 |
128 | return 'Qiniu '.$config->get('access_key').':'.$this->base64UrlSafeEncode($hmac);
129 | }
130 |
131 | /**
132 | * @param string $data
133 | *
134 | * @return string
135 | */
136 | protected function base64UrlSafeEncode($data)
137 | {
138 | $find = ['+', '/'];
139 | $replace = ['-', '_'];
140 |
141 | return str_replace($find, $replace, base64_encode($data));
142 | }
143 | }
144 |
--------------------------------------------------------------------------------
/src/Gateways/RongcloudGateway.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * This source file is subject to the MIT license that is bundled
9 | * with this source code in the file LICENSE.
10 | */
11 |
12 | namespace Overtrue\EasySms\Gateways;
13 |
14 | use GuzzleHttp\Exception\ClientException;
15 | use Overtrue\EasySms\Contracts\MessageInterface;
16 | use Overtrue\EasySms\Contracts\PhoneNumberInterface;
17 | use Overtrue\EasySms\Exceptions\GatewayErrorException;
18 | use Overtrue\EasySms\Support\Config;
19 | use Overtrue\EasySms\Traits\HasHttpRequest;
20 |
21 | /**
22 | * Class RongcloudGateway.
23 | *
24 | * @author Darren Gao
25 | *
26 | * @see http://www.rongcloud.cn/docs/sms_service.html#send_sms_code
27 | */
28 | class RongcloudGateway extends Gateway
29 | {
30 | use HasHttpRequest;
31 |
32 | public const ENDPOINT_TEMPLATE = 'http://api.sms.ronghub.com/%s.%s';
33 |
34 | public const ENDPOINT_ACTION = 'sendCode';
35 |
36 | public const ENDPOINT_FORMAT = 'json';
37 |
38 | public const ENDPOINT_REGION = '86'; // 中国区,目前只支持此国别
39 |
40 | public const SUCCESS_CODE = 200;
41 |
42 | /**
43 | * @return array
44 | *
45 | * @throws GatewayErrorException ;
46 | */
47 | public function send(PhoneNumberInterface $to, MessageInterface $message, Config $config)
48 | {
49 | $data = $message->getData();
50 | $action = array_key_exists('action', $data) ? $data['action'] : self::ENDPOINT_ACTION;
51 | $endpoint = $this->buildEndpoint($action);
52 |
53 | $headers = [
54 | 'Nonce' => uniqid(),
55 | 'App-Key' => $config->get('app_key'),
56 | 'Timestamp' => time(),
57 | ];
58 | $headers['Signature'] = $this->generateSign($headers, $config);
59 |
60 | switch ($action) {
61 | case 'sendCode':
62 | $params = [
63 | 'mobile' => $to->getNumber(),
64 | 'region' => self::ENDPOINT_REGION,
65 | 'templateId' => $message->getTemplate($this),
66 | ];
67 |
68 | break;
69 | case 'verifyCode':
70 | if (!array_key_exists('code', $data)
71 | or !array_key_exists('sessionId', $data)) {
72 | throw new GatewayErrorException('"code" or "sessionId" is not set', 0);
73 | }
74 | $params = [
75 | 'code' => $data['code'],
76 | 'sessionId' => $data['sessionId'],
77 | ];
78 |
79 | break;
80 | case 'sendNotify':
81 | $params = [
82 | 'mobile' => $to->getNumber(),
83 | 'region' => self::ENDPOINT_REGION,
84 | 'templateId' => $message->getTemplate($this),
85 | ];
86 | $params = array_merge($params, $data);
87 |
88 | break;
89 | default:
90 | throw new GatewayErrorException(sprintf('action: %s not supported', $action));
91 | }
92 |
93 | try {
94 | $result = $this->post($endpoint, $params, $headers);
95 |
96 | if (self::SUCCESS_CODE !== $result['code']) {
97 | throw new GatewayErrorException($result['errorMessage'], $result['code'], $result);
98 | }
99 | } catch (ClientException $e) {
100 | throw new GatewayErrorException($e->getMessage(), $e->getCode());
101 | }
102 |
103 | return $result;
104 | }
105 |
106 | /**
107 | * Generate Sign.
108 | *
109 | * @param array $params
110 | *
111 | * @return string
112 | */
113 | protected function generateSign($params, Config $config)
114 | {
115 | return sha1(sprintf('%s%s%s', $config->get('app_secret'), $params['Nonce'], $params['Timestamp']));
116 | }
117 |
118 | /**
119 | * Build endpoint url.
120 | *
121 | * @param string $action
122 | *
123 | * @return string
124 | */
125 | protected function buildEndpoint($action)
126 | {
127 | return sprintf(self::ENDPOINT_TEMPLATE, $action, self::ENDPOINT_FORMAT);
128 | }
129 | }
130 |
--------------------------------------------------------------------------------
/src/Gateways/RongheyunGateway.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * This source file is subject to the MIT license that is bundled
9 | * with this source code in the file LICENSE.
10 | */
11 |
12 | namespace Overtrue\EasySms\Gateways;
13 |
14 | use Overtrue\EasySms\Contracts\MessageInterface;
15 | use Overtrue\EasySms\Contracts\PhoneNumberInterface;
16 | use Overtrue\EasySms\Exceptions\GatewayErrorException;
17 | use Overtrue\EasySms\Support\Config;
18 | use Overtrue\EasySms\Traits\HasHttpRequest;
19 |
20 | /**
21 | * Class RongheyunGateway.
22 | *
23 | * @see https://doc.zthysms.com/web/#/1?page_id=13
24 | */
25 | class RongheyunGateway extends Gateway
26 | {
27 | use HasHttpRequest;
28 |
29 | public const ENDPOINT_URL = 'https://api.mix2.zthysms.com/v2/sendSmsTp';
30 |
31 | /**
32 | * @return array
33 | *
34 | * @throws GatewayErrorException ;
35 | */
36 | public function send(PhoneNumberInterface $to, MessageInterface $message, Config $config)
37 | {
38 | $tKey = time();
39 | $password = md5(md5($config->get('password')).$tKey);
40 | $params = [
41 | 'username' => $config->get('username', ''),
42 | 'password' => $password,
43 | 'tKey' => $tKey,
44 | 'signature' => $config->get('signature', ''),
45 | 'tpId' => $message->getTemplate($this),
46 | 'ext' => '',
47 | 'extend' => '',
48 | 'records' => [
49 | 'mobile' => $to->getNumber(),
50 | 'tpContent' => $message->getData($this),
51 | ],
52 | ];
53 |
54 | $result = $this->postJson(
55 | self::ENDPOINT_URL,
56 | $params,
57 | ['Content-Type' => 'application/json; charset="UTF-8"']
58 | );
59 | if (200 != $result['code']) {
60 | throw new GatewayErrorException($result['msg'], $result['code'], $result);
61 | }
62 |
63 | return $result;
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/src/Gateways/SendcloudGateway.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * This source file is subject to the MIT license that is bundled
9 | * with this source code in the file LICENSE.
10 | */
11 |
12 | namespace Overtrue\EasySms\Gateways;
13 |
14 | use Overtrue\EasySms\Contracts\MessageInterface;
15 | use Overtrue\EasySms\Contracts\PhoneNumberInterface;
16 | use Overtrue\EasySms\Exceptions\GatewayErrorException;
17 | use Overtrue\EasySms\Support\Config;
18 | use Overtrue\EasySms\Traits\HasHttpRequest;
19 |
20 | /**
21 | * Class SendcloudGateway.
22 | *
23 | * @see http://sendcloud.sohu.com/doc/sms/
24 | */
25 | class SendcloudGateway extends Gateway
26 | {
27 | use HasHttpRequest;
28 |
29 | public const ENDPOINT_TEMPLATE = 'http://www.sendcloud.net/smsapi/%s';
30 |
31 | /**
32 | * Send a short message.
33 | *
34 | * @return array
35 | *
36 | * @throws GatewayErrorException
37 | */
38 | public function send(PhoneNumberInterface $to, MessageInterface $message, Config $config)
39 | {
40 | $params = [
41 | 'smsUser' => $config->get('sms_user'),
42 | 'templateId' => $message->getTemplate($this),
43 | 'msgType' => $to->getIDDCode() ? 2 : 0,
44 | 'phone' => $to->getZeroPrefixedNumber(),
45 | 'vars' => $this->formatTemplateVars($message->getData($this)),
46 | ];
47 |
48 | if ($config->get('timestamp', false)) {
49 | $params['timestamp'] = time() * 1000;
50 | }
51 |
52 | $params['signature'] = $this->sign($params, $config->get('sms_key'));
53 |
54 | $result = $this->post(sprintf(self::ENDPOINT_TEMPLATE, 'send'), $params);
55 |
56 | if (!$result['result']) {
57 | throw new GatewayErrorException($result['message'], $result['statusCode'], $result);
58 | }
59 |
60 | return $result;
61 | }
62 |
63 | /**
64 | * @return string
65 | */
66 | protected function formatTemplateVars(array $vars)
67 | {
68 | $formatted = [];
69 |
70 | foreach ($vars as $key => $value) {
71 | $formatted[sprintf('%%%s%%', trim($key, '%'))] = $value;
72 | }
73 |
74 | return json_encode($formatted, JSON_FORCE_OBJECT);
75 | }
76 |
77 | /**
78 | * @param array $params
79 | * @param string $key
80 | *
81 | * @return string
82 | */
83 | protected function sign($params, $key)
84 | {
85 | ksort($params);
86 |
87 | return md5(sprintf('%s&%s&%s', $key, urldecode(http_build_query($params)), $key));
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/src/Gateways/SmsbaoGateway.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * This source file is subject to the MIT license that is bundled
9 | * with this source code in the file LICENSE.
10 | */
11 |
12 | namespace Overtrue\EasySms\Gateways;
13 |
14 | use Overtrue\EasySms\Contracts\MessageInterface;
15 | use Overtrue\EasySms\Contracts\PhoneNumberInterface;
16 | use Overtrue\EasySms\Exceptions\GatewayErrorException;
17 | use Overtrue\EasySms\Support\Config;
18 | use Overtrue\EasySms\Traits\HasHttpRequest;
19 |
20 | /**
21 | * Class SmsbaoGateway.
22 | *
23 | * @author iwindy <203962638@qq.com>
24 | *
25 | * @see http://www.smsbao.com/openapi/
26 | */
27 | class SmsbaoGateway extends Gateway
28 | {
29 | use HasHttpRequest;
30 |
31 | public const ENDPOINT_URL = 'http://api.smsbao.com/%s';
32 |
33 | public const SUCCESS_CODE = '0';
34 |
35 | protected $errorStatuses = [
36 | '0' => '短信发送成功',
37 | '-1' => '参数不全',
38 | '-2' => '服务器空间不支持,请确认支持curl或者fsocket,联系您的空间商解决或者更换空间!',
39 | '30' => '密码错误',
40 | '40' => '账号不存在',
41 | '41' => '余额不足',
42 | '42' => '帐户已过期',
43 | '43' => 'IP地址限制',
44 | '50' => '内容含有敏感词',
45 | ];
46 |
47 | public function send(PhoneNumberInterface $to, MessageInterface $message, Config $config)
48 | {
49 | $data = $message->getContent($this);
50 |
51 | if (is_null($to->getIDDCode()) || '86' == $to->getIDDCode()) {
52 | $number = $to->getNumber();
53 | $action = 'sms';
54 | } else {
55 | $number = $to->getUniversalNumber();
56 | $action = 'wsms';
57 | }
58 |
59 | $params = [
60 | 'u' => $config->get('user'),
61 | 'p' => md5($config->get('password')),
62 | 'm' => $number,
63 | 'c' => $data,
64 | ];
65 |
66 | $result = $this->get($this->buildEndpoint($action), $params);
67 |
68 | if (self::SUCCESS_CODE !== $result) {
69 | throw new GatewayErrorException($this->errorStatuses[$result], $result);
70 | }
71 |
72 | return $result;
73 | }
74 |
75 | protected function buildEndpoint($type)
76 | {
77 | return sprintf(self::ENDPOINT_URL, $type);
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/src/Gateways/SubmailGateway.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * This source file is subject to the MIT license that is bundled
9 | * with this source code in the file LICENSE.
10 | */
11 |
12 | namespace Overtrue\EasySms\Gateways;
13 |
14 | use Overtrue\EasySms\Contracts\MessageInterface;
15 | use Overtrue\EasySms\Contracts\PhoneNumberInterface;
16 | use Overtrue\EasySms\Exceptions\GatewayErrorException;
17 | use Overtrue\EasySms\Support\Config;
18 | use Overtrue\EasySms\Traits\HasHttpRequest;
19 |
20 | /**
21 | * Class SubmailGateway.
22 | *
23 | * @see https://www.mysubmail.com/chs/documents/developer/index
24 | */
25 | class SubmailGateway extends Gateway
26 | {
27 | use HasHttpRequest;
28 |
29 | public const ENDPOINT_TEMPLATE = 'https://api.mysubmail.com/%s.%s';
30 |
31 | public const ENDPOINT_FORMAT = 'json';
32 |
33 | /**
34 | * @return array
35 | *
36 | * @throws GatewayErrorException ;
37 | */
38 | public function send(PhoneNumberInterface $to, MessageInterface $message, Config $config)
39 | {
40 | $isContent = (bool) $message->getContent($this);
41 | if ($isContent) {
42 | $endpoint = $this->buildEndpoint($this->inChineseMainland($to) ? 'sms/send' : 'internationalsms/send');
43 | $params = [
44 | 'appid' => $config->get('app_id'),
45 | 'signature' => $config->get('app_key'),
46 | 'content' => $message->getContent($this),
47 | 'to' => $to->getUniversalNumber(),
48 | ];
49 | } else {
50 | $endpoint = $this->buildEndpoint($this->inChineseMainland($to) ? 'message/xsend' : 'internationalsms/xsend');
51 | $data = $message->getData($this);
52 | $template_code = $message->getTemplate($this);
53 | $params = [
54 | 'appid' => $config->get('app_id'),
55 | 'signature' => $config->get('app_key'),
56 | 'project' => !empty($template_code) ? $template_code : (!empty($data['project']) ? $data['project'] : $config->get('project')),
57 | 'to' => $to->getUniversalNumber(),
58 | 'vars' => json_encode($data, JSON_FORCE_OBJECT),
59 | ];
60 | }
61 |
62 | $result = $this->post($endpoint, $params);
63 |
64 | if ('success' != $result['status']) {
65 | throw new GatewayErrorException($result['msg'], $result['code'], $result);
66 | }
67 |
68 | return $result;
69 | }
70 |
71 | /**
72 | * Build endpoint url.
73 | *
74 | * @param string $function
75 | *
76 | * @return string
77 | */
78 | protected function buildEndpoint($function)
79 | {
80 | return sprintf(self::ENDPOINT_TEMPLATE, $function, self::ENDPOINT_FORMAT);
81 | }
82 |
83 | /**
84 | * Check if the phone number belongs to chinese mainland.
85 | *
86 | * @param PhoneNumberInterface $to
87 | *
88 | * @return bool
89 | */
90 | protected function inChineseMainland($to)
91 | {
92 | $code = $to->getIDDCode();
93 |
94 | return empty($code) || 86 === $code;
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/src/Gateways/TianyiwuxianGateway.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * This source file is subject to the MIT license that is bundled
9 | * with this source code in the file LICENSE.
10 | */
11 |
12 | namespace Overtrue\EasySms\Gateways;
13 |
14 | use Overtrue\EasySms\Contracts\MessageInterface;
15 | use Overtrue\EasySms\Contracts\PhoneNumberInterface;
16 | use Overtrue\EasySms\Exceptions\GatewayErrorException;
17 | use Overtrue\EasySms\Support\Config;
18 | use Overtrue\EasySms\Traits\HasHttpRequest;
19 |
20 | /**
21 | * Class TianyiwuxianGateway.
22 | *
23 | * @author Darren Gao
24 | */
25 | class TianyiwuxianGateway extends Gateway
26 | {
27 | use HasHttpRequest;
28 |
29 | public const ENDPOINT_TEMPLATE = 'http://jk.106api.cn/sms%s.aspx';
30 |
31 | public const ENDPOINT_ENCODE = 'UTF8';
32 |
33 | public const ENDPOINT_TYPE = 'send';
34 |
35 | public const ENDPOINT_FORMAT = 'json';
36 |
37 | public const SUCCESS_STATUS = 'success';
38 |
39 | public const SUCCESS_CODE = '0';
40 |
41 | /**
42 | * @return array
43 | *
44 | * @throws GatewayErrorException ;
45 | */
46 | public function send(PhoneNumberInterface $to, MessageInterface $message, Config $config)
47 | {
48 | $endpoint = $this->buildEndpoint();
49 |
50 | $params = [
51 | 'gwid' => $config->get('gwid'),
52 | 'type' => self::ENDPOINT_TYPE,
53 | 'rece' => self::ENDPOINT_FORMAT,
54 | 'mobile' => $to->getNumber(),
55 | 'message' => $message->getContent($this),
56 | 'username' => $config->get('username'),
57 | 'password' => strtoupper(md5($config->get('password'))),
58 | ];
59 |
60 | $result = $this->post($endpoint, $params);
61 |
62 | $result = json_decode($result, true);
63 |
64 | if (self::SUCCESS_STATUS !== $result['returnstatus'] || self::SUCCESS_CODE !== $result['code']) {
65 | throw new GatewayErrorException($result['remark'], $result['code']);
66 | }
67 |
68 | return $result;
69 | }
70 |
71 | /**
72 | * Build endpoint url.
73 | *
74 | * @return string
75 | */
76 | protected function buildEndpoint()
77 | {
78 | return sprintf(self::ENDPOINT_TEMPLATE, self::ENDPOINT_ENCODE);
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/src/Gateways/TiniyoGateway.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * This source file is subject to the MIT license that is bundled
9 | * with this source code in the file LICENSE.
10 | */
11 |
12 | namespace Overtrue\EasySms\Gateways;
13 |
14 | use Overtrue\EasySms\Contracts\MessageInterface;
15 | use Overtrue\EasySms\Contracts\PhoneNumberInterface;
16 | use Overtrue\EasySms\Exceptions\GatewayErrorException;
17 | use Overtrue\EasySms\Support\Config;
18 | use Overtrue\EasySms\Traits\HasHttpRequest;
19 |
20 | /**
21 | * Class Tiniyo Gateway.
22 | *
23 | * @see https://tiniyo.com/sms.html
24 | */
25 | class TiniyoGateway extends Gateway
26 | {
27 | use HasHttpRequest;
28 |
29 | public const ENDPOINT_URL = 'https://api.tiniyo.com/v1/Account/%s/Message';
30 |
31 | public const SUCCESS_CODE = '000000';
32 |
33 | public function getName()
34 | {
35 | return 'tiniyo';
36 | }
37 |
38 | /**
39 | * @return array
40 | *
41 | * @throws GatewayErrorException
42 | */
43 | public function send(PhoneNumberInterface $to, MessageInterface $message, Config $config)
44 | {
45 | $accountSid = $config->get('account_sid');
46 | $endpoint = $this->buildEndPoint($accountSid);
47 |
48 | $params = [
49 | 'dst' => $to->getUniversalNumber(),
50 | 'src' => $config->get('from'),
51 | 'text' => $message->getContent($this),
52 | ];
53 |
54 | $result = $this->request('post', $endpoint, [
55 | 'json' => $params,
56 | 'headers' => [
57 | 'Accept' => 'application/json',
58 | 'Content-Type' => 'application/json;charset=utf-8',
59 | 'Authorization' => base64_encode($config->get('account_sid').':'.$config->get('token')),
60 | ],
61 | ]);
62 |
63 | if (self::SUCCESS_CODE != $result['statusCode']) {
64 | throw new GatewayErrorException($result['statusCode'], $result['statusCode'], $result);
65 | }
66 |
67 | return $result;
68 | }
69 |
70 | /**
71 | * build endpoint url.
72 | *
73 | * @param string $accountSid
74 | *
75 | * @return string
76 | */
77 | protected function buildEndPoint($accountSid)
78 | {
79 | return sprintf(self::ENDPOINT_URL, $accountSid);
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/src/Gateways/TinreeGateway.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * This source file is subject to the MIT license that is bundled
9 | * with this source code in the file LICENSE.
10 | */
11 |
12 | namespace Overtrue\EasySms\Gateways;
13 |
14 | use Overtrue\EasySms\Contracts\MessageInterface;
15 | use Overtrue\EasySms\Contracts\PhoneNumberInterface;
16 | use Overtrue\EasySms\Exceptions\GatewayErrorException;
17 | use Overtrue\EasySms\Support\Config;
18 | use Overtrue\EasySms\Traits\HasHttpRequest;
19 |
20 | /**
21 | * Class TinreeGateway.
22 | *
23 | * @see http://cms.tinree.com
24 | */
25 | class TinreeGateway extends Gateway
26 | {
27 | use HasHttpRequest;
28 |
29 | public const ENDPOINT_URL = 'http://api.tinree.com/api/v2/single_send';
30 |
31 | /**
32 | * @return array
33 | *
34 | * @throws GatewayErrorException
35 | */
36 | public function send(PhoneNumberInterface $to, MessageInterface $message, Config $config)
37 | {
38 | $params = [
39 | 'accesskey' => $config->get('accesskey'),
40 | 'secret' => $config->get('secret'),
41 | 'sign' => $config->get('sign'),
42 | 'templateId' => $message->getTemplate($this),
43 | 'mobile' => $to->getNumber(),
44 | 'content' => $this->buildContent($message),
45 | ];
46 |
47 | $result = $this->post(self::ENDPOINT_URL, $params);
48 |
49 | if (0 != $result['code']) {
50 | throw new GatewayErrorException($result['msg'], $result['code'], $result);
51 | }
52 |
53 | return $result;
54 | }
55 |
56 | /**
57 | * 构建发送内容
58 | * 用 data 数据合成内容,或者直接使用 data 的值
59 | *
60 | * @return string
61 | */
62 | protected function buildContent(MessageInterface $message)
63 | {
64 | $data = $message->getData($this);
65 |
66 | if (is_array($data)) {
67 | return implode('##', $data);
68 | }
69 |
70 | return $data;
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/src/Gateways/TwilioGateway.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * This source file is subject to the MIT license that is bundled
9 | * with this source code in the file LICENSE.
10 | */
11 |
12 | namespace Overtrue\EasySms\Gateways;
13 |
14 | use GuzzleHttp\Exception\ClientException;
15 | use Overtrue\EasySms\Contracts\MessageInterface;
16 | use Overtrue\EasySms\Contracts\PhoneNumberInterface;
17 | use Overtrue\EasySms\Exceptions\GatewayErrorException;
18 | use Overtrue\EasySms\Support\Config;
19 | use Overtrue\EasySms\Traits\HasHttpRequest;
20 |
21 | /**
22 | * Class TwilioGateway.
23 | *
24 | * @see https://www.twilio.com/docs/api/messaging/send-messages
25 | */
26 | class TwilioGateway extends Gateway
27 | {
28 | use HasHttpRequest;
29 |
30 | public const ENDPOINT_URL = 'https://api.twilio.com/2010-04-01/Accounts/%s/Messages.json';
31 |
32 | protected $errorStatuses = [
33 | 'failed',
34 | 'undelivered',
35 | ];
36 |
37 | public function getName()
38 | {
39 | return 'twilio';
40 | }
41 |
42 | /**
43 | * @return array
44 | *
45 | * @throws GatewayErrorException
46 | */
47 | public function send(PhoneNumberInterface $to, MessageInterface $message, Config $config)
48 | {
49 | $accountSid = $config->get('account_sid');
50 | $endpoint = $this->buildEndPoint($accountSid);
51 |
52 | $params = [
53 | 'To' => $to->getUniversalNumber(),
54 | 'From' => $config->get('from'),
55 | 'Body' => $message->getContent($this),
56 | ];
57 |
58 | try {
59 | $result = $this->request('post', $endpoint, [
60 | 'auth' => [
61 | $accountSid,
62 | $config->get('token'),
63 | ],
64 | 'form_params' => $params,
65 | ]);
66 | if (in_array($result['status'], $this->errorStatuses) || !is_null($result['error_code'])) {
67 | throw new GatewayErrorException($result['message'], $result['error_code'], $result);
68 | }
69 | } catch (ClientException $e) {
70 | throw new GatewayErrorException($e->getMessage(), $e->getCode());
71 | }
72 |
73 | return $result;
74 | }
75 |
76 | /**
77 | * build endpoint url.
78 | *
79 | * @param string $accountSid
80 | *
81 | * @return string
82 | */
83 | protected function buildEndPoint($accountSid)
84 | {
85 | return sprintf(self::ENDPOINT_URL, $accountSid);
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/src/Gateways/UcloudGateway.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * This source file is subject to the MIT license that is bundled
9 | * with this source code in the file LICENSE.
10 | */
11 |
12 | namespace Overtrue\EasySms\Gateways;
13 |
14 | use Overtrue\EasySms\Contracts\MessageInterface;
15 | use Overtrue\EasySms\Contracts\PhoneNumberInterface;
16 | use Overtrue\EasySms\Exceptions\GatewayErrorException;
17 | use Overtrue\EasySms\Support\Config;
18 | use Overtrue\EasySms\Traits\HasHttpRequest;
19 |
20 | /**
21 | * Class UcloudGateway.
22 | */
23 | class UcloudGateway extends Gateway
24 | {
25 | use HasHttpRequest;
26 |
27 | public const ENDPOINT_URL = 'https://api.ucloud.cn';
28 |
29 | public const ENDPOINT_Action = 'SendUSMSMessage';
30 |
31 | public const SUCCESS_CODE = 0;
32 |
33 | /**
34 | * Send Message.
35 | *
36 | * @return array
37 | *
38 | * @throws GatewayErrorException
39 | */
40 | public function send(PhoneNumberInterface $to, MessageInterface $message, Config $config)
41 | {
42 | $params = $this->buildParams($to, $message, $config);
43 |
44 | $result = $this->get(self::ENDPOINT_URL, $params);
45 |
46 | if (self::SUCCESS_CODE != $result['RetCode']) {
47 | throw new GatewayErrorException($result['Message'], $result['RetCode'], $result);
48 | }
49 |
50 | return $result;
51 | }
52 |
53 | /**
54 | * Build Params.
55 | *
56 | * @return array
57 | */
58 | protected function buildParams(PhoneNumberInterface $to, MessageInterface $message, Config $config)
59 | {
60 | $data = $message->getData($this);
61 | $params = [
62 | 'Action' => self::ENDPOINT_Action,
63 | 'SigContent' => !empty($data['sig_content']) ? $data['sig_content'] : $config->get('sig_content', ''),
64 | 'TemplateId' => $message->getTemplate($this),
65 | 'PublicKey' => $config->get('public_key'),
66 | ];
67 | $code = isset($data['code']) ? $data['code'] : '';
68 | if (is_array($code) && !empty($code)) {
69 | foreach ($code as $key => $value) {
70 | $params['TemplateParams.'.$key] = $value;
71 | }
72 | } else {
73 | if (!empty($code) || !is_null($code)) {
74 | $params['TemplateParams.0'] = $code;
75 | }
76 | }
77 |
78 | $mobiles = isset($data['mobiles']) ? $data['mobiles'] : '';
79 | if (!empty($mobiles) && !is_null($mobiles)) {
80 | if (is_array($mobiles)) {
81 | foreach ($mobiles as $key => $value) {
82 | $params['PhoneNumbers.'.$key] = $value;
83 | }
84 | } else {
85 | $params['PhoneNumbers.0'] = $mobiles;
86 | }
87 | } else {
88 | $params['PhoneNumbers.0'] = $to->getNumber();
89 | }
90 |
91 | if (!is_null($config->get('project_id')) && !empty($config->get('project_id'))) {
92 | $params['ProjectId'] = $config->get('project_id');
93 | }
94 |
95 | $signature = $this->getSignature($params, $config->get('private_key'));
96 | $params['Signature'] = $signature;
97 |
98 | return $params;
99 | }
100 |
101 | /**
102 | * Generate Sign.
103 | *
104 | * @param array $params
105 | * @param string $privateKey
106 | *
107 | * @return string
108 | */
109 | protected function getSignature($params, $privateKey)
110 | {
111 | ksort($params);
112 |
113 | $paramsData = '';
114 | foreach ($params as $key => $value) {
115 | $paramsData .= $key;
116 | $paramsData .= $value;
117 | }
118 | $paramsData .= $privateKey;
119 |
120 | return sha1($paramsData);
121 | }
122 | }
123 |
--------------------------------------------------------------------------------
/src/Gateways/Ue35Gateway.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * This source file is subject to the MIT license that is bundled
9 | * with this source code in the file LICENSE.
10 | */
11 |
12 | namespace Overtrue\EasySms\Gateways;
13 |
14 | use Overtrue\EasySms\Contracts\MessageInterface;
15 | use Overtrue\EasySms\Contracts\PhoneNumberInterface;
16 | use Overtrue\EasySms\Exceptions\GatewayErrorException;
17 | use Overtrue\EasySms\Support\Config;
18 | use Overtrue\EasySms\Traits\HasHttpRequest;
19 |
20 | /**
21 | * Class Ue35Gateway.
22 | *
23 | * @see https://shimo.im/docs/380b42d8cba24521
24 | */
25 | class Ue35Gateway extends Gateway
26 | {
27 | use HasHttpRequest;
28 |
29 | public const ENDPOINT_HOST = 'sms.ue35.net:8443';
30 |
31 | public const ENDPOINT_URI = '/sms/interface/sendmess.htm';
32 |
33 | public const SUCCESS_CODE = 1;
34 |
35 | /**
36 | * Send message.
37 | *
38 | * @return array
39 | *
40 | * @throws GatewayErrorException ;
41 | */
42 | public function send(PhoneNumberInterface $to, MessageInterface $message, Config $config)
43 | {
44 | $params = [
45 | 'username' => $config->get('username'),
46 | 'userpwd' => $config->get('userpwd'),
47 | 'mobiles' => $to->getNumber(),
48 | 'content' => $message->getContent($this),
49 | ];
50 |
51 | $headers = [
52 | 'host' => static::ENDPOINT_HOST,
53 | 'content-type' => 'application/json',
54 | 'user-agent' => 'PHP EasySms Client',
55 | ];
56 |
57 | $result = $this->request('get', self::getEndpointUri().'?'.http_build_query($params), ['headers' => $headers]);
58 | if (is_string($result)) {
59 | $result = json_decode(json_encode(simplexml_load_string($result)), true);
60 | }
61 |
62 | if (self::SUCCESS_CODE != $result['errorcode']) {
63 | throw new GatewayErrorException($result['message'], $result['errorcode'], $result);
64 | }
65 |
66 | return $result;
67 | }
68 |
69 | public static function getEndpointUri()
70 | {
71 | return 'https://'.static::ENDPOINT_HOST.static::ENDPOINT_URI;
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/src/Gateways/VolcengineGateway.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * This source file is subject to the MIT license that is bundled
9 | * with this source code in the file LICENSE.
10 | */
11 |
12 | namespace Overtrue\EasySms\Gateways;
13 |
14 | use GuzzleHttp\Exception\ClientException;
15 | use GuzzleHttp\HandlerStack;
16 | use GuzzleHttp\Psr7;
17 | use GuzzleHttp\Psr7\Query;
18 | use GuzzleHttp\Psr7\Utils;
19 | use Overtrue\EasySms\Contracts\MessageInterface;
20 | use Overtrue\EasySms\Contracts\PhoneNumberInterface;
21 | use Overtrue\EasySms\Exceptions\GatewayErrorException;
22 | use Overtrue\EasySms\Support\Config;
23 | use Overtrue\EasySms\Traits\HasHttpRequest;
24 | use Psr\Http\Message\RequestInterface;
25 |
26 | /**
27 | * Class VolcengineGateway.
28 | *
29 | * @see https://www.volcengine.com/docs/6361/66704
30 | */
31 | class VolcengineGateway extends Gateway
32 | {
33 | use HasHttpRequest;
34 |
35 | public const ENDPOINT_ACTION = 'SendSms';
36 | public const ENDPOINT_VERSION = '2020-01-01';
37 | public const ENDPOINT_CONTENT_TYPE = 'application/json; charset=utf-8';
38 | public const ENDPOINT_ACCEPT = 'application/json';
39 | public const ENDPOINT_USER_AGENT = 'overtrue/easy-sms';
40 | public const ENDPOINT_SERVICE = 'volcSMS';
41 |
42 | public const Algorithm = 'HMAC-SHA256';
43 |
44 | public const ENDPOINT_DEFAULT_REGION_ID = 'cn-north-1';
45 |
46 | public static $endpoints = [
47 | 'cn-north-1' => 'https://sms.volcengineapi.com',
48 | 'ap-singapore-1' => 'https://sms.byteplusapi.com',
49 | ];
50 |
51 | private $regionId = self::ENDPOINT_DEFAULT_REGION_ID;
52 | protected $requestDate;
53 |
54 | public function send(PhoneNumberInterface $to, MessageInterface $message, Config $config)
55 | {
56 | $data = $message->getData($this);
57 | $signName = !empty($data['sign_name']) ? $data['sign_name'] : $config->get('sign_name');
58 | $smsAccount = !empty($data['sms_account']) ? $data['sms_account'] : $config->get('sms_account');
59 | $templateId = $message->getTemplate($this);
60 | $phoneNumbers = !empty($data['phone_numbers']) ? $data['phone_numbers'] : $to->getNumber();
61 | $templateParam = !empty($data['template_param']) ? $data['template_param'] : $message->getData($this);
62 |
63 | $tag = !empty($data['tag']) ? $data['tag'] : '';
64 |
65 | $payload = [
66 | 'SmsAccount' => $smsAccount, // 消息组帐号,火山短信页面右上角,短信应用括号中的字符串
67 | 'Sign' => $signName, // 短信签名
68 | 'TemplateID' => $templateId, // 短信模板ID
69 | 'TemplateParam' => json_encode($templateParam), // 短信模板占位符要替换的值
70 | 'PhoneNumbers' => $phoneNumbers, // 手机号,如果有多个使用英文逗号分割
71 | ];
72 | if ($tag) {
73 | $payload['Tag'] = $tag;
74 | }
75 | $queries = [
76 | 'Action' => self::ENDPOINT_ACTION,
77 | 'Version' => self::ENDPOINT_VERSION,
78 | ];
79 |
80 | try {
81 | $stack = HandlerStack::create();
82 | $stack->push($this->signHandle());
83 | $this->setGuzzleOptions([
84 | 'headers' => [
85 | 'Content-Type' => self::ENDPOINT_CONTENT_TYPE,
86 | 'Accept' => self::ENDPOINT_ACCEPT,
87 | 'User-Agent' => self::ENDPOINT_USER_AGENT,
88 | ],
89 | 'timeout' => $this->getTimeout(),
90 | 'handler' => $stack,
91 | 'base_uri' => $this->getEndpoint(),
92 | ]);
93 |
94 | $response = $this->request('post', $this->getEndpoint().$this->getCanonicalURI(), [
95 | 'query' => $queries,
96 | 'json' => $payload,
97 | ]);
98 | if ($response instanceof Psr7\Response) {
99 | $response = json_decode($response->getBody()->getContents(), true);
100 | }
101 | if (isset($response['ResponseMetadata']['Error'])) { // 请求错误参数,如果请求没有错误,则不存在该参数返回
102 | // 火山引擎错误码格式为:'ZJ'+ 5位数字,比如 ZJ20009,取出数字部分
103 | preg_match('/\d+/', $response['ResponseMetadata']['Error']['Code'], $matches);
104 | throw new GatewayErrorException($response['ResponseMetadata']['Error']['Code'].':'.$response['ResponseMetadata']['Error']['Message'], $matches[0], $response);
105 | }
106 |
107 | return $response;
108 | } catch (ClientException $exception) {
109 | $responseContent = $exception->getResponse()->getBody()->getContents();
110 | $response = json_decode($responseContent, true);
111 | if (isset($response['ResponseMetadata']['Error']) && $error = $response['ResponseMetadata']['Error']) { // 请求错误参数,如果请求没有错误,则不存在该参数返回
112 | // 火山引擎公共错误码Error与业务错误码略有不同,比如:"Error":{"CodeN":100004,"Code":"MissingRequestInfo","Message":"The request is missing timestamp information."}
113 | // 此处错误码直接取 CodeN
114 | throw new GatewayErrorException($error['CodeN'].':'.$error['Message'], $error['CodeN'], $response);
115 | }
116 | throw new GatewayErrorException($responseContent, $exception->getCode(), ['content' => $responseContent]);
117 | }
118 | }
119 |
120 | protected function signHandle()
121 | {
122 | return function (callable $handler) {
123 | return function (RequestInterface $request, array $options) use ($handler) {
124 | $request = $request->withHeader('X-Date', $this->getRequestDate());
125 | list($canonicalHeaders, $signedHeaders) = $this->getCanonicalHeaders($request);
126 |
127 | $queries = Query::parse($request->getUri()->getQuery());
128 | $canonicalRequest = $request->getMethod()."\n"
129 | .$this->getCanonicalURI()."\n"
130 | .$this->getCanonicalQueryString($queries)."\n"
131 | .$canonicalHeaders."\n"
132 | .$signedHeaders."\n"
133 | .$this->getPayloadHash($request);
134 |
135 | $stringToSign = $this->getStringToSign($canonicalRequest);
136 |
137 | $signingKey = $this->getSigningKey();
138 |
139 | $signature = hash_hmac('sha256', $stringToSign, $signingKey);
140 | $parsed = $this->parseRequest($request);
141 |
142 | $parsed['headers']['Authorization'] = self::Algorithm.
143 | ' Credential='.$this->getAccessKeyId().'/'.$this->getCredentialScope().', SignedHeaders='.$signedHeaders.', Signature='.$signature;
144 |
145 | $buildRequest = function () use ($parsed) {
146 | if ($parsed['query']) {
147 | $parsed['uri'] = $parsed['uri']->withQuery(Query::build($parsed['query']));
148 | }
149 |
150 | return new Psr7\Request(
151 | $parsed['method'],
152 | $parsed['uri'],
153 | $parsed['headers'],
154 | $parsed['body'],
155 | $parsed['version']
156 | );
157 | };
158 |
159 | return $handler($buildRequest(), $options);
160 | };
161 | };
162 | }
163 |
164 | private function parseRequest(RequestInterface $request)
165 | {
166 | $uri = $request->getUri();
167 |
168 | return [
169 | 'method' => $request->getMethod(),
170 | 'path' => $uri->getPath(),
171 | 'query' => Query::parse($uri->getQuery()),
172 | 'uri' => $uri,
173 | 'headers' => $request->getHeaders(),
174 | 'body' => $request->getBody(),
175 | 'version' => $request->getProtocolVersion(),
176 | ];
177 | }
178 |
179 | public function getPayloadHash(RequestInterface $request)
180 | {
181 | if ($request->hasHeader('X-Content-Sha256')) {
182 | return $request->getHeaderLine('X-Content-Sha256');
183 | }
184 |
185 | return Utils::hash($request->getBody(), 'sha256');
186 | }
187 |
188 | public function getRegionId()
189 | {
190 | return $this->config->get('region_id', self::ENDPOINT_DEFAULT_REGION_ID);
191 | }
192 |
193 | public function getEndpoint()
194 | {
195 | $regionId = $this->getRegionId();
196 | if (!in_array($regionId, array_keys(self::$endpoints))) {
197 | $regionId = self::ENDPOINT_DEFAULT_REGION_ID;
198 | }
199 |
200 | return static::$endpoints[$regionId];
201 | }
202 |
203 | public function getRequestDate()
204 | {
205 | return $this->requestDate ?: gmdate('Ymd\THis\Z');
206 | }
207 |
208 | /**
209 | * 指代信任状,格式为:YYYYMMDD/region/service/request.
210 | *
211 | * @return string
212 | */
213 | public function getCredentialScope()
214 | {
215 | return date('Ymd', strtotime($this->getRequestDate())).'/'.$this->getRegionId().'/'.self::ENDPOINT_SERVICE.'/request';
216 | }
217 |
218 | /**
219 | * 计算签名密钥
220 | * 在计算签名前,首先从私有访问密钥(Secret Access Key)派生出签名密钥(signing key),而不是直接使用私有访问密钥。具体计算过程如下:
221 | * kSecret = *Your Secret Access Key*
222 | * kDate = HMAC(kSecret, Date)
223 | * kRegion = HMAC(kDate, Region)
224 | * kService = HMAC(kRegion, Service)
225 | * kSigning = HMAC(kService, "request")
226 | * 其中Date精确到日,与RequestDate中YYYYMMDD部分相同。
227 | *
228 | * @return string
229 | */
230 | protected function getSigningKey()
231 | {
232 | $dateKey = hash_hmac('sha256', date('Ymd', strtotime($this->getRequestDate())), $this->getAccessKeySecret(), true);
233 | $regionKey = hash_hmac('sha256', $this->getRegionId(), $dateKey, true);
234 | $serviceKey = hash_hmac('sha256', self::ENDPOINT_SERVICE, $regionKey, true);
235 |
236 | return hash_hmac('sha256', 'request', $serviceKey, true);
237 | }
238 |
239 | /**
240 | * 创建签名字符串
241 | * 签名字符串主要包含请求以及正规化请求的元数据信息,由签名算法、请求日期、信任状和正规化请求哈希值连接组成,伪代码如下:
242 | * StringToSign = Algorithm + '\n' + RequestDate + '\n' + CredentialScope + '\n' + HexEncode(Hash(CanonicalRequest)).
243 | *
244 | * @return string
245 | */
246 | public function getStringToSign($canonicalRequest)
247 | {
248 | return self::Algorithm."\n".$this->getRequestDate()."\n".$this->getCredentialScope()."\n".hash('sha256', $canonicalRequest);
249 | }
250 |
251 | /**
252 | * @return string
253 | */
254 | public function getAccessKeySecret()
255 | {
256 | return $this->config->get('access_key_secret');
257 | }
258 |
259 | /**
260 | * @return string
261 | */
262 | public function getAccessKeyId()
263 | {
264 | return $this->config->get('access_key_id');
265 | }
266 |
267 | /**
268 | * 指代正规化后的Header。
269 | * 其中伪代码如下:
270 | * CanonicalHeaders = CanonicalHeadersEntry0 + CanonicalHeadersEntry1 + ... + CanonicalHeadersEntryN
271 | * 其中CanonicalHeadersEntry = Lowercase(HeaderName) + ':' + Trimall(HeaderValue) + '\n'
272 | * Lowcase代表将Header的名称全部转化成小写,Trimall表示去掉Header的值的前后多余的空格。
273 | * 特别注意:最后需要添加"\n"的换行符,header的顺序是以headerName的小写后ascii排序。
274 | *
275 | * @return array
276 | */
277 | public function getCanonicalHeaders(RequestInterface $request)
278 | {
279 | $headers = $request->getHeaders();
280 | ksort($headers);
281 | $canonicalHeaders = '';
282 | $signedHeaders = [];
283 | foreach ($headers as $key => $val) {
284 | $lowerKey = strtolower($key);
285 | $canonicalHeaders .= $lowerKey.':'.trim($val[0]).PHP_EOL;
286 | $signedHeaders[] = $lowerKey;
287 | }
288 | $signedHeadersString = implode(';', $signedHeaders);
289 |
290 | return [$canonicalHeaders, $signedHeadersString];
291 | }
292 |
293 | /**
294 | * urlencode(注:同RFC3986方法)每一个querystring参数名称和参数值。
295 | * 按照ASCII字节顺序对参数名称严格排序,相同参数名的不同参数值需保持请求的原始顺序。
296 | * 将排序好的参数名称和参数值用=连接,按照排序结果将“参数对”用&连接。
297 | * 例如:CanonicalQueryString = "Action=ListUsers&Version=2018-01-01".
298 | *
299 | * @return string
300 | */
301 | public function getCanonicalQueryString(array $query)
302 | {
303 | ksort($query);
304 |
305 | return http_build_query($query);
306 | }
307 |
308 | /**
309 | * 指代正规化后的URI。
310 | * 如果URI为空,那么使用"/"作为绝对路径。
311 | * 在火山引擎中绝大多数接口的URI都为"/"。
312 | * 如果是复杂的path,请通过RFC3986规范进行编码。
313 | *
314 | * @return string
315 | */
316 | public function getCanonicalURI()
317 | {
318 | return '/';
319 | }
320 | }
321 |
--------------------------------------------------------------------------------
/src/Gateways/YidongmasblackGateway.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * This source file is subject to the MIT license that is bundled
9 | * with this source code in the file LICENSE.
10 | */
11 |
12 | namespace Overtrue\EasySms\Gateways;
13 |
14 | use Overtrue\EasySms\Contracts\MessageInterface;
15 | use Overtrue\EasySms\Contracts\PhoneNumberInterface;
16 | use Overtrue\EasySms\Exceptions\GatewayErrorException;
17 | use Overtrue\EasySms\Support\Config;
18 | use Overtrue\EasySms\Traits\HasHttpRequest;
19 |
20 | /**
21 | * Class YidongmasblackGateway.
22 | * 移动MAS黑名单模式(免创建模板).
23 | *
24 | * @author houang
25 | *
26 | * @see https://mas.10086.cn
27 | */
28 | class YidongmasblackGateway extends Gateway
29 | {
30 | use HasHttpRequest;
31 |
32 | public const ENDPOINT_URL = 'http://112.35.1.155:1992/sms/norsubmit';
33 |
34 | public const ENDPOINT_METHOD = 'send';
35 |
36 | /**
37 | * @return \Psr\Http\Message\ResponseInterface|array|string
38 | *
39 | * @throws GatewayErrorException
40 | */
41 | public function send(PhoneNumberInterface $to, MessageInterface $message, Config $config)
42 | {
43 | $params['ecName'] = $config->get('ecName');
44 | $params['apId'] = $config->get('apId');
45 | $params['sign'] = $config->get('sign');
46 | $params['addSerial'] = $config->get('addSerial');
47 | $params['mobiles'] = $to->getNumber();
48 | $params['content'] = $message->getContent();
49 | $result = $this->postJson(self::ENDPOINT_URL, $this->generateContent($params));
50 |
51 | if ('true' != $result['success']) {
52 | throw new GatewayErrorException($result['success'], $result['rspcod'], $result);
53 | }
54 |
55 | return $result;
56 | }
57 |
58 | /**
59 | * Generate Content.
60 | *
61 | * @param array $params
62 | *
63 | * @return string
64 | */
65 | protected function generateContent($params)
66 | {
67 | $secretKey = $this->config->get('secretKey');
68 | $params['mac'] = md5($params['ecName'].$params['apId'].$secretKey.$params['mobiles'].$params['content'].$params['sign'].$params['addSerial']);
69 |
70 | return base64_encode(json_encode($params));
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/src/Gateways/YunpianGateway.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * This source file is subject to the MIT license that is bundled
9 | * with this source code in the file LICENSE.
10 | */
11 |
12 | namespace Overtrue\EasySms\Gateways;
13 |
14 | use Overtrue\EasySms\Contracts\MessageInterface;
15 | use Overtrue\EasySms\Contracts\PhoneNumberInterface;
16 | use Overtrue\EasySms\Exceptions\GatewayErrorException;
17 | use Overtrue\EasySms\Support\Config;
18 | use Overtrue\EasySms\Traits\HasHttpRequest;
19 |
20 | /**
21 | * Class YunpianGateway.
22 | *
23 | * @see https://www.yunpian.com/doc/zh_CN/intl/single_send.html
24 | */
25 | class YunpianGateway extends Gateway
26 | {
27 | use HasHttpRequest;
28 |
29 | public const ENDPOINT_TEMPLATE = 'https://%s.yunpian.com/%s/%s/%s.%s';
30 |
31 | public const ENDPOINT_VERSION = 'v2';
32 |
33 | public const ENDPOINT_FORMAT = 'json';
34 |
35 | /**
36 | * @return array
37 | *
38 | * @throws GatewayErrorException ;
39 | */
40 | public function send(PhoneNumberInterface $to, MessageInterface $message, Config $config)
41 | {
42 | $template = $message->getTemplate($this);
43 | $function = 'single_send';
44 | $option = [
45 | 'form_params' => [
46 | 'apikey' => $config->get('api_key'),
47 | 'mobile' => $to->getUniversalNumber(),
48 | ],
49 | 'exceptions' => false,
50 | ];
51 |
52 | if (!is_null($template)) {
53 | $function = 'tpl_single_send';
54 | $data = [];
55 |
56 | $templateData = $message->getData($this);
57 | $templateData = isset($templateData) ? $templateData : [];
58 | foreach ($templateData as $key => $value) {
59 | $data[] = urlencode('#'.$key.'#').'='.urlencode($value);
60 | }
61 |
62 | $option['form_params'] = array_merge($option['form_params'], [
63 | 'tpl_id' => $template,
64 | 'tpl_value' => implode('&', $data),
65 | ]);
66 | } else {
67 | $content = $message->getContent($this);
68 | $signature = $config->get('signature', '');
69 | $option['form_params'] = array_merge($option['form_params'], [
70 | 'text' => 0 === \stripos($content, '【') ? $content : $signature.$content,
71 | ]);
72 | }
73 |
74 | $endpoint = $this->buildEndpoint('sms', 'sms', $function);
75 | $result = $this->request('post', $endpoint, $option);
76 |
77 | if ($result['code']) {
78 | throw new GatewayErrorException($result['msg'], $result['code'], $result);
79 | }
80 |
81 | return $result;
82 | }
83 |
84 | /**
85 | * Build endpoint url.
86 | *
87 | * @param string $type
88 | * @param string $resource
89 | * @param string $function
90 | *
91 | * @return string
92 | */
93 | protected function buildEndpoint($type, $resource, $function)
94 | {
95 | return sprintf(self::ENDPOINT_TEMPLATE, $type, self::ENDPOINT_VERSION, $resource, $function, self::ENDPOINT_FORMAT);
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/src/Gateways/YuntongxunGateway.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * This source file is subject to the MIT license that is bundled
9 | * with this source code in the file LICENSE.
10 | */
11 |
12 | namespace Overtrue\EasySms\Gateways;
13 |
14 | use Overtrue\EasySms\Contracts\MessageInterface;
15 | use Overtrue\EasySms\Contracts\PhoneNumberInterface;
16 | use Overtrue\EasySms\Exceptions\GatewayErrorException;
17 | use Overtrue\EasySms\Support\Config;
18 | use Overtrue\EasySms\Traits\HasHttpRequest;
19 |
20 | /**
21 | * Class YuntongxunGateway.
22 | *
23 | * @see Chinese Mainland: http://doc.yuntongxun.com/pe/5a533de33b8496dd00dce07c
24 | * @see International: http://doc.yuntongxun.com/pe/604f29eda80948a1006e928d
25 | */
26 | class YuntongxunGateway extends Gateway
27 | {
28 | use HasHttpRequest;
29 |
30 | public const ENDPOINT_TEMPLATE = 'https://%s:%s/%s/%s/%s/%s/%s?sig=%s';
31 |
32 | public const SERVER_IP = 'app.cloopen.com';
33 |
34 | public const DEBUG_SERVER_IP = 'sandboxapp.cloopen.com';
35 |
36 | public const DEBUG_TEMPLATE_ID = 1;
37 |
38 | public const SERVER_PORT = '8883';
39 |
40 | public const SDK_VERSION = '2013-12-26';
41 |
42 | public const SDK_VERSION_INT = 'v2';
43 |
44 | public const SUCCESS_CODE = '000000';
45 |
46 | private $international = false; // if international SMS, default false means no.
47 |
48 | /**
49 | * @return array
50 | *
51 | * @throws GatewayErrorException ;
52 | */
53 | public function send(PhoneNumberInterface $to, MessageInterface $message, Config $config)
54 | {
55 | $datetime = date('YmdHis');
56 |
57 | $data = [
58 | 'appId' => $config->get('app_id'),
59 | ];
60 |
61 | if ($to->inChineseMainland()) {
62 | $type = 'SMS';
63 | $resource = 'TemplateSMS';
64 | $data['to'] = $to->getNumber();
65 | $data['templateId'] = (int) ($this->config->get('debug') ? self::DEBUG_TEMPLATE_ID : $message->getTemplate($this));
66 | $data['datas'] = $message->getData($this);
67 | } else {
68 | $type = 'international';
69 | $resource = 'send';
70 | $this->international = true;
71 | $data['mobile'] = $to->getZeroPrefixedNumber();
72 | $data['content'] = $message->getContent($this);
73 | }
74 |
75 | $endpoint = $this->buildEndpoint($type, $resource, $datetime, $config);
76 |
77 | $result = $this->request('post', $endpoint, [
78 | 'json' => $data,
79 | 'headers' => [
80 | 'Accept' => 'application/json',
81 | 'Content-Type' => 'application/json;charset=utf-8',
82 | 'Authorization' => base64_encode($config->get('account_sid').':'.$datetime),
83 | ],
84 | ]);
85 |
86 | if (self::SUCCESS_CODE != $result['statusCode']) {
87 | throw new GatewayErrorException($result['statusCode'], $result['statusCode'], $result);
88 | }
89 |
90 | return $result;
91 | }
92 |
93 | /**
94 | * Build endpoint url.
95 | *
96 | * @param string $type
97 | * @param string $resource
98 | * @param string $datetime
99 | *
100 | * @return string
101 | */
102 | protected function buildEndpoint($type, $resource, $datetime, Config $config)
103 | {
104 | $serverIp = $this->config->get('debug') ? self::DEBUG_SERVER_IP : self::SERVER_IP;
105 |
106 | if ($this->international) {
107 | $accountType = 'account';
108 | $sdkVersion = self::SDK_VERSION_INT;
109 | } else {
110 | $accountType = $this->config->get('is_sub_account') ? 'SubAccounts' : 'Accounts';
111 | $sdkVersion = self::SDK_VERSION;
112 | }
113 |
114 | $sig = strtoupper(md5($config->get('account_sid').$config->get('account_token').$datetime));
115 |
116 | return sprintf(self::ENDPOINT_TEMPLATE, $serverIp, self::SERVER_PORT, $sdkVersion, $accountType, $config->get('account_sid'), $type, $resource, $sig);
117 | }
118 | }
119 |
--------------------------------------------------------------------------------
/src/Gateways/YunxinGateway.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * This source file is subject to the MIT license that is bundled
9 | * with this source code in the file LICENSE.
10 | */
11 |
12 | namespace Overtrue\EasySms\Gateways;
13 |
14 | use Overtrue\EasySms\Contracts\MessageInterface;
15 | use Overtrue\EasySms\Contracts\PhoneNumberInterface;
16 | use Overtrue\EasySms\Exceptions\GatewayErrorException;
17 | use Overtrue\EasySms\Support\Config;
18 | use Overtrue\EasySms\Traits\HasHttpRequest;
19 |
20 | /**
21 | * Class YunxinGateway.
22 | *
23 | * @author her-cat
24 | *
25 | * @see https://dev.yunxin.163.com/docs/product/%E7%9F%AD%E4%BF%A1/%E7%9F%AD%E4%BF%A1%E6%8E%A5%E5%8F%A3%E6%8C%87%E5%8D%97
26 | */
27 | class YunxinGateway extends Gateway
28 | {
29 | use HasHttpRequest;
30 |
31 | public const ENDPOINT_TEMPLATE = 'https://api.netease.im/%s/%s.action';
32 |
33 | public const ENDPOINT_ACTION = 'sendCode';
34 |
35 | public const SUCCESS_CODE = 200;
36 |
37 | /**
38 | * Send a short message.
39 | *
40 | * @return array
41 | *
42 | * @throws GatewayErrorException
43 | */
44 | public function send(PhoneNumberInterface $to, MessageInterface $message, Config $config)
45 | {
46 | $data = $message->getData($this);
47 |
48 | $action = isset($data['action']) ? $data['action'] : self::ENDPOINT_ACTION;
49 |
50 | $endpoint = $this->buildEndpoint('sms', $action);
51 |
52 | switch ($action) {
53 | case 'sendCode':
54 | $params = $this->buildSendCodeParams($to, $message, $config);
55 |
56 | break;
57 | case 'verifyCode':
58 | $params = $this->buildVerifyCodeParams($to, $message);
59 |
60 | break;
61 | case 'sendTemplate':
62 | $params = $this->buildTemplateParams($to, $message, $config);
63 |
64 | break;
65 | default:
66 | throw new GatewayErrorException(sprintf('action: %s not supported', $action), 0);
67 | }
68 |
69 | $headers = $this->buildHeaders($config);
70 |
71 | try {
72 | $result = $this->post($endpoint, $params, $headers);
73 |
74 | if (!isset($result['code']) || self::SUCCESS_CODE !== $result['code']) {
75 | $code = isset($result['code']) ? $result['code'] : 0;
76 | $error = isset($result['msg']) ? $result['msg'] : json_encode($result, JSON_UNESCAPED_UNICODE);
77 |
78 | throw new GatewayErrorException($error, $code);
79 | }
80 | } catch (\Exception $e) {
81 | throw new GatewayErrorException($e->getMessage(), $e->getCode());
82 | }
83 |
84 | return $result;
85 | }
86 |
87 | /**
88 | * @return string
89 | */
90 | protected function buildEndpoint($resource, $function)
91 | {
92 | return sprintf(self::ENDPOINT_TEMPLATE, $resource, strtolower($function));
93 | }
94 |
95 | /**
96 | * Get the request headers.
97 | *
98 | * @return array
99 | */
100 | protected function buildHeaders(Config $config)
101 | {
102 | $headers = [
103 | 'AppKey' => $config->get('app_key'),
104 | 'Nonce' => md5(uniqid('easysms')),
105 | 'CurTime' => (string) time(),
106 | 'Content-Type' => 'application/x-www-form-urlencoded;charset=utf-8',
107 | ];
108 |
109 | $headers['CheckSum'] = sha1("{$config->get('app_secret')}{$headers['Nonce']}{$headers['CurTime']}");
110 |
111 | return $headers;
112 | }
113 |
114 | /**
115 | * @return array
116 | */
117 | public function buildSendCodeParams(PhoneNumberInterface $to, MessageInterface $message, Config $config)
118 | {
119 | $data = $message->getData($this);
120 | $template = $message->getTemplate($this);
121 |
122 | return [
123 | 'mobile' => $to->getUniversalNumber(),
124 | 'authCode' => array_key_exists('code', $data) ? $data['code'] : '',
125 | 'deviceId' => array_key_exists('device_id', $data) ? $data['device_id'] : '',
126 | 'templateid' => is_string($template) ? $template : '',
127 | 'codeLen' => $config->get('code_length', 4),
128 | 'needUp' => $config->get('need_up', false),
129 | ];
130 | }
131 |
132 | /**
133 | * @return array
134 | *
135 | * @throws GatewayErrorException
136 | */
137 | public function buildVerifyCodeParams(PhoneNumberInterface $to, MessageInterface $message)
138 | {
139 | $data = $message->getData($this);
140 |
141 | if (!array_key_exists('code', $data)) {
142 | throw new GatewayErrorException('"code" cannot be empty', 0);
143 | }
144 |
145 | return [
146 | 'mobile' => $to->getUniversalNumber(),
147 | 'code' => $data['code'],
148 | ];
149 | }
150 |
151 | /**
152 | * @return array
153 | */
154 | public function buildTemplateParams(PhoneNumberInterface $to, MessageInterface $message, Config $config)
155 | {
156 | $data = $message->getData($this);
157 |
158 | $template = $message->getTemplate($this);
159 |
160 | return [
161 | 'templateid' => $template,
162 | 'mobiles' => json_encode([$to->getUniversalNumber()]),
163 | 'params' => array_key_exists('params', $data) ? json_encode($data['params']) : '',
164 | 'needUp' => $config->get('need_up', false),
165 | ];
166 | }
167 | }
168 |
--------------------------------------------------------------------------------
/src/Gateways/YunzhixunGateway.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * This source file is subject to the MIT license that is bundled
9 | * with this source code in the file LICENSE.
10 | */
11 |
12 | namespace Overtrue\EasySms\Gateways;
13 |
14 | use Overtrue\EasySms\Contracts\MessageInterface;
15 | use Overtrue\EasySms\Contracts\PhoneNumberInterface;
16 | use Overtrue\EasySms\Exceptions\GatewayErrorException;
17 | use Overtrue\EasySms\Support\Config;
18 | use Overtrue\EasySms\Traits\HasHttpRequest;
19 |
20 | /**
21 | * Class YunzhixunGateway.
22 | *
23 | * @author her-cat
24 | *
25 | * @see http://docs.ucpaas.com/doku.php?id=%E7%9F%AD%E4%BF%A1:sendsms
26 | */
27 | class YunzhixunGateway extends Gateway
28 | {
29 | use HasHttpRequest;
30 |
31 | public const SUCCESS_CODE = '000000';
32 |
33 | public const FUNCTION_SEND_SMS = 'sendsms';
34 |
35 | public const FUNCTION_BATCH_SEND_SMS = 'sendsms_batch';
36 |
37 | public const ENDPOINT_TEMPLATE = 'https://open.ucpaas.com/ol/%s/%s';
38 |
39 | /**
40 | * Send a short message.
41 | *
42 | * @return array
43 | *
44 | * @throws GatewayErrorException
45 | */
46 | public function send(PhoneNumberInterface $to, MessageInterface $message, Config $config)
47 | {
48 | $data = $message->getData($this);
49 |
50 | $function = isset($data['mobiles']) ? self::FUNCTION_BATCH_SEND_SMS : self::FUNCTION_SEND_SMS;
51 |
52 | $endpoint = $this->buildEndpoint('sms', $function);
53 |
54 | $params = $this->buildParams($to, $message, $config);
55 |
56 | return $this->execute($endpoint, $params);
57 | }
58 |
59 | /**
60 | * @return string
61 | */
62 | protected function buildEndpoint($resource, $function)
63 | {
64 | return sprintf(self::ENDPOINT_TEMPLATE, $resource, $function);
65 | }
66 |
67 | /**
68 | * @return array
69 | */
70 | protected function buildParams(PhoneNumberInterface $to, MessageInterface $message, Config $config)
71 | {
72 | $data = $message->getData($this);
73 |
74 | return [
75 | 'sid' => $config->get('sid'),
76 | 'token' => $config->get('token'),
77 | 'appid' => $config->get('app_id'),
78 | 'templateid' => $message->getTemplate($this),
79 | 'uid' => isset($data['uid']) ? $data['uid'] : '',
80 | 'param' => isset($data['params']) ? $data['params'] : '',
81 | 'mobile' => isset($data['mobiles']) ? $data['mobiles'] : $to->getNumber(),
82 | ];
83 | }
84 |
85 | /**
86 | * @return array
87 | *
88 | * @throws GatewayErrorException
89 | */
90 | protected function execute($endpoint, $params)
91 | {
92 | try {
93 | $result = $this->postJson($endpoint, $params);
94 |
95 | if (!isset($result['code']) || self::SUCCESS_CODE !== $result['code']) {
96 | $code = isset($result['code']) ? $result['code'] : 0;
97 | $error = isset($result['msg']) ? $result['msg'] : json_encode($result, JSON_UNESCAPED_UNICODE);
98 |
99 | throw new GatewayErrorException($error, $code);
100 | }
101 |
102 | return $result;
103 | } catch (\Exception $e) {
104 | throw new GatewayErrorException($e->getMessage(), $e->getCode());
105 | }
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/src/Gateways/ZzyunGateway.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * This source file is subject to the MIT license that is bundled
9 | * with this source code in the file LICENSE.
10 | */
11 |
12 | namespace Overtrue\EasySms\Gateways;
13 |
14 | use Overtrue\EasySms\Contracts\MessageInterface;
15 | use Overtrue\EasySms\Contracts\PhoneNumberInterface;
16 | use Overtrue\EasySms\Exceptions\GatewayErrorException;
17 | use Overtrue\EasySms\Support\Config;
18 | use Overtrue\EasySms\Traits\HasHttpRequest;
19 |
20 | /**
21 | * Class RongheyunGateway.
22 | *
23 | * @see https://zzyun.com/
24 | */
25 | class ZzyunGateway extends Gateway
26 | {
27 | use HasHttpRequest;
28 |
29 | public const ENDPOINT_URL = 'https://zzyun.com/api/sms/sendByTplCode';
30 |
31 | /**
32 | * @return array
33 | *
34 | * @throws GatewayErrorException ;
35 | */
36 | public function send(PhoneNumberInterface $to, MessageInterface $message, Config $config)
37 | {
38 | $time = time();
39 | $user_id = $config->get('user_id');
40 | $token = md5($time.$user_id.$config->get('secret'));
41 | $params = [
42 | 'user_id' => $user_id,
43 | 'time' => $time,
44 | 'token' => $token,
45 | 'mobiles' => $to->getNumber(), // 手机号码,多个英文逗号隔开
46 | 'tpl_code' => $message->getTemplate($this),
47 | 'tpl_params' => $message->getData($this),
48 | 'sign_name' => $config->get('sign_name'),
49 | ];
50 |
51 | $result = $this->post(self::ENDPOINT_URL, $params);
52 |
53 | if ('Success' != $result['Code']) {
54 | throw new GatewayErrorException($result['Message'], $result['Code'], $result);
55 | }
56 |
57 | return $result;
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/src/Message.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * This source file is subject to the MIT license that is bundled
9 | * with this source code in the file LICENSE.
10 | */
11 |
12 | namespace Overtrue\EasySms;
13 |
14 | use Overtrue\EasySms\Contracts\GatewayInterface;
15 | use Overtrue\EasySms\Contracts\MessageInterface;
16 |
17 | /**
18 | * Class Message.
19 | */
20 | class Message implements MessageInterface
21 | {
22 | protected array $gateways = [];
23 |
24 | protected string $type;
25 |
26 | protected \Closure|string|null $content = null;
27 |
28 | protected \Closure|string|null $template = null;
29 |
30 | protected array|\Closure $data = [];
31 |
32 | /**
33 | * Message constructor.
34 | */
35 | public function __construct(array $attributes = [], string $type = MessageInterface::TEXT_MESSAGE)
36 | {
37 | $this->type = $type;
38 |
39 | foreach ($attributes as $property => $value) {
40 | if (property_exists($this, $property)) {
41 | $this->$property = $value;
42 | }
43 | }
44 | }
45 |
46 | /**
47 | * Return the message type.
48 | */
49 | public function getMessageType(): string
50 | {
51 | return $this->type;
52 | }
53 |
54 | /**
55 | * Return message content.
56 | */
57 | public function getContent(?GatewayInterface $gateway = null): ?string
58 | {
59 | return is_callable($this->content) ? call_user_func($this->content, $gateway) : $this->content;
60 | }
61 |
62 | /**
63 | * Return the template id of message.
64 | */
65 | public function getTemplate(?GatewayInterface $gateway = null): ?string
66 | {
67 | return is_callable($this->template) ? call_user_func($this->template, $gateway) : $this->template;
68 | }
69 |
70 | /**
71 | * @return $this
72 | */
73 | public function setType($type): static
74 | {
75 | $this->type = $type;
76 |
77 | return $this;
78 | }
79 |
80 | /**
81 | * @return $this
82 | */
83 | public function setContent($content): static
84 | {
85 | $this->content = $content;
86 |
87 | return $this;
88 | }
89 |
90 | /**
91 | * @return $this
92 | */
93 | public function setTemplate($template): static
94 | {
95 | $this->template = $template;
96 |
97 | return $this;
98 | }
99 |
100 | public function getData(?GatewayInterface $gateway = null): array
101 | {
102 | return is_callable($this->data) ? call_user_func($this->data, $gateway) : $this->data;
103 | }
104 |
105 | /**
106 | * @return $this
107 | */
108 | public function setData(callable|array $data): static
109 | {
110 | $this->data = $data;
111 |
112 | return $this;
113 | }
114 |
115 | public function getGateways(): array
116 | {
117 | return $this->gateways;
118 | }
119 |
120 | /**
121 | * @return $this
122 | */
123 | public function setGateways(array $gateways): static
124 | {
125 | $this->gateways = $gateways;
126 |
127 | return $this;
128 | }
129 |
130 | /**
131 | * @return string
132 | */
133 | public function __get($property)
134 | {
135 | if (property_exists($this, $property)) {
136 | return $this->$property;
137 | }
138 | }
139 | }
140 |
--------------------------------------------------------------------------------
/src/Messenger.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * This source file is subject to the MIT license that is bundled
9 | * with this source code in the file LICENSE.
10 | */
11 |
12 | namespace Overtrue\EasySms;
13 |
14 | use Overtrue\EasySms\Contracts\MessageInterface;
15 | use Overtrue\EasySms\Contracts\PhoneNumberInterface;
16 | use Overtrue\EasySms\Exceptions\InvalidArgumentException;
17 | use Overtrue\EasySms\Exceptions\NoGatewayAvailableException;
18 |
19 | /**
20 | * Class Messenger.
21 | */
22 | class Messenger
23 | {
24 | public const STATUS_SUCCESS = 'success';
25 |
26 | public const STATUS_FAILURE = 'failure';
27 |
28 | protected EasySms $easySms;
29 |
30 | /**
31 | * Messenger constructor.
32 | */
33 | public function __construct(EasySms $easySms)
34 | {
35 | $this->easySms = $easySms;
36 | }
37 |
38 | /**
39 | * Send a message.
40 | *
41 | * @throws InvalidArgumentException
42 | * @throws NoGatewayAvailableException
43 | */
44 | public function send(PhoneNumberInterface $to, MessageInterface $message, array $gateways = []): array
45 | {
46 | $results = [];
47 | $isSuccessful = false;
48 |
49 | foreach ($gateways as $gateway => $config) {
50 | try {
51 | $results[$gateway] = [
52 | 'gateway' => $gateway,
53 | 'status' => self::STATUS_SUCCESS,
54 | 'template' => $message->getTemplate($this->easySms->gateway($gateway)),
55 | 'result' => $this->easySms->gateway($gateway)->send($to, $message, $config),
56 | ];
57 | $isSuccessful = true;
58 |
59 | break;
60 | } catch (\Exception|\Throwable $e) {
61 | $results[$gateway] = [
62 | 'gateway' => $gateway,
63 | 'status' => self::STATUS_FAILURE,
64 | 'template' => $message->getTemplate($this->easySms->gateway($gateway)),
65 | 'exception' => $e,
66 | ];
67 | }
68 | }
69 |
70 | if (!$isSuccessful) {
71 | throw new NoGatewayAvailableException($results);
72 | }
73 |
74 | return $results;
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/src/PhoneNumber.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * This source file is subject to the MIT license that is bundled
9 | * with this source code in the file LICENSE.
10 | */
11 |
12 | namespace Overtrue\EasySms;
13 |
14 | /**
15 | * Class PhoneNumberInterface.
16 | *
17 | * @author overtrue
18 | */
19 | class PhoneNumber implements Contracts\PhoneNumberInterface
20 | {
21 | protected int|string $number;
22 |
23 | protected ?int $IDDCode;
24 |
25 | /**
26 | * PhoneNumberInterface constructor.
27 | */
28 | public function __construct(int|string $numberWithoutIDDCode, ?string $IDDCode = null)
29 | {
30 | $this->number = $numberWithoutIDDCode;
31 | $this->IDDCode = $IDDCode ? intval(ltrim($IDDCode, '+0')) : null;
32 | }
33 |
34 | /**
35 | * 86.
36 | */
37 | public function getIDDCode(): ?int
38 | {
39 | return $this->IDDCode;
40 | }
41 |
42 | /**
43 | * 18888888888.
44 | */
45 | public function getNumber(): int|string
46 | {
47 | return $this->number;
48 | }
49 |
50 | /**
51 | * +8618888888888.
52 | */
53 | public function getUniversalNumber(): string
54 | {
55 | return $this->getPrefixedIDDCode('+').$this->number;
56 | }
57 |
58 | /**
59 | * 008618888888888.
60 | */
61 | public function getZeroPrefixedNumber(): string
62 | {
63 | return $this->getPrefixedIDDCode('00').$this->number;
64 | }
65 |
66 | public function getPrefixedIDDCode(string $prefix): ?string
67 | {
68 | return $this->IDDCode ? $prefix.$this->IDDCode : null;
69 | }
70 |
71 | /**
72 | * @return string
73 | */
74 | public function __toString()
75 | {
76 | return $this->getUniversalNumber();
77 | }
78 |
79 | /**
80 | * Specify data which should be serialized to JSON.
81 | *
82 | * @see http://php.net/manual/en/jsonserializable.jsonserialize.php
83 | *
84 | * @return mixed data which can be serialized by json_encode,
85 | * which is a value of any type other than a resource
86 | *
87 | * @since 5.4.0
88 | */
89 | #[\ReturnTypeWillChange]
90 | public function jsonSerialize()
91 | {
92 | return $this->getUniversalNumber();
93 | }
94 |
95 | /**
96 | * Check if the phone number belongs to chinese mainland.
97 | */
98 | public function inChineseMainland(): bool
99 | {
100 | return empty($this->IDDCode) || 86 === $this->IDDCode;
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/src/Strategies/OrderStrategy.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * This source file is subject to the MIT license that is bundled
9 | * with this source code in the file LICENSE.
10 | */
11 |
12 | namespace Overtrue\EasySms\Strategies;
13 |
14 | use Overtrue\EasySms\Contracts\StrategyInterface;
15 |
16 | /**
17 | * Class OrderStrategy.
18 | */
19 | class OrderStrategy implements StrategyInterface
20 | {
21 | /**
22 | * Apply the strategy and return result.
23 | */
24 | public function apply(array $gateways): array
25 | {
26 | return array_keys($gateways);
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/Strategies/RandomStrategy.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * This source file is subject to the MIT license that is bundled
9 | * with this source code in the file LICENSE.
10 | */
11 |
12 | namespace Overtrue\EasySms\Strategies;
13 |
14 | use Overtrue\EasySms\Contracts\StrategyInterface;
15 |
16 | /**
17 | * Class RandomStrategy.
18 | */
19 | class RandomStrategy implements StrategyInterface
20 | {
21 | public function apply(array $gateways): array
22 | {
23 | try {
24 | $keys = \array_keys($gateways);
25 | $n = \count($keys);
26 |
27 | for ($i = $n - 1; $i > 0; --$i) {
28 | $j = \random_int(0, $i);
29 | [$keys[$i], $keys[$j]] = [$keys[$j], $keys[$i]];
30 | }
31 |
32 | return $keys;
33 | } catch (\Throwable $exception) {
34 | $keys = \array_keys($gateways);
35 | shuffle($keys);
36 |
37 | return $keys;
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/Support/Config.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * This source file is subject to the MIT license that is bundled
9 | * with this source code in the file LICENSE.
10 | */
11 |
12 | namespace Overtrue\EasySms\Support;
13 |
14 | use ArrayAccess;
15 |
16 | /**
17 | * Class Config.
18 | */
19 | class Config implements \ArrayAccess
20 | {
21 | protected array $config;
22 |
23 | /**
24 | * Config constructor.
25 | */
26 | public function __construct(array $config = [])
27 | {
28 | $this->config = $config;
29 | }
30 |
31 | /**
32 | * Get an item from an array using "dot" notation.
33 | *
34 | * @return array|mixed|null
35 | */
36 | public function get(string $key, mixed $default = null): mixed
37 | {
38 | $config = $this->config;
39 |
40 | if (isset($config[$key])) {
41 | return $config[$key];
42 | }
43 |
44 | if (!str_contains($key, '.')) {
45 | return $default;
46 | }
47 |
48 | foreach (explode('.', $key) as $segment) {
49 | if (!is_array($config) || !array_key_exists($segment, $config)) {
50 | return $default;
51 | }
52 | $config = $config[$segment];
53 | }
54 |
55 | return $config;
56 | }
57 |
58 | /**
59 | * Whether a offset exists.
60 | *
61 | * @see http://php.net/manual/en/arrayaccess.offsetexists.php
62 | *
63 | * @param mixed $offset
64 | * An offset to check for.
65 | *
66 | *
67 | * @return bool true on success or false on failure.
68 | *
69 | *
70 | * The return value will be casted to boolean if non-boolean was returned
71 | *
72 | * @since 5.0.0
73 | */
74 | #[\ReturnTypeWillChange]
75 | public function offsetExists(mixed $offset): bool
76 | {
77 | return array_key_exists($offset, $this->config);
78 | }
79 |
80 | /**
81 | * Offset to retrieve.
82 | *
83 | * @see http://php.net/manual/en/arrayaccess.offsetget.php
84 | *
85 | * @param mixed $offset
86 | * The offset to retrieve.
87 | *
88 | *
89 | * @return mixed Can return all value types
90 | *
91 | * @since 5.0.0
92 | */
93 | #[\ReturnTypeWillChange]
94 | public function offsetGet(mixed $offset): mixed
95 | {
96 | return $this->get($offset);
97 | }
98 |
99 | /**
100 | * Offset to set.
101 | *
102 | * @see http://php.net/manual/en/arrayaccess.offsetset.php
103 | *
104 | * @param mixed $offset
105 | * The offset to assign the value to.
106 | *
107 | * @param mixed $value
108 | * The value to set.
109 | *
110 | *
111 | * @since 5.0.0
112 | */
113 | #[\ReturnTypeWillChange]
114 | public function offsetSet(mixed $offset, mixed $value): void
115 | {
116 | if (isset($this->config[$offset])) {
117 | $this->config[$offset] = $value;
118 | }
119 | }
120 |
121 | /**
122 | * Offset to unset.
123 | *
124 | * @see http://php.net/manual/en/arrayaccess.offsetunset.php
125 | *
126 | * @param mixed $offset
127 | * The offset to unset.
128 | *
129 | *
130 | * @since 5.0.0
131 | */
132 | #[\ReturnTypeWillChange]
133 | public function offsetUnset(mixed $offset): void
134 | {
135 | if (isset($this->config[$offset])) {
136 | unset($this->config[$offset]);
137 | }
138 | }
139 | }
140 |
--------------------------------------------------------------------------------
/src/Traits/HasHttpRequest.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * This source file is subject to the MIT license that is bundled
9 | * with this source code in the file LICENSE.
10 | */
11 |
12 | namespace Overtrue\EasySms\Traits;
13 |
14 | use GuzzleHttp\Client;
15 | use Psr\Http\Message\ResponseInterface;
16 |
17 | /**
18 | * Trait HasHttpRequest.
19 | */
20 | trait HasHttpRequest
21 | {
22 | /**
23 | * Make a get request.
24 | *
25 | * @param string $endpoint
26 | * @param array $query
27 | * @param array $headers
28 | *
29 | * @return ResponseInterface|array|string
30 | */
31 | protected function get($endpoint, $query = [], $headers = [])
32 | {
33 | return $this->request('get', $endpoint, [
34 | 'headers' => $headers,
35 | 'query' => $query,
36 | ]);
37 | }
38 |
39 | /**
40 | * Make a post request.
41 | *
42 | * @param string $endpoint
43 | * @param array $params
44 | * @param array $headers
45 | *
46 | * @return ResponseInterface|array|string
47 | */
48 | protected function post($endpoint, $params = [], $headers = [])
49 | {
50 | return $this->request('post', $endpoint, [
51 | 'headers' => $headers,
52 | 'form_params' => $params,
53 | ]);
54 | }
55 |
56 | /**
57 | * Make a post request with json params.
58 | *
59 | * @param array $params
60 | * @param array $headers
61 | *
62 | * @return ResponseInterface|array|string
63 | */
64 | protected function postJson($endpoint, $params = [], $headers = [])
65 | {
66 | return $this->request('post', $endpoint, [
67 | 'headers' => $headers,
68 | 'json' => $params,
69 | ]);
70 | }
71 |
72 | /**
73 | * Make a http request.
74 | *
75 | * @param string $method
76 | * @param string $endpoint
77 | * @param array $options http://docs.guzzlephp.org/en/latest/request-options.html
78 | *
79 | * @return ResponseInterface|array|string
80 | */
81 | protected function request($method, $endpoint, $options = [])
82 | {
83 | return $this->unwrapResponse($this->getHttpClient($this->getBaseOptions())->{$method}($endpoint, $options));
84 | }
85 |
86 | /**
87 | * Return base Guzzle options.
88 | *
89 | * @return array
90 | */
91 | protected function getBaseOptions()
92 | {
93 | $options = method_exists($this, 'getGuzzleOptions') ? $this->getGuzzleOptions() : [];
94 |
95 | return \array_merge($options, [
96 | 'base_uri' => method_exists($this, 'getBaseUri') ? $this->getBaseUri() : '',
97 | 'timeout' => method_exists($this, 'getTimeout') ? $this->getTimeout() : 5.0,
98 | ]);
99 | }
100 |
101 | /**
102 | * Return http client.
103 | *
104 | * @return Client
105 | *
106 | * @codeCoverageIgnore
107 | */
108 | protected function getHttpClient(array $options = [])
109 | {
110 | return new Client($options);
111 | }
112 |
113 | /**
114 | * Convert response contents to json.
115 | *
116 | * @return ResponseInterface|array|string
117 | */
118 | protected function unwrapResponse(ResponseInterface $response)
119 | {
120 | $contentType = $response->getHeaderLine('Content-Type');
121 | $contents = $response->getBody()->getContents();
122 |
123 | if (false !== stripos($contentType, 'json') || stripos($contentType, 'javascript')) {
124 | return json_decode($contents, true);
125 | } elseif (false !== stripos($contentType, 'xml')) {
126 | return json_decode(json_encode(simplexml_load_string($contents)), true);
127 | }
128 |
129 | return $contents;
130 | }
131 | }
132 |
--------------------------------------------------------------------------------