初始化项目

This commit is contained in:
2026-09-01 22:15:50 +08:00
commit b5a26a8848
11217 changed files with 1710987 additions and 0 deletions
@@ -0,0 +1,107 @@
<?php
/*
* This file is part of the overtrue/easy-sms.
*
* (c) overtrue <i@overtrue.me>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Overtrue\EasySms\Gateways;
use Overtrue\EasySms\Contracts\MessageInterface;
use Overtrue\EasySms\Contracts\PhoneNumberInterface;
use Overtrue\EasySms\Exceptions\GatewayErrorException;
use Overtrue\EasySms\Support\Config;
use Overtrue\EasySms\Traits\HasHttpRequest;
/**
* Class AliyunGateway.
*
* @author carson <docxcn@gmail.com>
*
* @see https://help.aliyun.com/document_detail/55451.html
*/
class AliyunGateway extends Gateway
{
use HasHttpRequest;
const ENDPOINT_URL = 'http://dysmsapi.aliyuncs.com';
const ENDPOINT_METHOD = 'SendSms';
const ENDPOINT_VERSION = '2017-05-25';
const ENDPOINT_FORMAT = 'JSON';
const ENDPOINT_REGION_ID = 'cn-hangzhou';
const ENDPOINT_SIGNATURE_METHOD = 'HMAC-SHA1';
const ENDPOINT_SIGNATURE_VERSION = '1.0';
/**
* @param \Overtrue\EasySms\Contracts\PhoneNumberInterface $to
* @param \Overtrue\EasySms\Contracts\MessageInterface $message
* @param \Overtrue\EasySms\Support\Config $config
*
* @return array
*
* @throws \Overtrue\EasySms\Exceptions\GatewayErrorException ;
*/
public function send(PhoneNumberInterface $to, MessageInterface $message, Config $config)
{
$data = $message->getData($this);
$signName = !empty($data['sign_name']) ? $data['sign_name'] : $config->get('sign_name');
unset($data['sign_name']);
$params = [
'RegionId' => self::ENDPOINT_REGION_ID,
'AccessKeyId' => $config->get('access_key_id'),
'Format' => self::ENDPOINT_FORMAT,
'SignatureMethod' => self::ENDPOINT_SIGNATURE_METHOD,
'SignatureVersion' => self::ENDPOINT_SIGNATURE_VERSION,
'SignatureNonce' => uniqid(),
'Timestamp' => gmdate('Y-m-d\TH:i:s\Z'),
'Action' => self::ENDPOINT_METHOD,
'Version' => self::ENDPOINT_VERSION,
'PhoneNumbers' => !\is_null($to->getIDDCode()) ? strval($to->getZeroPrefixedNumber()) : $to->getNumber(),
'SignName' => $signName,
'TemplateCode' => $message->getTemplate($this),
'TemplateParam' => json_encode($data, JSON_FORCE_OBJECT),
];
$params['Signature'] = $this->generateSign($params);
$result = $this->get(self::ENDPOINT_URL, $params);
if (!empty($result['Code']) && 'OK' != $result['Code']) {
throw new GatewayErrorException($result['Message'], $result['Code'], $result);
}
return $result;
}
/**
* Generate Sign.
*
* @param array $params
*
* @return string
*
* @see https://help.aliyun.com/document_detail/101343.html
*/
protected function generateSign($params)
{
ksort($params);
$accessKeySecret = $this->config->get('access_key_secret');
$stringToSign = 'GET&%2F&'.urlencode(http_build_query($params, '', '&', PHP_QUERY_RFC3986));
$stringToSign = str_replace('%7E', '~', $stringToSign);
return base64_encode(hash_hmac('sha1', $stringToSign, $accessKeySecret.'&', true));
}
}
@@ -0,0 +1,97 @@
<?php
namespace Overtrue\EasySms\Gateways;
use Overtrue\EasySms\Contracts\MessageInterface;
use Overtrue\EasySms\Contracts\PhoneNumberInterface;
use Overtrue\EasySms\Exceptions\GatewayErrorException;
use Overtrue\EasySms\Support\Config;
use Overtrue\EasySms\Traits\HasHttpRequest;
/**
* Class AliyunIntlGateway
*
* @package \Overtrue\EasySms\Gateways
*
* @see https://www.alibabacloud.com/help/zh/doc-detail/162279.htm
*/
class AliyunIntlGateway extends Gateway
{
use HasHttpRequest;
const ENDPOINT_URL = 'https://dysmsapi.ap-southeast-1.aliyuncs.com';
const ENDPOINT_ACTION = 'SendMessageWithTemplate';
const ENDPOINT_VERSION = '2018-05-01';
const ENDPOINT_FORMAT = 'JSON';
const ENDPOINT_REGION_ID = 'ap-southeast-1';
const ENDPOINT_SIGNATURE_METHOD = 'HMAC-SHA1';
const ENDPOINT_SIGNATURE_VERSION = '1.0';
/**
* @param \Overtrue\EasySms\Contracts\PhoneNumberInterface $to
* @param \Overtrue\EasySms\Contracts\MessageInterface $message
* @param \Overtrue\EasySms\Support\Config $config
*
* @return array
*
* @throws \Overtrue\EasySms\Exceptions\GatewayErrorException
*
*/
public function send(PhoneNumberInterface $to, MessageInterface $message, Config $config)
{
$data = $message->getData($this);
$signName = !empty($data['sign_name']) ? $data['sign_name'] : $config->get('sign_name');
unset($data['sign_name']);
$params = [
'RegionId' => self::ENDPOINT_REGION_ID,
'AccessKeyId' => $config->get('access_key_id'),
'Format' => self::ENDPOINT_FORMAT,
'SignatureMethod' => self::ENDPOINT_SIGNATURE_METHOD,
'SignatureVersion' => self::ENDPOINT_SIGNATURE_VERSION,
'SignatureNonce' => uniqid('', true),
'Timestamp' => gmdate('Y-m-d\TH:i:s\Z'),
'Version' => self::ENDPOINT_VERSION,
'To' => !\is_null($to->getIDDCode()) ? (int) $to->getZeroPrefixedNumber() : $to->getNumber(),
'Action' => self::ENDPOINT_ACTION,
'From' => $signName,
'TemplateCode' => $message->getTemplate($this),
'TemplateParam' => json_encode($data, JSON_FORCE_OBJECT),
];
$params['Signature'] = $this->generateSign($params);
$result = $this->get(self::ENDPOINT_URL, $params);
if ('OK' !== $result['ResponseCode']) {
throw new GatewayErrorException($result['ResponseDescription'], $result['ResponseCode'], $result);
}
return $result;
}
/**
* Generate sign
*
* @param array $params
*
* @return string
*/
protected function generateSign(array $params)
{
ksort($params);
$accessKeySecret = $this->config->get('access_key_secret');
$stringToSign = 'GET&%2F&'.urlencode(http_build_query($params, '', '&', PHP_QUERY_RFC3986));
return base64_encode(hash_hmac('sha1', $stringToSign, $accessKeySecret.'&', true));
}
}
@@ -0,0 +1,107 @@
<?php
/*
* This file is part of the overtrue/easy-sms.
*
* (c) overtrue <i@overtrue.me>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Overtrue\EasySms\Gateways;
use Overtrue\EasySms\Contracts\MessageInterface;
use Overtrue\EasySms\Contracts\PhoneNumberInterface;
use Overtrue\EasySms\Exceptions\GatewayErrorException;
use Overtrue\EasySms\Support\Config;
use Overtrue\EasySms\Traits\HasHttpRequest;
/**
* Class AliyunrestGateway.
*/
class AliyunrestGateway extends Gateway
{
use HasHttpRequest;
const ENDPOINT_URL = 'http://gw.api.taobao.com/router/rest';
const ENDPOINT_VERSION = '2.0';
const ENDPOINT_FORMAT = 'json';
const ENDPOINT_METHOD = 'alibaba.aliqin.fc.sms.num.send';
const ENDPOINT_SIGNATURE_METHOD = 'md5';
const ENDPOINT_PARTNER_ID = 'EasySms';
/**
* @param PhoneNumberInterface $to
* @param MessageInterface $message
* @param Config $config
*
* @return array|void
*/
public function send(PhoneNumberInterface $to, MessageInterface $message, Config $config)
{
$urlParams = [
'app_key' => $config->get('app_key'),
'v' => self::ENDPOINT_VERSION,
'format' => self::ENDPOINT_FORMAT,
'sign_method' => self::ENDPOINT_SIGNATURE_METHOD,
'method' => self::ENDPOINT_METHOD,
'timestamp' => date('Y-m-d H:i:s'),
'partner_id' => self::ENDPOINT_PARTNER_ID,
];
$params = [
'extend' => '',
'sms_type' => 'normal',
'sms_free_sign_name' => $config->get('sign_name'),
'sms_param' => json_encode($message->getData($this)),
'rec_num' => !\is_null($to->getIDDCode()) ? strval($to->getZeroPrefixedNumber()) : $to->getNumber(),
'sms_template_code' => $message->getTemplate($this),
];
$urlParams['sign'] = $this->generateSign(array_merge($params, $urlParams));
$result = $this->post($this->getEndpointUrl($urlParams), $params);
if (isset($result['error_response']) && 0 != $result['error_response']['code']) {
throw new GatewayErrorException($result['error_response']['msg'], $result['error_response']['code'], $result);
}
return $result;
}
/**
* @param array $params
*
* @return string
*/
protected function getEndpointUrl($params)
{
return self::ENDPOINT_URL.'?'.http_build_query($params);
}
/**
* @param array $params
*
* @return string
*/
protected function generateSign($params)
{
ksort($params);
$stringToBeSigned = $this->config->get('app_secret_key');
foreach ($params as $k => $v) {
if (!is_array($v) && '@' != substr($v, 0, 1)) {
$stringToBeSigned .= "$k$v";
}
}
unset($k, $v);
$stringToBeSigned .= $this->config->get('app_secret_key');
return strtoupper(md5($stringToBeSigned));
}
}
@@ -0,0 +1,60 @@
<?php
/*
* This file is part of the overtrue/easy-sms.
*
* (c) overtrue <i@overtrue.me>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Overtrue\EasySms\Gateways;
use Overtrue\EasySms\Contracts\MessageInterface;
use Overtrue\EasySms\Contracts\PhoneNumberInterface;
use Overtrue\EasySms\Exceptions\GatewayErrorException;
use Overtrue\EasySms\Support\Config;
use Overtrue\EasySms\Traits\HasHttpRequest;
/**
* Class AvatardataGateway.
*
* @see http://www.avatardata.cn/Docs/Api/fd475e40-7809-4be7-936c-5926dd41b0fe
*/
class AvatardataGateway extends Gateway
{
use HasHttpRequest;
const ENDPOINT_URL = 'http://v1.avatardata.cn/Sms/Send';
const ENDPOINT_FORMAT = 'json';
/**
* @param PhoneNumberInterface $to
* @param MessageInterface $message
* @param Config $config
*
* @return array
*
* @throws GatewayErrorException;
*/
public function send(PhoneNumberInterface $to, MessageInterface $message, Config $config)
{
$params = [
'mobile' => $to->getNumber(),
'templateId' => $message->getTemplate($this),
'param' => implode(',', $message->getData($this)),
'dtype' => self::ENDPOINT_FORMAT,
'key' => $config->get('app_key'),
];
$result = $this->get(self::ENDPOINT_URL, $params);
if ($result['error_code']) {
throw new GatewayErrorException($result['reason'], $result['error_code'], $result);
}
return $result;
}
}
@@ -0,0 +1,174 @@
<?php
/*
* This file is part of the overtrue/easy-sms.
*
* (c) overtrue <i@overtrue.me>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Overtrue\EasySms\Gateways;
use Overtrue\EasySms\Contracts\MessageInterface;
use Overtrue\EasySms\Contracts\PhoneNumberInterface;
use Overtrue\EasySms\Exceptions\GatewayErrorException;
use Overtrue\EasySms\Support\Config;
use Overtrue\EasySms\Traits\HasHttpRequest;
/**
* Class BaiduGateway.
*
* @see https://cloud.baidu.com/doc/SMS/index.html
*/
class BaiduGateway extends Gateway
{
use HasHttpRequest;
const ENDPOINT_HOST = 'smsv3.bj.baidubce.com';
const ENDPOINT_URI = '/api/v3/sendSms';
const BCE_AUTH_VERSION = 'bce-auth-v1';
const DEFAULT_EXPIRATION_IN_SECONDS = 1800; //签名有效期默认1800秒
const SUCCESS_CODE = 1000;
/**
* Send message.
*
* @param \Overtrue\EasySms\Contracts\PhoneNumberInterface $to
* @param \Overtrue\EasySms\Contracts\MessageInterface $message
* @param \Overtrue\EasySms\Support\Config $config
*
* @return array
*
* @throws \Overtrue\EasySms\Exceptions\GatewayErrorException ;
*/
public function send(PhoneNumberInterface $to, MessageInterface $message, Config $config)
{
$params = [
'signatureId' => $config->get('invoke_id'),
'mobile' => $to->getNumber(),
'template' => $message->getTemplate($this),
'contentVar' => $message->getData($this),
];
if (!empty($params['contentVar']['custom'])) {
//用户自定义参数,格式为字符串,状态回调时会回传该值
$params['custom'] = $params['contentVar']['custom'];
unset($params['contentVar']['custom']);
}
if (!empty($params['contentVar']['userExtId'])) {
//通道自定义扩展码,上行回调时会回传该值,其格式为纯数字串。默认为不开通,请求时无需设置该参数。如需开通请联系客服申请
$params['userExtId'] = $params['contentVar']['userExtId'];
unset($params['contentVar']['userExtId']);
}
$datetime = gmdate('Y-m-d\TH:i:s\Z');
$headers = [
'host' => self::ENDPOINT_HOST,
'content-type' => 'application/json',
'x-bce-date' => $datetime,
];
//获得需要签名的数据
$signHeaders = $this->getHeadersToSign($headers, ['host', 'x-bce-date']);
$headers['Authorization'] = $this->generateSign($signHeaders, $datetime, $config);
$result = $this->request('post', self::buildEndpoint($config), ['headers' => $headers, 'json' => $params]);
if (self::SUCCESS_CODE != $result['code']) {
throw new GatewayErrorException($result['message'], $result['code'], $result);
}
return $result;
}
/**
* Build endpoint url.
*
* @param \Overtrue\EasySms\Support\Config $config
*
* @return string
*/
protected function buildEndpoint(Config $config)
{
return 'http://'.$config->get('domain', self::ENDPOINT_HOST).self::ENDPOINT_URI;
}
/**
* Generate Authorization header.
*
* @param array $signHeaders
* @param int $datetime
* @param \Overtrue\EasySms\Support\Config $config
*
* @return string
*/
protected function generateSign(array $signHeaders, $datetime, Config $config)
{
// 生成 authString
$authString = self::BCE_AUTH_VERSION.'/'.$config->get('ak').'/'
.$datetime.'/'.self::DEFAULT_EXPIRATION_IN_SECONDS;
// 使用 sk 和 authString 生成 signKey
$signingKey = hash_hmac('sha256', $authString, $config->get('sk'));
// 生成标准化 URI
// 根据 RFC 3986,除了:1.大小写英文字符 2.阿拉伯数字 3.点'.'、波浪线'~'、减号'-'以及下划线'_' 以外都要编码
$canonicalURI = str_replace('%2F', '/', rawurlencode(self::ENDPOINT_URI));
// 生成标准化 QueryString
$canonicalQueryString = ''; // 此 api 不需要此项。返回空字符串
// 整理 headersToSign,以 ';' 号连接
$signedHeaders = empty($signHeaders) ? '' : strtolower(trim(implode(';', array_keys($signHeaders))));
// 生成标准化 header
$canonicalHeader = $this->getCanonicalHeaders($signHeaders);
// 组成标准请求串
$canonicalRequest = "POST\n{$canonicalURI}\n{$canonicalQueryString}\n{$canonicalHeader}";
// 使用 signKey 和标准请求串完成签名
$signature = hash_hmac('sha256', $canonicalRequest, $signingKey);
// 组成最终签名串
return "{$authString}/{$signedHeaders}/{$signature}";
}
/**
* 生成标准化 http 请求头串.
*
* @param array $headers
*
* @return string
*/
protected function getCanonicalHeaders(array $headers)
{
$headerStrings = [];
foreach ($headers as $name => $value) {
//trim后再encode,之后使用':'号连接起来
$headerStrings[] = rawurlencode(strtolower(trim($name))).':'.rawurlencode(trim($value));
}
sort($headerStrings);
return implode("\n", $headerStrings);
}
/**
* 根据 指定的 keys 过滤应该参与签名的 header.
*
* @param array $headers
* @param array $keys
*
* @return array
*/
protected function getHeadersToSign(array $headers, array $keys)
{
return array_intersect_key($headers, array_flip($keys));
}
}
@@ -0,0 +1,156 @@
<?php
/*
* This file is part of the overtrue/easy-sms.
*
* (c) overtrue <i@overtrue.me>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Overtrue\EasySms\Gateways;
use Overtrue\EasySms\Contracts\MessageInterface;
use Overtrue\EasySms\Contracts\PhoneNumberInterface;
use Overtrue\EasySms\Exceptions\GatewayErrorException;
use Overtrue\EasySms\Exceptions\InvalidArgumentException;
use Overtrue\EasySms\Support\Config;
use Overtrue\EasySms\Traits\HasHttpRequest;
/**
* Class ChuanglanGateway.
*
* @see https://zz.253.com/v5.html#/api_doc
*/
class ChuanglanGateway extends Gateway
{
use HasHttpRequest;
/**
* URL模板
*/
const ENDPOINT_URL_TEMPLATE = 'https://%s.253.com/msg/send/json';
/**
* 国际短信
*/
const INT_URL = 'http://intapi.253.com/send/json';
/**
* 验证码渠道code.
*/
const CHANNEL_VALIDATE_CODE = 'smsbj1';
/**
* 会员营销渠道code.
*/
const CHANNEL_PROMOTION_CODE = 'smssh1';
/**
* @param PhoneNumberInterface $to
* @param MessageInterface $message
* @param Config $config
*
* @return array
*
* @throws GatewayErrorException
* @throws InvalidArgumentException
*/
public function send(PhoneNumberInterface $to, MessageInterface $message, Config $config)
{
$IDDCode = !empty($to->getIDDCode()) ? $to->getIDDCode() : 86;
$params = [
'account' => $config->get('account'),
'password' => $config->get('password'),
'phone' => $to->getNumber(),
'msg' => $this->wrapChannelContent($message->getContent($this), $config, $IDDCode),
];
if (86 != $IDDCode) {
$params['mobile'] = $to->getIDDCode().$to->getNumber();
$params['account'] = $config->get('intel_account') ?: $config->get('account');
$params['password'] = $config->get('intel_password') ?: $config->get('password');
}
$result = $this->postJson($this->buildEndpoint($config, $IDDCode), $params);
if (!isset($result['code']) || '0' != $result['code']) {
throw new GatewayErrorException(json_encode($result, JSON_UNESCAPED_UNICODE), isset($result['code']) ? $result['code'] : 0, $result);
}
return $result;
}
/**
* @param Config $config
* @param int $IDDCode
*
* @return string
*
* @throws InvalidArgumentException
*/
protected function buildEndpoint(Config $config, $IDDCode = 86)
{
$channel = $this->getChannel($config, $IDDCode);
if (self::INT_URL === $channel) {
return $channel;
}
return sprintf(self::ENDPOINT_URL_TEMPLATE, $channel);
}
/**
* @param Config $config
* @param int $IDDCode
*
* @return mixed
*
* @throws InvalidArgumentException
*/
protected function getChannel(Config $config, $IDDCode)
{
if (86 != $IDDCode) {
return self::INT_URL;
}
$channel = $config->get('channel', self::CHANNEL_VALIDATE_CODE);
if (!in_array($channel, [self::CHANNEL_VALIDATE_CODE, self::CHANNEL_PROMOTION_CODE])) {
throw new InvalidArgumentException('Invalid channel for ChuanglanGateway.');
}
return $channel;
}
/**
* @param string $content
* @param Config $config
* @param int $IDDCode
*
* @return string|string
*
* @throws InvalidArgumentException
*/
protected function wrapChannelContent($content, Config $config, $IDDCode)
{
$channel = $this->getChannel($config, $IDDCode);
if (self::CHANNEL_PROMOTION_CODE == $channel) {
$sign = (string) $config->get('sign', '');
if (empty($sign)) {
throw new InvalidArgumentException('Invalid sign for ChuanglanGateway when using promotion channel');
}
$unsubscribe = (string) $config->get('unsubscribe', '');
if (empty($unsubscribe)) {
throw new InvalidArgumentException('Invalid unsubscribe for ChuanglanGateway when using promotion channel');
}
$content = $sign.$content.$unsubscribe;
}
return $content;
}
}
@@ -0,0 +1,147 @@
<?php
/*
* This file is part of the overtrue/easy-sms.
*
* (c) overtrue <i@overtrue.me>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Overtrue\EasySms\Gateways;
use Overtrue\EasySms\Contracts\MessageInterface;
use Overtrue\EasySms\Contracts\PhoneNumberInterface;
use Overtrue\EasySms\Exceptions\GatewayErrorException;
use Overtrue\EasySms\Exceptions\InvalidArgumentException;
use Overtrue\EasySms\Support\Config;
use Overtrue\EasySms\Traits\HasHttpRequest;
/**
* Class ChuanglanGateway.
*
* @see https://www.chuanglan.com/document/6110e57909fd9600010209de/62b3dc1d272e290001af3e75
*/
class Chuanglanv1Gateway extends Gateway
{
use HasHttpRequest;
/**
* 国际短信
*/
const INT_URL = 'http://intapi.253.com/send/json';
/**
* URL模板
*/
const ENDPOINT_URL_TEMPLATE = 'https://smssh1.253.com/msg/%s/json';
/**
* 支持单发、群发短信
*/
const CHANNEL_NORMAL_CODE = 'v1/send';
/**
* 单号码对应单内容批量下发
*/
const CHANNEL_VARIABLE_CODE = 'variable';
/**
* @param PhoneNumberInterface $to
* @param MessageInterface $message
* @param Config $config
*
* @return array
*
* @throws GatewayErrorException
* @throws InvalidArgumentException
*/
public function send(PhoneNumberInterface $to, MessageInterface $message, Config $config)
{
$IDDCode = !empty($to->getIDDCode()) ? $to->getIDDCode() : 86;
$params = [
'account' => $config->get('account'),
'password' => $config->get('password'),
'report' => $config->get('needstatus') ?? false
];
if (86 != $IDDCode) {
$params['mobile'] = $to->getIDDCode() . $to->getNumber();
$params['account'] = $config->get('intel_account') ?: $config->get('account');
$params['password'] = $config->get('intel_password') ?: $config->get('password');
}
if (self::CHANNEL_VARIABLE_CODE == $this->getChannel($config, $IDDCode)) {
$params['params'] = $message->getData($this);
$params['msg'] = $this->wrapChannelContent($message->getTemplate($this), $config, $IDDCode);
} else {
$params['phone'] = $to->getNumber();
$params['msg'] = $this->wrapChannelContent($message->getContent($this), $config, $IDDCode);
}
$result = $this->postJson($this->buildEndpoint($config, $IDDCode), $params);
if (!isset($result['code']) || '0' != $result['code']) {
throw new GatewayErrorException(json_encode($result, JSON_UNESCAPED_UNICODE), isset($result['code']) ? $result['code'] : 0, $result);
}
return $result;
}
/**
* @param Config $config
* @param int $IDDCode
*
* @return string
*
* @throws InvalidArgumentException
*/
protected function buildEndpoint(Config $config, $IDDCode = 86)
{
$channel = $this->getChannel($config, $IDDCode);
if (self::INT_URL === $channel) {
return $channel;
}
return sprintf(self::ENDPOINT_URL_TEMPLATE, $channel);
}
/**
* @param Config $config
* @param int $IDDCode
*
* @return mixed
*
* @throws InvalidArgumentException
*/
protected function getChannel(Config $config, $IDDCode)
{
if (86 != $IDDCode) {
return self::INT_URL;
}
$channel = $config->get('channel', self::CHANNEL_NORMAL_CODE);
if (!in_array($channel, [self::CHANNEL_NORMAL_CODE, self::CHANNEL_VARIABLE_CODE])) {
throw new InvalidArgumentException('Invalid channel for ChuanglanGateway.');
}
return $channel;
}
/**
* @param string $content
* @param Config $config
* @param int $IDDCode
*
* @return string|string
*
* @throws InvalidArgumentException
*/
protected function wrapChannelContent($content, Config $config, $IDDCode)
{
return $content;
}
}
@@ -0,0 +1,50 @@
<?php
/*
* This file is part of the overtrue/easy-sms.
*
* (c) overtrue <i@overtrue.me>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Overtrue\EasySms\Gateways;
use Overtrue\EasySms\Contracts\MessageInterface;
use Overtrue\EasySms\Contracts\PhoneNumberInterface;
use Overtrue\EasySms\Support\Config;
/**
* Class ErrorlogGateway.
*/
class ErrorlogGateway extends Gateway
{
/**
* @param \Overtrue\EasySms\Contracts\PhoneNumberInterface $to
* @param \Overtrue\EasySms\Contracts\MessageInterface $message
* @param \Overtrue\EasySms\Support\Config $config
*
* @return array
*/
public function send(PhoneNumberInterface $to, MessageInterface $message, Config $config)
{
if (is_array($to)) {
$to = implode(',', $to);
}
$message = sprintf(
"[%s] to: %s | message: \"%s\" | template: \"%s\" | data: %s\n",
date('Y-m-d H:i:s'),
$to,
$message->getContent($this),
$message->getTemplate($this),
json_encode($message->getData($this))
);
$file = $this->config->get('file', ini_get('error_log'));
$status = error_log($message, 3, $file);
return compact('status', 'file');
}
}
@@ -0,0 +1,120 @@
<?php
/*
* This file is part of the overtrue/easy-sms.
*
* (c) overtrue <i@overtrue.me>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Overtrue\EasySms\Gateways;
use Overtrue\EasySms\Contracts\GatewayInterface;
use Overtrue\EasySms\Support\Config;
/**
* Class Gateway.
*/
abstract class Gateway implements GatewayInterface
{
const DEFAULT_TIMEOUT = 5.0;
/**
* @var \Overtrue\EasySms\Support\Config
*/
protected $config;
/**
* @var array
*/
protected $options;
/**
* @var float
*/
protected $timeout;
/**
* Gateway constructor.
*
* @param array $config
*/
public function __construct(array $config)
{
$this->config = new Config($config);
}
/**
* Return timeout.
*
* @return int|mixed
*/
public function getTimeout()
{
return $this->timeout ?: $this->config->get('timeout', self::DEFAULT_TIMEOUT);
}
/**
* Set timeout.
*
* @param int $timeout
*
* @return $this
*/
public function setTimeout($timeout)
{
$this->timeout = floatval($timeout);
return $this;
}
/**
* @return \Overtrue\EasySms\Support\Config
*/
public function getConfig()
{
return $this->config;
}
/**
* @param \Overtrue\EasySms\Support\Config $config
*
* @return $this
*/
public function setConfig(Config $config)
{
$this->config = $config;
return $this;
}
/**
* @param $options
*
* @return $this
*/
public function setGuzzleOptions($options)
{
$this->options = $options;
return $this;
}
/**
* @return array
*/
public function getGuzzleOptions()
{
return $this->options ?: $this->config->get('options', []);
}
/**
* {@inheritdoc}
*/
public function getName()
{
return \strtolower(str_replace([__NAMESPACE__.'\\', 'Gateway'], '', \get_class($this)));
}
}
@@ -0,0 +1,148 @@
<?php
/*
* This file is part of the overtrue/easy-sms.
*
* (c) overtrue <i@overtrue.me>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Overtrue\EasySms\Gateways;
use GuzzleHttp\Exception\RequestException;
use Overtrue\EasySms\Contracts\MessageInterface;
use Overtrue\EasySms\Contracts\PhoneNumberInterface;
use Overtrue\EasySms\Exceptions\GatewayErrorException;
use Overtrue\EasySms\Exceptions\InvalidArgumentException;
use Overtrue\EasySms\Support\Config;
use Overtrue\EasySms\Traits\HasHttpRequest;
class HuaweiGateway extends Gateway
{
use HasHttpRequest;
const ENDPOINT_HOST = 'https://api.rtc.huaweicloud.com:10443';
const ENDPOINT_URI = '/sms/batchSendSms/v1';
const SUCCESS_CODE = '000000';
/**
* 发送信息.
*
* @param PhoneNumberInterface $to
* @param MessageInterface $message
* @param Config $config
*
* @return array
*
* @throws GatewayErrorException
* @throws InvalidArgumentException
*/
public function send(PhoneNumberInterface $to, MessageInterface $message, Config $config)
{
$appKey = $config->get('app_key');
$appSecret = $config->get('app_secret');
$channels = $config->get('from');
$statusCallback = $config->get('callback', '');
$endpoint = $this->getEndpoint($config);
$headers = $this->getHeaders($appKey, $appSecret);
$templateId = $message->getTemplate($this);
$messageData = $message->getData($this);
// 短信签名通道号码
$from = 'default';
if (isset($messageData['from'])) {
$from = $messageData['from'];
unset($messageData['from']);
}
$channel = isset($channels[$from]) ? $channels[$from] : '';
if (empty($channel)) {
throw new InvalidArgumentException("From Channel [{$from}] Not Exist");
}
$params = [
'from' => $channel,
'to' => $to->getUniversalNumber(),
'templateId' => $templateId,
'templateParas' => json_encode($messageData),
'statusCallback' => $statusCallback,
];
try {
$result = $this->request('post', $endpoint, [
'headers' => $headers,
'form_params' => $params,
//为防止因HTTPS证书认证失败造成API调用失败,需要先忽略证书信任问题
'verify' => false,
]);
} catch (RequestException $e) {
$result = $this->unwrapResponse($e->getResponse());
}
if (self::SUCCESS_CODE != $result['code']) {
throw new GatewayErrorException($result['description'], ltrim($result['code'], 'E'), $result);
}
return $result;
}
/**
* 构造 Endpoint.
*
* @param Config $config
*
* @return string
*/
protected function getEndpoint(Config $config)
{
$endpoint = rtrim($config->get('endpoint', self::ENDPOINT_HOST), '/');
return $endpoint.self::ENDPOINT_URI;
}
/**
* 获取请求 Headers 参数.
*
* @param string $appKey
* @param string $appSecret
*
* @return array
*/
protected function getHeaders($appKey, $appSecret)
{
return [
'Content-Type' => 'application/x-www-form-urlencoded',
'Authorization' => 'WSSE realm="SDP",profile="UsernameToken",type="Appkey"',
'X-WSSE' => $this->buildWsseHeader($appKey, $appSecret),
];
}
/**
* 构造X-WSSE参数值
*
* @param string $appKey
* @param string $appSecret
*
* @return string
*/
protected function buildWsseHeader($appKey, $appSecret)
{
$now = date('Y-m-d\TH:i:s\Z');
$nonce = uniqid();
$passwordDigest = base64_encode(hash('sha256', ($nonce.$now.$appSecret)));
return sprintf(
'UsernameToken Username="%s",PasswordDigest="%s",Nonce="%s",Created="%s"',
$appKey,
$passwordDigest,
$nonce,
$now
);
}
}
@@ -0,0 +1,73 @@
<?php
/*
* This file is part of the overtrue/easy-sms.
*
* (c) overtrue <i@overtrue.me>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Overtrue\EasySms\Gateways;
use Overtrue\EasySms\Contracts\MessageInterface;
use Overtrue\EasySms\Contracts\PhoneNumberInterface;
use Overtrue\EasySms\Exceptions\GatewayErrorException;
use Overtrue\EasySms\Support\Config;
use Overtrue\EasySms\Traits\HasHttpRequest;
/**
* Class HuaxinGateway.
*
* @see http://www.ipyy.com/help/
*/
class HuaxinGateway extends Gateway
{
use HasHttpRequest;
const ENDPOINT_TEMPLATE = 'http://%s/smsJson.aspx';
/**
* @param \Overtrue\EasySms\Contracts\PhoneNumberInterface $to
* @param \Overtrue\EasySms\Contracts\MessageInterface $message
* @param \Overtrue\EasySms\Support\Config $config
*
* @return array
*
* @throws \Overtrue\EasySms\Exceptions\GatewayErrorException ;
*/
public function send(PhoneNumberInterface $to, MessageInterface $message, Config $config)
{
$endpoint = $this->buildEndpoint($config->get('ip'));
$result = $this->post($endpoint, [
'userid' => $config->get('user_id'),
'account' => $config->get('account'),
'password' => $config->get('password'),
'mobile' => $to->getNumber(),
'content' => $message->getContent($this),
'sendTime' => '',
'action' => 'send',
'extno' => $config->get('ext_no'),
]);
if ('Success' !== $result['returnstatus']) {
throw new GatewayErrorException($result['message'], 400, $result);
}
return $result;
}
/**
* Build endpoint url.
*
* @param string $ip
*
* @return string
*/
protected function buildEndpoint($ip)
{
return sprintf(self::ENDPOINT_TEMPLATE, $ip);
}
}
@@ -0,0 +1,77 @@
<?php
/*
* This file is part of the overtrue/easy-sms.
*
* (c) overtrue <i@overtrue.me>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Overtrue\EasySms\Gateways;
use Overtrue\EasySms\Contracts\MessageInterface;
use Overtrue\EasySms\Contracts\PhoneNumberInterface;
use Overtrue\EasySms\Exceptions\GatewayErrorException;
use Overtrue\EasySms\Support\Config;
use Overtrue\EasySms\Traits\HasHttpRequest;
/**
* Class HuyiGateway.
*
* @see http://www.ihuyi.com/api/sms.html
*/
class HuyiGateway extends Gateway
{
use HasHttpRequest;
const ENDPOINT_URL = 'http://106.ihuyi.com/webservice/sms.php?method=Submit';
const ENDPOINT_FORMAT = 'json';
const SUCCESS_CODE = 2;
/**
* @param \Overtrue\EasySms\Contracts\PhoneNumberInterface $to
* @param \Overtrue\EasySms\Contracts\MessageInterface $message
* @param \Overtrue\EasySms\Support\Config $config
*
* @return array
*
* @throws \Overtrue\EasySms\Exceptions\GatewayErrorException ;
*/
public function send(PhoneNumberInterface $to, MessageInterface $message, Config $config)
{
$params = [
'account' => $config->get('api_id'),
'mobile' => $to->getIDDCode() ? \sprintf('%s %s', $to->getIDDCode(), $to->getNumber()) : $to->getNumber(),
'content' => $message->getContent($this),
'time' => time(),
'format' => self::ENDPOINT_FORMAT,
'sign' => $config->get('signature'),
];
$params['password'] = $this->generateSign($params);
$result = $this->post(self::ENDPOINT_URL, $params);
if (self::SUCCESS_CODE != $result['code']) {
throw new GatewayErrorException($result['msg'], $result['code'], $result);
}
return $result;
}
/**
* Generate Sign.
*
* @param array $params
*
* @return string
*/
protected function generateSign($params)
{
return md5($params['account'].$this->config->get('api_key').$params['mobile'].$params['content'].$params['time']);
}
}
@@ -0,0 +1,76 @@
<?php
/*
* This file is part of the overtrue/easy-sms.
*
* (c) overtrue <i@overtrue.me>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Overtrue\EasySms\Gateways;
use Overtrue\EasySms\Contracts\MessageInterface;
use Overtrue\EasySms\Contracts\PhoneNumberInterface;
use Overtrue\EasySms\Exceptions\GatewayErrorException;
use Overtrue\EasySms\Support\Config;
use Overtrue\EasySms\Traits\HasHttpRequest;
/**
* Class JuheGateway.
*
* @see https://www.juhe.cn/docs/api/id/54
*/
class JuheGateway extends Gateway
{
use HasHttpRequest;
const ENDPOINT_URL = 'http://v.juhe.cn/sms/send';
const ENDPOINT_FORMAT = 'json';
/**
* @param \Overtrue\EasySms\Contracts\PhoneNumberInterface $to
* @param \Overtrue\EasySms\Contracts\MessageInterface $message
* @param \Overtrue\EasySms\Support\Config $config
*
* @return array
*
* @throws \Overtrue\EasySms\Exceptions\GatewayErrorException ;
*/
public function send(PhoneNumberInterface $to, MessageInterface $message, Config $config)
{
$params = [
'mobile' => $to->getNumber(),
'tpl_id' => $message->getTemplate($this),
'tpl_value' => $this->formatTemplateVars($message->getData($this)),
'dtype' => self::ENDPOINT_FORMAT,
'key' => $config->get('app_key'),
];
$result = $this->get(self::ENDPOINT_URL, $params);
if ($result['error_code']) {
throw new GatewayErrorException($result['reason'], $result['error_code'], $result);
}
return $result;
}
/**
* @param array $vars
*
* @return string
*/
protected function formatTemplateVars(array $vars)
{
$formatted = [];
foreach ($vars as $key => $value) {
$formatted[sprintf('#%s#', trim($key, '#'))] = $value;
}
return urldecode(http_build_query($formatted));
}
}
@@ -0,0 +1,61 @@
<?php
/*
* This file is part of the overtrue/easy-sms.
*
* (c) overtrue <i@overtrue.me>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Overtrue\EasySms\Gateways;
use Overtrue\EasySms\Contracts\MessageInterface;
use Overtrue\EasySms\Contracts\PhoneNumberInterface;
use Overtrue\EasySms\Exceptions\GatewayErrorException;
use Overtrue\EasySms\Support\Config;
use Overtrue\EasySms\Traits\HasHttpRequest;
/**
* Class KingttoGateWay.
*
* @see http://www.kingtto.cn/
*/
class KingttoGateway extends Gateway
{
use HasHttpRequest;
const ENDPOINT_URL = 'http://101.201.41.194:9999/sms.aspx';
const ENDPOINT_METHOD = 'send';
/**
* @param PhoneNumberInterface $to
* @param MessageInterface $message
* @param Config $config
*
* @return \Psr\Http\Message\ResponseInterface|array|string
*
* @throws GatewayErrorException
*/
public function send(PhoneNumberInterface $to, MessageInterface $message, Config $config)
{
$params = [
'action' => self::ENDPOINT_METHOD,
'userid' => $config->get('userid'),
'account' => $config->get('account'),
'password' => $config->get('password'),
'mobile' => $to->getNumber(),
'content' => $message->getContent(),
];
$result = $this->post(self::ENDPOINT_URL, $params);
if ('Success' != $result['returnstatus']) {
throw new GatewayErrorException($result['message'], $result['remainpoint'], $result);
}
return $result;
}
}
@@ -0,0 +1,74 @@
<?php
/*
* This file is part of the overtrue/easy-sms.
*
* (c) overtrue <i@overtrue.me>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Overtrue\EasySms\Gateways;
use Overtrue\EasySms\Contracts\MessageInterface;
use Overtrue\EasySms\Contracts\PhoneNumberInterface;
use Overtrue\EasySms\Exceptions\GatewayErrorException;
use Overtrue\EasySms\Support\Config;
use Overtrue\EasySms\Traits\HasHttpRequest;
/**
* Class LuosimaoGateway.
*
* @see https://luosimao.com/docs/api/
*/
class LuosimaoGateway extends Gateway
{
use HasHttpRequest;
const ENDPOINT_TEMPLATE = 'https://%s.luosimao.com/%s/%s.%s';
const ENDPOINT_VERSION = 'v1';
const ENDPOINT_FORMAT = 'json';
/**
* @param \Overtrue\EasySms\Contracts\PhoneNumberInterface $to
* @param \Overtrue\EasySms\Contracts\MessageInterface $message
* @param \Overtrue\EasySms\Support\Config $config
*
* @return array
*
* @throws \Overtrue\EasySms\Exceptions\GatewayErrorException ;
*/
public function send(PhoneNumberInterface $to, MessageInterface $message, Config $config)
{
$endpoint = $this->buildEndpoint('sms-api', 'send');
$result = $this->post($endpoint, [
'mobile' => $to->getNumber(),
'message' => $message->getContent($this),
], [
'Authorization' => 'Basic '.base64_encode('api:key-'.$config->get('api_key')),
]);
if ($result['error']) {
throw new GatewayErrorException($result['msg'], $result['error'], $result);
}
return $result;
}
/**
* Build endpoint url.
*
* @param string $type
* @param string $function
*
* @return string
*/
protected function buildEndpoint($type, $function)
{
return sprintf(self::ENDPOINT_TEMPLATE, $type, self::ENDPOINT_VERSION, $function, self::ENDPOINT_FORMAT);
}
}
@@ -0,0 +1,72 @@
<?php
/*
* This file is part of the overtrue/easy-sms.
*
* (c) overtrue <i@overtrue.me>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Overtrue\EasySms\Gateways;
use Overtrue\EasySms\Contracts\MessageInterface;
use Overtrue\EasySms\Contracts\PhoneNumberInterface;
use Overtrue\EasySms\Exceptions\GatewayErrorException;
use Overtrue\EasySms\Support\Config;
use Overtrue\EasySms\Traits\HasHttpRequest;
/**
* Class MaapGateway.
*
* @see https://maap.wo.cn/
*/
class MaapGateway extends Gateway
{
use HasHttpRequest;
const ENDPOINT_URL = 'http://rcsapi.wo.cn:8000/umcinterface/sendtempletmsg';
/**
* Send message.
* @param \Overtrue\EasySms\Contracts\PhoneNumberInterface $to
* @param \Overtrue\EasySms\Contracts\MessageInterface $message
* @param \Overtrue\EasySms\Support\Config $config
*
* @return array
*
* @throws \Overtrue\EasySms\Exceptions\GatewayErrorException
*/
public function send(PhoneNumberInterface $to, MessageInterface $message, Config $config)
{
$params = [
'cpcode' => $config->get('cpcode'),
'msg' => implode(',', $message->getData($this)),
'mobiles' => $to->getNumber(),
'excode' => $config->get('excode', ''),
'templetid' => $message->getTemplate($this),
];
$params['sign'] = $this->generateSign($params, $config->get('key'));
$result = $this->postJson(self::ENDPOINT_URL, $params);
if (0 != $result['resultcode']) {
throw new GatewayErrorException($result['resultmsg'], $result['resultcode'], $result);
}
return $result;
}
/**
* Generate Sign.
*
* @param array $params
* @param string $key 签名Key
* @return string
*/
protected function generateSign($params, $key)
{
return md5($params['cpcode'] . $params['msg'] . $params['mobiles'] . $params['excode'] . $params['templetid'] . $key);
}
}
@@ -0,0 +1,99 @@
<?php
/*
* This file is part of the overtrue/easy-sms.
*
* (c) overtrue <i@overtrue.me>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Overtrue\EasySms\Gateways;
use Overtrue\EasySms\Contracts\MessageInterface;
use Overtrue\EasySms\Contracts\PhoneNumberInterface;
use Overtrue\EasySms\Exceptions\GatewayErrorException;
use Overtrue\EasySms\Support\Config;
use Overtrue\EasySms\Traits\HasHttpRequest;
/**
* Class ModuyunGateway.
*
* @see https://www.moduyun.com/doc/index.html#10002
*/
class ModuyunGateway extends Gateway
{
use HasHttpRequest;
const ENDPOINT_URL = 'https://live.moduyun.com/sms/v2/sendsinglesms';
/**
* @param \Overtrue\EasySms\Contracts\PhoneNumberInterface $to
* @param \Overtrue\EasySms\Contracts\MessageInterface $message
* @param \Overtrue\EasySms\Support\Config $config
*
* @return array
*
* @throws \Overtrue\EasySms\Exceptions\GatewayErrorException ;
*/
public function send(PhoneNumberInterface $to, MessageInterface $message, Config $config)
{
$urlParams = [
'accesskey' => $config->get('accesskey'),
'random' => rand(100000, 999999),
];
$params = [
'tel' => [
'mobile' => $to->getNumber(),
'nationcode' => $to->getIDDCode() ?: '86',
],
'signId' => $config->get('signId', ''),
'templateId' => $message->getTemplate($this),
'time' => time(),
'type' => $config->get('type', 0),
'params' => array_values($message->getData($this)),
'ext' => '',
'extend' => '',
];
$params['sig'] = $this->generateSign($params, $urlParams['random']);
$result = $this->postJson($this->getEndpointUrl($urlParams), $params);
$result = is_string($result) ? json_decode($result, true) : $result;
if (0 != $result['result']) {
throw new GatewayErrorException($result['errmsg'], $result['result'], $result);
}
return $result;
}
/**
* @param array $params
*
* @return string
*/
protected function getEndpointUrl($params)
{
return self::ENDPOINT_URL . '?' . http_build_query($params);
}
/**
* Generate Sign.
*
* @param array $params
* @param string $random
*
* @return string
*/
protected function generateSign($params, $random)
{
return hash('sha256', sprintf(
'secretkey=%s&random=%d&time=%d&mobile=%s',
$this->config->get('secretkey'),
$random,
$params['time'],
$params['tel']['mobile']
));
}
}
@@ -0,0 +1,38 @@
<?php
namespace Overtrue\EasySms\Gateways;
use Overtrue\EasySms\Contracts\MessageInterface;
use Overtrue\EasySms\Contracts\PhoneNumberInterface;
use Overtrue\EasySms\Exceptions\GatewayErrorException;
use Overtrue\EasySms\Support\Config;
use Overtrue\EasySms\Traits\HasHttpRequest;
class NowcnGateway extends Gateway
{
use HasHttpRequest;
const ENDPOINT_URL = 'http://ad1200.now.net.cn:2003/sms/sendSMS';
const SUCCESS_CODE = 0;
public function send(PhoneNumberInterface $to, MessageInterface $message, Config $config)
{
if (!$config->get('key')) {
throw new GatewayErrorException("key not found", -2, []);
}
$params = [
'mobile' => $to->getNumber(),
'content' => $message->getContent($this),
'userId' => $config->get('key'),
'password' => $config->get('secret'),
'apiType' => $config->get('api_type'),
];
$result = $this->get(self::ENDPOINT_URL, $params);
$result = is_string($result) ? json_decode($result, true) : $result;
if (self::SUCCESS_CODE != $result['code']) {
throw new GatewayErrorException($result['msg'], $result['code'], $result);
}
return $result;
}
}
@@ -0,0 +1,137 @@
<?php
/*
* This file is part of the overtrue/easy-sms.
*
* (c) overtrue <i@overtrue.me>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Overtrue\EasySms\Gateways;
use Overtrue\EasySms\Contracts\MessageInterface;
use Overtrue\EasySms\Contracts\PhoneNumberInterface;
use Overtrue\EasySms\Exceptions\GatewayErrorException;
use Overtrue\EasySms\Support\Config;
use Overtrue\EasySms\Traits\HasHttpRequest;
/**
* Class QcloudGateway.
*
* @see https://cloud.tencent.com/document/api/382/55981
*/
class QcloudGateway extends Gateway
{
use HasHttpRequest;
const ENDPOINT_URL = 'https://sms.tencentcloudapi.com';
const ENDPOINT_HOST = 'sms.tencentcloudapi.com';
const ENDPOINT_SERVICE = 'sms';
const ENDPOINT_METHOD = 'SendSms';
const ENDPOINT_VERSION = '2021-01-11';
const ENDPOINT_REGION = 'ap-guangzhou';
const ENDPOINT_FORMAT = 'json';
/**
* @param \Overtrue\EasySms\Contracts\PhoneNumberInterface $to
* @param \Overtrue\EasySms\Contracts\MessageInterface $message
* @param \Overtrue\EasySms\Support\Config $config
*
* @return array
*
* @throws \Overtrue\EasySms\Exceptions\GatewayErrorException ;
*/
public function send(PhoneNumberInterface $to, MessageInterface $message, Config $config)
{
$data = $message->getData($this);
$signName = !empty($data['sign_name']) ? $data['sign_name'] : $config->get('sign_name', '');
unset($data['sign_name']);
$phone = !\is_null($to->getIDDCode()) ? strval($to->getUniversalNumber()) : $to->getNumber();
$params = [
'PhoneNumberSet' => [
$phone
],
'SmsSdkAppId' => $this->config->get('sdk_app_id'),
'SignName' => $signName,
'TemplateId' => (string) $message->getTemplate($this),
'TemplateParamSet' => array_map('strval', array_values($data)),
];
$time = time();
$result = $this->request('post', self::ENDPOINT_URL, [
'headers' => [
'Authorization' => $this->generateSign($params, $time),
'Host' => self::ENDPOINT_HOST,
'Content-Type' => 'application/json; charset=utf-8',
'X-TC-Action' => self::ENDPOINT_METHOD,
'X-TC-Region' => $this->config->get('region', self::ENDPOINT_REGION),
'X-TC-Timestamp' => $time,
'X-TC-Version' => self::ENDPOINT_VERSION,
],
'json' => $params,
]);
if (!empty($result['Response']['Error']['Code'])) {
throw new GatewayErrorException($result['Response']['Error']['Message'], 400, $result);
}
if (!empty($result['Response']['SendStatusSet'])) {
foreach ($result['Response']['SendStatusSet'] as $group) {
if ($group['Code'] != 'Ok') {
throw new GatewayErrorException($group['Message'], 400, $result);
}
}
}
return $result;
}
/**
* Generate Sign.
*
* @param array $params
*
* @return string
*/
protected function generateSign($params, $timestamp)
{
$date = gmdate("Y-m-d", $timestamp);
$secretKey = $this->config->get('secret_key');
$secretId = $this->config->get('secret_id');
$canonicalRequest = 'POST'."\n".
'/'."\n".
'' ."\n".
'content-type:application/json; charset=utf-8'."\n".
'host:' . self::ENDPOINT_HOST."\n"."\n".
'content-type;host'."\n".
hash("SHA256", json_encode($params));
$stringToSign =
'TC3-HMAC-SHA256'."\n".
$timestamp."\n".
$date . '/'. self::ENDPOINT_SERVICE .'/tc3_request'."\n".
hash("SHA256", $canonicalRequest);
$secretDate = hash_hmac("SHA256", $date, "TC3".$secretKey, true);
$secretService = hash_hmac("SHA256", self::ENDPOINT_SERVICE, $secretDate, true);
$secretSigning = hash_hmac("SHA256", "tc3_request", $secretService, true);
$signature = hash_hmac("SHA256", $stringToSign, $secretSigning);
return 'TC3-HMAC-SHA256'
." Credential=". $secretId ."/". $date . '/'. self::ENDPOINT_SERVICE .'/tc3_request'
.", SignedHeaders=content-type;host, Signature=".$signature;
}
}
@@ -0,0 +1,148 @@
<?php
/*
* This file is part of the overtrue/easy-sms.
*
* (c) overtrue <i@overtrue.me>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Overtrue\EasySms\Gateways;
use Overtrue\EasySms\Contracts\MessageInterface;
use Overtrue\EasySms\Contracts\PhoneNumberInterface;
use Overtrue\EasySms\Exceptions\GatewayErrorException;
use Overtrue\EasySms\Support\Config;
use Overtrue\EasySms\Traits\HasHttpRequest;
/**
* Class QiniuGateway.
*
* @see https://developer.qiniu.com/sms/api/5897/sms-api-send-message
*/
class QiniuGateway extends Gateway
{
use HasHttpRequest;
const ENDPOINT_TEMPLATE = 'https://%s.qiniuapi.com/%s/%s';
const ENDPOINT_VERSION = 'v1';
/**
* @param \Overtrue\EasySms\Contracts\PhoneNumberInterface $to
* @param \Overtrue\EasySms\Contracts\MessageInterface $message
* @param \Overtrue\EasySms\Support\Config $config
*
* @return array
*
* @throws \Overtrue\EasySms\Exceptions\GatewayErrorException ;
*/
public function send(PhoneNumberInterface $to, MessageInterface $message, Config $config)
{
$endpoint = $this->buildEndpoint('sms', 'message/single');
$data = $message->getData($this);
$params = [
'template_id' => $message->getTemplate($this),
'mobile' => $to->getNumber(),
];
if (!empty($data)) {
$params['parameters'] = $data;
}
$headers = [
'Content-Type' => 'application/json',
];
$headers['Authorization'] = $this->generateSign($endpoint, 'POST', json_encode($params), $headers['Content-Type'], $config);
$result = $this->postJson($endpoint, $params, $headers);
if (isset($result['error'])) {
throw new GatewayErrorException($result['message'], $result['error'], $result);
}
return $result;
}
/**
* Build endpoint url.
*
* @param string $type
* @param string $function
*
* @return string
*/
protected function buildEndpoint($type, $function)
{
return sprintf(self::ENDPOINT_TEMPLATE, $type, self::ENDPOINT_VERSION, $function);
}
/**
* Build endpoint url.
*
* @param string $url
* @param string $method
* @param string $body
* @param string $contentType
* @param Config $config
*
* @return string
*/
protected function generateSign($url, $method, $body, $contentType, Config $config)
{
$urlItems = parse_url($url);
$host = $urlItems['host'];
if (isset($urlItems['port'])) {
$port = $urlItems['port'];
} else {
$port = '';
}
$path = $urlItems['path'];
if (isset($urlItems['query'])) {
$query = $urlItems['query'];
} else {
$query = '';
}
//write request uri
$toSignStr = $method.' '.$path;
if (!empty($query)) {
$toSignStr .= '?'.$query;
}
//write host and port
$toSignStr .= "\nHost: ".$host;
if (!empty($port)) {
$toSignStr .= ':'.$port;
}
//write content type
if (!empty($contentType)) {
$toSignStr .= "\nContent-Type: ".$contentType;
}
$toSignStr .= "\n\n";
//write body
if (!empty($body)) {
$toSignStr .= $body;
}
$hmac = hash_hmac('sha1', $toSignStr, $config->get('secret_key'), true);
return 'Qiniu '.$config->get('access_key').':'.$this->base64UrlSafeEncode($hmac);
}
/**
* @param string $data
*
* @return string
*/
protected function base64UrlSafeEncode($data)
{
$find = array('+', '/');
$replace = array('-', '_');
return str_replace($find, $replace, base64_encode($data));
}
}
@@ -0,0 +1,134 @@
<?php
/*
* This file is part of the overtrue/easy-sms.
*
* (c) overtrue <i@overtrue.me>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Overtrue\EasySms\Gateways;
use GuzzleHttp\Exception\ClientException;
use Overtrue\EasySms\Contracts\MessageInterface;
use Overtrue\EasySms\Contracts\PhoneNumberInterface;
use Overtrue\EasySms\Exceptions\GatewayErrorException;
use Overtrue\EasySms\Support\Config;
use Overtrue\EasySms\Traits\HasHttpRequest;
/**
* Class RongcloudGateway.
*
* @author Darren Gao <realgaodacheng@gmail.com>
*
* @see http://www.rongcloud.cn/docs/sms_service.html#send_sms_code
*/
class RongcloudGateway extends Gateway
{
use HasHttpRequest;
const ENDPOINT_TEMPLATE = 'http://api.sms.ronghub.com/%s.%s';
const ENDPOINT_ACTION = 'sendCode';
const ENDPOINT_FORMAT = 'json';
const ENDPOINT_REGION = '86'; // 中国区,目前只支持此国别
const SUCCESS_CODE = 200;
/**
* @param \Overtrue\EasySms\Contracts\PhoneNumberInterface $to
* @param \Overtrue\EasySms\Contracts\MessageInterface $message
* @param \Overtrue\EasySms\Support\Config $config
*
* @return array
*
* @throws \Overtrue\EasySms\Exceptions\GatewayErrorException ;
*/
public function send(PhoneNumberInterface $to, MessageInterface $message, Config $config)
{
$data = $message->getData();
$action = array_key_exists('action', $data) ? $data['action'] : self::ENDPOINT_ACTION;
$endpoint = $this->buildEndpoint($action);
$headers = [
'Nonce' => uniqid(),
'App-Key' => $config->get('app_key'),
'Timestamp' => time(),
];
$headers['Signature'] = $this->generateSign($headers, $config);
switch ($action) {
case 'sendCode':
$params = [
'mobile' => $to->getNumber(),
'region' => self::ENDPOINT_REGION,
'templateId' => $message->getTemplate($this),
];
break;
case 'verifyCode':
if (!array_key_exists('code', $data)
or !array_key_exists('sessionId', $data)) {
throw new GatewayErrorException('"code" or "sessionId" is not set', 0);
}
$params = [
'code' => $data['code'],
'sessionId' => $data['sessionId'],
];
break;
case 'sendNotify':
$params = [
'mobile' => $to->getNumber(),
'region' => self::ENDPOINT_REGION,
'templateId' => $message->getTemplate($this),
];
$params = array_merge($params, $data);
break;
default:
throw new GatewayErrorException(sprintf('action: %s not supported', $action));
}
try {
$result = $this->post($endpoint, $params, $headers);
if (self::SUCCESS_CODE !== $result['code']) {
throw new GatewayErrorException($result['errorMessage'], $result['code'], $result);
}
} catch (ClientException $e) {
throw new GatewayErrorException($e->getMessage(), $e->getCode());
}
return $result;
}
/**
* Generate Sign.
*
* @param array $params
* @param \Overtrue\EasySms\Support\Config $config
*
* @return string
*/
protected function generateSign($params, Config $config)
{
return sha1(sprintf('%s%s%s', $config->get('app_secret'), $params['Nonce'], $params['Timestamp']));
}
/**
* Build endpoint url.
*
* @param string $action
*
* @return string
*/
protected function buildEndpoint($action)
{
return sprintf(self::ENDPOINT_TEMPLATE, $action, self::ENDPOINT_FORMAT);
}
}
@@ -0,0 +1,69 @@
<?php
/*
* This file is part of the overtrue/easy-sms.
*
* (c) overtrue <i@overtrue.me>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Overtrue\EasySms\Gateways;
use Overtrue\EasySms\Contracts\MessageInterface;
use Overtrue\EasySms\Contracts\PhoneNumberInterface;
use Overtrue\EasySms\Exceptions\GatewayErrorException;
use Overtrue\EasySms\Support\Config;
use Overtrue\EasySms\Traits\HasHttpRequest;
/**
* Class RongheyunGateway.
*
* @see https://doc.zthysms.com/web/#/1?page_id=13
*/
class RongheyunGateway extends Gateway
{
use HasHttpRequest;
const ENDPOINT_URL = 'https://api.mix2.zthysms.com/v2/sendSmsTp';
/**
* @param \Overtrue\EasySms\Contracts\PhoneNumberInterface $to
* @param \Overtrue\EasySms\Contracts\MessageInterface $message
* @param \Overtrue\EasySms\Support\Config $config
*
* @return array
*
* @throws \Overtrue\EasySms\Exceptions\GatewayErrorException ;
*/
public function send(PhoneNumberInterface $to, MessageInterface $message, Config $config)
{
$tKey = time();
$password = md5(md5($config->get('password')) . $tKey);
$params = [
'username' => $config->get('username', ''),
'password' => $password,
'tKey' => $tKey,
'signature' => $config->get('signature', ''),
'tpId' => $message->getTemplate($this),
'ext' => '',
'extend' => '',
'records' => [
'mobile' => $to->getNumber(),
'tpContent' => $message->getData($this),
],
];
$result = $this->postJson(
self::ENDPOINT_URL,
$params,
['Content-Type' => 'application/json; charset="UTF-8"']
);
if (200 != $result['code']) {
throw new GatewayErrorException($result['msg'], $result['code'], $result);
}
return $result;
}
}
@@ -0,0 +1,95 @@
<?php
/*
* This file is part of the overtrue/easy-sms.
*
* (c) overtrue <i@overtrue.me>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Overtrue\EasySms\Gateways;
use Overtrue\EasySms\Contracts\MessageInterface;
use Overtrue\EasySms\Contracts\PhoneNumberInterface;
use Overtrue\EasySms\Exceptions\GatewayErrorException;
use Overtrue\EasySms\Support\Config;
use Overtrue\EasySms\Traits\HasHttpRequest;
/**
* Class SendcloudGateway.
*
* @see http://sendcloud.sohu.com/doc/sms/
*/
class SendcloudGateway extends Gateway
{
use HasHttpRequest;
const ENDPOINT_TEMPLATE = 'http://www.sendcloud.net/smsapi/%s';
/**
* Send a short message.
*
* @param \Overtrue\EasySms\Contracts\PhoneNumberInterface $to
* @param \Overtrue\EasySms\Contracts\MessageInterface $message
* @param \Overtrue\EasySms\Support\Config $config
*
* @return array
*
* @throws \Overtrue\EasySms\Exceptions\GatewayErrorException
*/
public function send(PhoneNumberInterface $to, MessageInterface $message, Config $config)
{
$params = [
'smsUser' => $config->get('sms_user'),
'templateId' => $message->getTemplate($this),
'msgType' => $to->getIDDCode() ? 2 : 0,
'phone' => $to->getZeroPrefixedNumber(),
'vars' => $this->formatTemplateVars($message->getData($this)),
];
if ($config->get('timestamp', false)) {
$params['timestamp'] = time() * 1000;
}
$params['signature'] = $this->sign($params, $config->get('sms_key'));
$result = $this->post(sprintf(self::ENDPOINT_TEMPLATE, 'send'), $params);
if (!$result['result']) {
throw new GatewayErrorException($result['message'], $result['statusCode'], $result);
}
return $result;
}
/**
* @param array $vars
*
* @return string
*/
protected function formatTemplateVars(array $vars)
{
$formatted = [];
foreach ($vars as $key => $value) {
$formatted[sprintf('%%%s%%', trim($key, '%'))] = $value;
}
return json_encode($formatted, JSON_FORCE_OBJECT);
}
/**
* @param array $params
* @param string $key
*
* @return string
*/
protected function sign($params, $key)
{
ksort($params);
return md5(sprintf('%s&%s&%s', $key, urldecode(http_build_query($params)), $key));
}
}
@@ -0,0 +1,77 @@
<?php
/*
* This file is part of the overtrue/easy-sms.
*
* (c) overtrue <i@overtrue.me>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Overtrue\EasySms\Gateways;
use Overtrue\EasySms\Contracts\MessageInterface;
use Overtrue\EasySms\Contracts\PhoneNumberInterface;
use Overtrue\EasySms\Exceptions\GatewayErrorException;
use Overtrue\EasySms\Support\Config;
use Overtrue\EasySms\Traits\HasHttpRequest;
/**
* Class SmsbaoGateway
* @author iwindy <203962638@qq.com>
* @see http://www.smsbao.com/openapi/
*/
class SmsbaoGateway extends Gateway
{
use HasHttpRequest;
const ENDPOINT_URL = 'http://api.smsbao.com/%s';
const SUCCESS_CODE = '0';
protected $errorStatuses = [
'0' => '短信发送成功',
'-1' => '参数不全',
'-2' => '服务器空间不支持,请确认支持curl或者fsocket,联系您的空间商解决或者更换空间!',
'30' => '密码错误',
'40' => '账号不存在',
'41' => '余额不足',
'42' => '帐户已过期',
'43' => 'IP地址限制',
'50' => '内容含有敏感词'
];
public function send(PhoneNumberInterface $to, MessageInterface $message, Config $config)
{
$data = $message->getContent($this);
if (is_null($to->getIDDCode()) || $to->getIDDCode() == '86') {
$number = $to->getNumber();
$action = 'sms';
} else {
$number = $to->getUniversalNumber();
$action = 'wsms';
}
$params = [
'u' => $config->get('user'),
'p' => md5($config->get('password')),
'm' => $number,
'c' => $data
];
$result = $this->get($this->buildEndpoint($action), $params);
if ($result !== self::SUCCESS_CODE) {
throw new GatewayErrorException($this->errorStatuses[$result], $result);
}
return $result;
}
protected function buildEndpoint($type)
{
return sprintf(self::ENDPOINT_URL, $type);
}
}
@@ -0,0 +1,100 @@
<?php
/*
* This file is part of the overtrue/easy-sms.
*
* (c) overtrue <i@overtrue.me>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Overtrue\EasySms\Gateways;
use Overtrue\EasySms\Contracts\MessageInterface;
use Overtrue\EasySms\Contracts\PhoneNumberInterface;
use Overtrue\EasySms\Exceptions\GatewayErrorException;
use Overtrue\EasySms\Support\Config;
use Overtrue\EasySms\Traits\HasHttpRequest;
/**
* Class SubmailGateway.
*
* @see https://www.mysubmail.com/chs/documents/developer/index
*/
class SubmailGateway extends Gateway
{
use HasHttpRequest;
const ENDPOINT_TEMPLATE = 'https://api.mysubmail.com/%s.%s';
const ENDPOINT_FORMAT = 'json';
/**
* @param \Overtrue\EasySms\Contracts\PhoneNumberInterface $to
* @param \Overtrue\EasySms\Contracts\MessageInterface $message
* @param \Overtrue\EasySms\Support\Config $config
*
* @return array
*
* @throws \Overtrue\EasySms\Exceptions\GatewayErrorException ;
*/
public function send(PhoneNumberInterface $to, MessageInterface $message, Config $config)
{
$isContent = !!$message->getContent($this);
if ($isContent) {
$endpoint = $this->buildEndpoint($this->inChineseMainland($to) ? 'sms/send' : 'internationalsms/send');
$params = [
'appid' => $config->get('app_id'),
'signature' => $config->get('app_key'),
'content' => $message->getContent($this),
'to' => $to->getUniversalNumber(),
];
} else {
$endpoint = $this->buildEndpoint($this->inChineseMainland($to) ? 'message/xsend' : 'internationalsms/xsend');
$data = $message->getData($this);
$template_code = $message->getTemplate($this);
$params = [
'appid' => $config->get('app_id'),
'signature' => $config->get('app_key'),
'project' => !empty($template_code) ? $template_code : (!empty($data['project']) ? $data['project'] : $config->get('project')),
'to' => $to->getUniversalNumber(),
'vars' => json_encode($data, JSON_FORCE_OBJECT),
];
}
$result = $this->post($endpoint, $params);
if ('success' != $result['status']) {
throw new GatewayErrorException($result['msg'], $result['code'], $result);
}
return $result;
}
/**
* Build endpoint url.
*
* @param string $function
*
* @return string
*/
protected function buildEndpoint($function)
{
return sprintf(self::ENDPOINT_TEMPLATE, $function, self::ENDPOINT_FORMAT);
}
/**
* Check if the phone number belongs to chinese mainland.
*
* @param \Overtrue\EasySms\Contracts\PhoneNumberInterface $to
*
* @return bool
*/
protected function inChineseMainland($to)
{
$code = $to->getIDDCode();
return empty($code) || 86 === $code;
}
}
@@ -0,0 +1,84 @@
<?php
/*
* This file is part of the overtrue/easy-sms.
*
* (c) overtrue <i@overtrue.me>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Overtrue\EasySms\Gateways;
use Overtrue\EasySms\Contracts\MessageInterface;
use Overtrue\EasySms\Contracts\PhoneNumberInterface;
use Overtrue\EasySms\Exceptions\GatewayErrorException;
use Overtrue\EasySms\Support\Config;
use Overtrue\EasySms\Traits\HasHttpRequest;
/**
* Class TianyiwuxianGateway.
*
* @author Darren Gao <realgaodacheng@gmail.com>
*/
class TianyiwuxianGateway extends Gateway
{
use HasHttpRequest;
const ENDPOINT_TEMPLATE = 'http://jk.106api.cn/sms%s.aspx';
const ENDPOINT_ENCODE = 'UTF8';
const ENDPOINT_TYPE = 'send';
const ENDPOINT_FORMAT = 'json';
const SUCCESS_STATUS = 'success';
const SUCCESS_CODE = '0';
/**
* @param \Overtrue\EasySms\Contracts\PhoneNumberInterface $to
* @param \Overtrue\EasySms\Contracts\MessageInterface $message
* @param \Overtrue\EasySms\Support\Config $config
*
* @return array
*
* @throws \Overtrue\EasySms\Exceptions\GatewayErrorException ;
*/
public function send(PhoneNumberInterface $to, MessageInterface $message, Config $config)
{
$endpoint = $this->buildEndpoint();
$params = [
'gwid' => $config->get('gwid'),
'type' => self::ENDPOINT_TYPE,
'rece' => self::ENDPOINT_FORMAT,
'mobile' => $to->getNumber(),
'message' => $message->getContent($this),
'username' => $config->get('username'),
'password' => strtoupper(md5($config->get('password'))),
];
$result = $this->post($endpoint, $params);
$result = json_decode($result, true);
if (self::SUCCESS_STATUS !== $result['returnstatus'] || self::SUCCESS_CODE !== $result['code']) {
throw new GatewayErrorException($result['remark'], $result['code']);
}
return $result;
}
/**
* Build endpoint url.
*
* @return string
*/
protected function buildEndpoint()
{
return sprintf(self::ENDPOINT_TEMPLATE, self::ENDPOINT_ENCODE);
}
}
@@ -0,0 +1,85 @@
<?php
/*
* This file is part of the overtrue/easy-sms.
*
* (c) overtrue <i@overtrue.me>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Overtrue\EasySms\Gateways;
use Overtrue\EasySms\Contracts\MessageInterface;
use Overtrue\EasySms\Contracts\PhoneNumberInterface;
use Overtrue\EasySms\Exceptions\GatewayErrorException;
use Overtrue\EasySms\Support\Config;
use Overtrue\EasySms\Traits\HasHttpRequest;
/**
* Class Tiniyo Gateway.
*
* @see https://tiniyo.com/sms.html
*/
class TiniyoGateway extends Gateway
{
use HasHttpRequest;
const ENDPOINT_URL = 'https://api.tiniyo.com/v1/Account/%s/Message';
const SUCCESS_CODE = '000000';
public function getName()
{
return 'tiniyo';
}
/**
* @param \Overtrue\EasySms\Contracts\PhoneNumberInterface $to
* @param \Overtrue\EasySms\Contracts\MessageInterface $message
* @param \Overtrue\EasySms\Support\Config $config
*
* @return array
*
* @throws \Overtrue\EasySms\Exceptions\GatewayErrorException
*/
public function send(PhoneNumberInterface $to, MessageInterface $message, Config $config)
{
$accountSid = $config->get('account_sid');
$endpoint = $this->buildEndPoint($accountSid);
$params = [
'dst' => $to->getUniversalNumber(),
'src' => $config->get('from'),
'text' => $message->getContent($this),
];
$result = $this->request('post', $endpoint, [
'json' => $params,
'headers' => [
'Accept' => 'application/json',
'Content-Type' => 'application/json;charset=utf-8',
'Authorization' => base64_encode($config->get('account_sid').':'.$config->get('token')),
],
]);
if (self::SUCCESS_CODE != $result['statusCode']) {
throw new GatewayErrorException($result['statusCode'], $result['statusCode'], $result);
}
return $result;
}
/**
* build endpoint url.
*
* @param string $accountSid
*
* @return string
*/
protected function buildEndPoint($accountSid)
{
return sprintf(self::ENDPOINT_URL, $accountSid);
}
}
@@ -0,0 +1,77 @@
<?php
/*
* This file is part of the overtrue/easy-sms.
*
* (c) overtrue <i@overtrue.me>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Overtrue\EasySms\Gateways;
use Overtrue\EasySms\Support\Config;
use Overtrue\EasySms\Traits\HasHttpRequest;
use Overtrue\EasySms\Contracts\MessageInterface;
use Overtrue\EasySms\Contracts\PhoneNumberInterface;
use Overtrue\EasySms\Exceptions\GatewayErrorException;
/**
* Class TinreeGateway.
*
* @see http://cms.tinree.com
*/
class TinreeGateway extends Gateway
{
use HasHttpRequest;
const ENDPOINT_URL = 'http://api.tinree.com/api/v2/single_send';
/**
* @param \Overtrue\EasySms\Contracts\PhoneNumberInterface $to
* @param \Overtrue\EasySms\Contracts\MessageInterface $message
* @param \Overtrue\EasySms\Support\Config $config
*
* @return array
*
* @throws \Overtrue\EasySms\Exceptions\GatewayErrorException
*/
public function send(PhoneNumberInterface $to, MessageInterface $message, Config $config)
{
$params = [
'accesskey' => $config->get('accesskey'),
'secret' => $config->get('secret'),
'sign' => $config->get('sign'),
'templateId' => $message->getTemplate($this),
'mobile' => $to->getNumber(),
'content' => $this->buildContent($message),
];
$result = $this->post(self::ENDPOINT_URL, $params);
if (0 != $result['code']) {
throw new GatewayErrorException($result['msg'], $result['code'], $result);
}
return $result;
}
/**
* 构建发送内容
* 用 data 数据合成内容,或者直接使用 data 的值
*
* @param MessageInterface $message
* @return string
*/
protected function buildContent(MessageInterface $message)
{
$data = $message->getData($this);
if (is_array($data)) {
return implode("##", $data);
}
return $data;
}
}
@@ -0,0 +1,91 @@
<?php
/*
* This file is part of the overtrue/easy-sms.
*
* (c) overtrue <i@overtrue.me>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Overtrue\EasySms\Gateways;
use GuzzleHttp\Exception\ClientException;
use Overtrue\EasySms\Contracts\MessageInterface;
use Overtrue\EasySms\Contracts\PhoneNumberInterface;
use Overtrue\EasySms\Exceptions\GatewayErrorException;
use Overtrue\EasySms\Support\Config;
use Overtrue\EasySms\Traits\HasHttpRequest;
/**
* Class TwilioGateway.
*
* @see https://www.twilio.com/docs/api/messaging/send-messages
*/
class TwilioGateway extends Gateway
{
use HasHttpRequest;
const ENDPOINT_URL = 'https://api.twilio.com/2010-04-01/Accounts/%s/Messages.json';
protected $errorStatuses = [
'failed',
'undelivered',
];
public function getName()
{
return 'twilio';
}
/**
* @param \Overtrue\EasySms\Contracts\PhoneNumberInterface $to
* @param \Overtrue\EasySms\Contracts\MessageInterface $message
* @param \Overtrue\EasySms\Support\Config $config
*
* @return array
*
* @throws \Overtrue\EasySms\Exceptions\GatewayErrorException
*/
public function send(PhoneNumberInterface $to, MessageInterface $message, Config $config)
{
$accountSid = $config->get('account_sid');
$endpoint = $this->buildEndPoint($accountSid);
$params = [
'To' => $to->getUniversalNumber(),
'From' => $config->get('from'),
'Body' => $message->getContent($this),
];
try {
$result = $this->request('post', $endpoint, [
'auth' => [
$accountSid,
$config->get('token'),
],
'form_params' => $params,
]);
if (in_array($result['status'], $this->errorStatuses) || !is_null($result['error_code'])) {
throw new GatewayErrorException($result['message'], $result['error_code'], $result);
}
} catch (ClientException $e) {
throw new GatewayErrorException($e->getMessage(), $e->getCode());
}
return $result;
}
/**
* build endpoint url.
*
* @param string $accountSid
*
* @return string
*/
protected function buildEndPoint($accountSid)
{
return sprintf(self::ENDPOINT_URL, $accountSid);
}
}
@@ -0,0 +1,130 @@
<?php
/*
* This file is part of the overtrue/easy-sms.
*
* (c) overtrue <i@overtrue.me>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Overtrue\EasySms\Gateways;
use Overtrue\EasySms\Contracts\MessageInterface;
use Overtrue\EasySms\Contracts\PhoneNumberInterface;
use Overtrue\EasySms\Exceptions\GatewayErrorException;
use Overtrue\EasySms\Support\Config;
use Overtrue\EasySms\Traits\HasHttpRequest;
/**
* Class UcloudGateway.
*/
class UcloudGateway extends Gateway
{
use HasHttpRequest;
const ENDPOINT_URL = 'https://api.ucloud.cn';
const ENDPOINT_Action = 'SendUSMSMessage';
const SUCCESS_CODE = 0;
/**
* Send Message.
*
* @param PhoneNumberInterface $to
* @param MessageInterface $message
* @param Config $config
*
* @return array
*
* @throws GatewayErrorException
*/
public function send(PhoneNumberInterface $to, MessageInterface $message, Config $config)
{
$params = $this->buildParams($to, $message, $config);
$result = $this->get(self::ENDPOINT_URL, $params);
if (self::SUCCESS_CODE != $result['RetCode']) {
throw new GatewayErrorException($result['Message'], $result['RetCode'], $result);
}
return $result;
}
/**
* Build Params.
*
* @param PhoneNumberInterface $to
* @param MessageInterface $message
* @param Config $config
*
* @return array
*/
protected function buildParams(PhoneNumberInterface $to, MessageInterface $message, Config $config)
{
$data = $message->getData($this);
$params = [
'Action' => self::ENDPOINT_Action,
'SigContent' => !empty($data['sig_content']) ? $data['sig_content'] : $config->get('sig_content', ''),
'TemplateId' => $message->getTemplate($this),
'PublicKey' => $config->get('public_key'),
];
$code = isset($data['code']) ? $data['code'] : '';
if (is_array($code) && !empty($code)) {
foreach ($code as $key => $value) {
$params['TemplateParams.'.$key] = $value;
}
} else {
if (!empty($code) || !is_null($code)) {
$params['TemplateParams.0'] = $code;
}
}
$mobiles = isset($data['mobiles']) ? $data['mobiles'] : '';
if (!empty($mobiles) && !is_null($mobiles)) {
if (is_array($mobiles)) {
foreach ($mobiles as $key => $value) {
$params['PhoneNumbers.'.$key] = $value;
}
} else {
$params['PhoneNumbers.0'] = $mobiles;
}
} else {
$params['PhoneNumbers.0'] = $to->getNumber();
}
if (!is_null($config->get('project_id')) && !empty($config->get('project_id'))) {
$params['ProjectId'] = $config->get('project_id');
}
$signature = $this->getSignature($params, $config->get('private_key'));
$params['Signature'] = $signature;
return $params;
}
/**
* Generate Sign.
*
* @param array $params
* @param string $privateKey
*
* @return string
*/
protected function getSignature($params, $privateKey)
{
ksort($params);
$paramsData = '';
foreach ($params as $key => $value) {
$paramsData .= $key;
$paramsData .= $value;
}
$paramsData .= $privateKey;
return sha1($paramsData);
}
}
@@ -0,0 +1,77 @@
<?php
/*
* This file is part of the overtrue/easy-sms.
*
* (c) overtrue <i@overtrue.me>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Overtrue\EasySms\Gateways;
use Overtrue\EasySms\Contracts\MessageInterface;
use Overtrue\EasySms\Contracts\PhoneNumberInterface;
use Overtrue\EasySms\Exceptions\GatewayErrorException;
use Overtrue\EasySms\Support\Config;
use Overtrue\EasySms\Traits\HasHttpRequest;
/**
* Class Ue35Gateway.
*
* @see https://shimo.im/docs/380b42d8cba24521
*/
class Ue35Gateway extends Gateway
{
use HasHttpRequest;
const ENDPOINT_HOST = 'sms.ue35.cn';
const ENDPOINT_URI = '/sms/interface/sendmess.htm';
const SUCCESS_CODE = 1;
/**
* Send message.
*
* @param \Overtrue\EasySms\Contracts\PhoneNumberInterface $to
* @param \Overtrue\EasySms\Contracts\MessageInterface $message
* @param \Overtrue\EasySms\Support\Config $config
*
* @return array
*
* @throws \Overtrue\EasySms\Exceptions\GatewayErrorException ;
*/
public function send(PhoneNumberInterface $to, MessageInterface $message, Config $config)
{
$params = [
'username' => $config->get('username'),
'userpwd' => $config->get('userpwd'),
'mobiles' => $to->getNumber(),
'content' => $message->getContent($this),
];
$headers = [
'host' => static::ENDPOINT_HOST,
'content-type' => 'application/json',
'user-agent' => 'PHP EasySms Client',
];
$result = $this->request('get', self::getEndpointUri().'?'.http_build_query($params), ['headers' => $headers]);
if (is_string($result)) {
$result = json_decode(json_encode(simplexml_load_string($result)), true);
}
if (self::SUCCESS_CODE != $result['errorcode']) {
throw new GatewayErrorException($result['message'], $result['errorcode'], $result);
}
return $result;
}
public static function getEndpointUri()
{
return 'http://'.static::ENDPOINT_HOST.static::ENDPOINT_URI;
}
}
@@ -0,0 +1,311 @@
<?php
/*
* This file is part of the overtrue/easy-sms.
*
* (c) overtrue <i@overtrue.me>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Overtrue\EasySms\Gateways;
use GuzzleHttp\Exception\ClientException;
use GuzzleHttp\HandlerStack;
use GuzzleHttp\Psr7\Query;
use GuzzleHttp\Psr7\Utils;
use GuzzleHttp\Psr7;
use Overtrue\EasySms\Contracts\MessageInterface;
use Overtrue\EasySms\Contracts\PhoneNumberInterface;
use Overtrue\EasySms\Exceptions\GatewayErrorException;
use Overtrue\EasySms\Support\Config;
use Overtrue\EasySms\Traits\HasHttpRequest;
use Psr\Http\Message\RequestInterface;
/**
* Class VolcengineGateway.
*
* @see https://www.volcengine.com/docs/6361/66704
*/
class VolcengineGateway extends Gateway
{
use HasHttpRequest;
const ENDPOINT_ACTION = 'SendSms';
const ENDPOINT_VERSION = '2020-01-01';
const ENDPOINT_CONTENT_TYPE = 'application/json; charset=utf-8';
const ENDPOINT_ACCEPT = 'application/json';
const ENDPOINT_USER_AGENT = 'overtrue/easy-sms';
const ENDPOINT_SERVICE = 'volcSMS';
const Algorithm = 'HMAC-SHA256';
const ENDPOINT_DEFAULT_REGION_ID = 'cn-north-1';
public static $endpoints = [
'cn-north-1' => 'https://sms.volcengineapi.com',
'ap-singapore-1' => 'https://sms.byteplusapi.com',
];
private $regionId = self::ENDPOINT_DEFAULT_REGION_ID;
protected $requestDate;
public function send(PhoneNumberInterface $to, MessageInterface $message, Config $config)
{
$data = $message->getData($this);
$signName = !empty($data['sign_name']) ? $data['sign_name'] : $config->get('sign_name');
$smsAccount = !empty($data['sms_account']) ? $data['sms_account'] : $config->get('sms_account');
$templateId = $message->getTemplate($this);
$phoneNumbers = !empty($data['phone_numbers']) ? $data['phone_numbers'] : $to->getNumber();
$templateParam = !empty($data['template_param']) ? $data['template_param'] : $message->getData($this);
$tag = !empty($data['tag']) ? $data['tag'] : '';
$payload = [
'SmsAccount' => $smsAccount, // 消息组帐号,火山短信页面右上角,短信应用括号中的字符串
'Sign' => $signName, // 短信签名
'TemplateID' => $templateId, // 短信模板ID
'TemplateParam' => json_encode($templateParam), // 短信模板占位符要替换的值
'PhoneNumbers' => $phoneNumbers, // 手机号,如果有多个使用英文逗号分割
];
if ($tag) {
$payload['Tag'] = $tag;
}
$queries = [
'Action' => self::ENDPOINT_ACTION,
'Version' => self::ENDPOINT_VERSION,
];
try {
$stack = HandlerStack::create();
$stack->push($this->signHandle());
$this->setGuzzleOptions([
'headers' => [
'Content-Type' => self::ENDPOINT_CONTENT_TYPE,
'Accept' => self::ENDPOINT_ACCEPT,
'User-Agent' => self::ENDPOINT_USER_AGENT
],
'timeout' => $this->getTimeout(),
'handler' => $stack,
'base_uri' => $this->getEndpoint(),
]);
$response = $this->request('post', $this->getEndpoint().$this->getCanonicalURI(), [
'query' => $queries,
'json' => $payload,
]);
if ($response instanceof Psr7\Response) {
$response = json_decode($response->getBody()->getContents(), true);
}
if (isset($response['ResponseMetadata']['Error'])) { // 请求错误参数,如果请求没有错误,则不存在该参数返回
// 火山引擎错误码格式为:'ZJ'+ 5位数字,比如 ZJ20009,取出数字部分
preg_match('/\d+/', $response['ResponseMetadata']['Error']['Code'], $matches);
throw new GatewayErrorException($response['ResponseMetadata']['Error']['Code'].":".$response['ResponseMetadata']['Error']['Message'], $matches[0], $response);
}
return $response;
} catch (ClientException $exception) {
$responseContent = $exception->getResponse()->getBody()->getContents();
$response = json_decode($responseContent, true);
if (isset($response['ResponseMetadata']['Error']) && $error = $response['ResponseMetadata']['Error']) { // 请求错误参数,如果请求没有错误,则不存在该参数返回
// 火山引擎公共错误码Error与业务错误码略有不同,比如:"Error":{"CodeN":100004,"Code":"MissingRequestInfo","Message":"The request is missing timestamp information."}
// 此处错误码直接取 CodeN
throw new GatewayErrorException($error["CodeN"].":".$error['Message'], $error["CodeN"], $response);
}
throw new GatewayErrorException($responseContent, $exception->getCode(), ['content' => $responseContent]);
}
}
protected function signHandle()
{
return function (callable $handler) {
return function (RequestInterface $request, array $options) use ($handler) {
$request = $request->withHeader('X-Date', $this->getRequestDate());
list($canonicalHeaders, $signedHeaders) = $this->getCanonicalHeaders($request);
$queries = Query::parse($request->getUri()->getQuery());
$canonicalRequest = $request->getMethod()."\n"
.$this->getCanonicalURI()."\n"
.$this->getCanonicalQueryString($queries)."\n"
.$canonicalHeaders."\n"
.$signedHeaders."\n"
.$this->getPayloadHash($request);
$stringToSign = $this->getStringToSign($canonicalRequest);
$signingKey = $this->getSigningKey();
$signature = hash_hmac('sha256', $stringToSign, $signingKey);
$parsed = $this->parseRequest($request);
$parsed['headers']['Authorization'] = self::Algorithm.
' Credential='.$this->getAccessKeyId().'/'.$this->getCredentialScope().', SignedHeaders='.$signedHeaders.', Signature='.$signature;
$buildRequest = function () use ($request, $parsed) {
if ($parsed['query']) {
$parsed['uri'] = $parsed['uri']->withQuery(Query::build($parsed['query']));
}
return new Psr7\Request(
$parsed['method'],
$parsed['uri'],
$parsed['headers'],
$parsed['body'],
$parsed['version']
);
};
return $handler($buildRequest(), $options);
};
};
}
private function parseRequest(RequestInterface $request)
{
$uri = $request->getUri();
return [
'method' => $request->getMethod(),
'path' => $uri->getPath(),
'query' => Query::parse($uri->getQuery()),
'uri' => $uri,
'headers' => $request->getHeaders(),
'body' => $request->getBody(),
'version' => $request->getProtocolVersion()
];
}
public function getPayloadHash(RequestInterface $request)
{
if ($request->hasHeader('X-Content-Sha256')) {
return $request->getHeaderLine('X-Content-Sha256');
}
return Utils::hash($request->getBody(), 'sha256');
}
public function getRegionId()
{
return $this->config->get('region_id', self::ENDPOINT_DEFAULT_REGION_ID);
}
public function getEndpoint()
{
$regionId = $this->getRegionId();
if (!in_array($regionId, array_keys(self::$endpoints))) {
$regionId = self::ENDPOINT_DEFAULT_REGION_ID;
}
return static::$endpoints[$regionId];
}
public function getRequestDate()
{
return $this->requestDate ?: gmdate('Ymd\THis\Z');
}
/**
* 指代信任状,格式为:YYYYMMDD/region/service/request
* @return string
*/
public function getCredentialScope()
{
return date('Ymd', strtotime($this->getRequestDate())).'/'.$this->getRegionId().'/'.self::ENDPOINT_SERVICE.'/request';
}
/**
* 计算签名密钥
* 在计算签名前,首先从私有访问密钥(Secret Access Key)派生出签名密钥(signing key),而不是直接使用私有访问密钥。具体计算过程如下:
* kSecret = *Your Secret Access Key*
* kDate = HMAC(kSecret, Date)
* kRegion = HMAC(kDate, Region)
* kService = HMAC(kRegion, Service)
* kSigning = HMAC(kService, "request")
* 其中Date精确到日,与RequestDate中YYYYMMDD部分相同。
* @return string
*/
protected function getSigningKey()
{
$dateKey = hash_hmac('sha256', date("Ymd", strtotime($this->getRequestDate())), $this->getAccessKeySecret(), true);
$regionKey = hash_hmac('sha256', $this->getRegionId(), $dateKey, true);
$serviceKey = hash_hmac('sha256', self::ENDPOINT_SERVICE, $regionKey, true);
return hash_hmac('sha256', 'request', $serviceKey, true);
}
/**
* 创建签名字符串
* 签名字符串主要包含请求以及正规化请求的元数据信息,由签名算法、请求日期、信任状和正规化请求哈希值连接组成,伪代码如下:
* StringToSign = Algorithm + '\n' + RequestDate + '\n' + CredentialScope + '\n' + HexEncode(Hash(CanonicalRequest))
* @return string
*/
public function getStringToSign($canonicalRequest)
{
return self::Algorithm."\n".$this->getRequestDate()."\n".$this->getCredentialScope()."\n".hash('sha256', $canonicalRequest);
}
/**
* @return string
*/
public function getAccessKeySecret()
{
return $this->config->get('access_key_secret');
}
/**
* @return string
*/
public function getAccessKeyId()
{
return $this->config->get('access_key_id');
}
/**
* 指代正规化后的Header。
* 其中伪代码如下:
* CanonicalHeaders = CanonicalHeadersEntry0 + CanonicalHeadersEntry1 + ... + CanonicalHeadersEntryN
* 其中CanonicalHeadersEntry = Lowercase(HeaderName) + ':' + Trimall(HeaderValue) + '\n'
* Lowcase代表将Header的名称全部转化成小写,Trimall表示去掉Header的值的前后多余的空格。
* 特别注意:最后需要添加"\n"的换行符,header的顺序是以headerName的小写后ascii排序。
* @return array
*/
public function getCanonicalHeaders(RequestInterface $request)
{
$headers = $request->getHeaders();
ksort($headers);
$canonicalHeaders = '';
$signedHeaders = [];
foreach ($headers as $key => $val) {
$lowerKey = strtolower($key);
$canonicalHeaders .= $lowerKey.':'.trim($val[0]).PHP_EOL;
$signedHeaders[] = $lowerKey;
}
$signedHeadersString = implode(';', $signedHeaders);
return [$canonicalHeaders, $signedHeadersString];
}
/**
* urlencode(注:同RFC3986方法)每一个querystring参数名称和参数值。
* 按照ASCII字节顺序对参数名称严格排序,相同参数名的不同参数值需保持请求的原始顺序。
* 将排序好的参数名称和参数值用=连接,按照排序结果将“参数对”用&连接。
* 例如:CanonicalQueryString = "Action=ListUsers&Version=2018-01-01"
* @return string
*/
public function getCanonicalQueryString(array $query)
{
ksort($query);
return http_build_query($query);
}
/**
* 指代正规化后的URI。
* 如果URI为空,那么使用"/"作为绝对路径。
* 在火山引擎中绝大多数接口的URI都为"/"。
* 如果是复杂的path,请通过RFC3986规范进行编码。
*
* @return string
*/
public function getCanonicalURI()
{
return '/';
}
}
@@ -0,0 +1,75 @@
<?php
/*
* This file is part of the overtrue/easy-sms.
*
* (c) overtrue <i@overtrue.me>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Overtrue\EasySms\Gateways;
use Overtrue\EasySms\Contracts\MessageInterface;
use Overtrue\EasySms\Contracts\PhoneNumberInterface;
use Overtrue\EasySms\Exceptions\GatewayErrorException;
use Overtrue\EasySms\Support\Config;
use Overtrue\EasySms\Traits\HasHttpRequest;
/**
* Class YidongmasblackGateway.
* 移动MAS黑名单模式(免创建模板)
* @author houang <mail@houang.cn>
*
* @see https://mas.10086.cn
*/
class YidongmasblackGateway extends Gateway
{
use HasHttpRequest;
const ENDPOINT_URL = 'http://112.35.1.155:1992/sms/norsubmit';
const ENDPOINT_METHOD = 'send';
/**
* @param PhoneNumberInterface $to
* @param MessageInterface $message
* @param Config $config
*
* @return \Psr\Http\Message\ResponseInterface|array|string
*
* @throws GatewayErrorException
*/
public function send(PhoneNumberInterface $to, MessageInterface $message, Config $config)
{
$params["ecName"] = $config->get('ecName');
$params["apId"] = $config->get('apId');
$params["sign"] = $config->get('sign');
$params["addSerial"] = $config->get('addSerial');
$params["mobiles"] = $to->getNumber();
$params["content"] = $message->getContent();
$result = $this->postJson(self::ENDPOINT_URL, $this->generateContent($params));
if ('true' != $result['success']) {
throw new GatewayErrorException($result['success'], $result['rspcod'], $result);
}
return $result;
}
/**
* Generate Content.
*
* @param array $params
*
* @return string
*/
protected function generateContent($params)
{
$secretKey = $this->config->get('secretKey');
$params['mac'] = md5($params["ecName"].$params["apId"].$secretKey.$params["mobiles"].$params["content"].$params["sign"].$params["addSerial"]);
return base64_encode(json_encode($params));
}
}
@@ -0,0 +1,101 @@
<?php
/*
* This file is part of the overtrue/easy-sms.
*
* (c) overtrue <i@overtrue.me>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Overtrue\EasySms\Gateways;
use Overtrue\EasySms\Contracts\MessageInterface;
use Overtrue\EasySms\Contracts\PhoneNumberInterface;
use Overtrue\EasySms\Exceptions\GatewayErrorException;
use Overtrue\EasySms\Support\Config;
use Overtrue\EasySms\Traits\HasHttpRequest;
/**
* Class YunpianGateway.
*
* @see https://www.yunpian.com/doc/zh_CN/intl/single_send.html
*/
class YunpianGateway extends Gateway
{
use HasHttpRequest;
const ENDPOINT_TEMPLATE = 'https://%s.yunpian.com/%s/%s/%s.%s';
const ENDPOINT_VERSION = 'v2';
const ENDPOINT_FORMAT = 'json';
/**
* @param \Overtrue\EasySms\Contracts\PhoneNumberInterface $to
* @param \Overtrue\EasySms\Contracts\MessageInterface $message
* @param \Overtrue\EasySms\Support\Config $config
*
* @return array
*
* @throws \Overtrue\EasySms\Exceptions\GatewayErrorException ;
*/
public function send(PhoneNumberInterface $to, MessageInterface $message, Config $config)
{
$template = $message->getTemplate($this);
$function = 'single_send';
$option = [
'form_params' => [
'apikey' => $config->get('api_key'),
'mobile' => $to->getUniversalNumber()
],
'exceptions' => false,
];
if (!is_null($template)) {
$function = 'tpl_single_send';
$data = [];
$templateData = $message->getData($this);
$templateData = isset($templateData) ? $templateData : [];
foreach ($templateData as $key => $value) {
$data[] = urlencode('#'.$key.'#') . '=' . urlencode($value);
}
$option['form_params'] = array_merge($option['form_params'], [
'tpl_id' => $template,
'tpl_value' => implode('&', $data)
]);
} else {
$content = $message->getContent($this);
$signature = $config->get('signature', '');
$option['form_params'] = array_merge($option['form_params'], [
'text' => 0 === \stripos($content, '【') ? $content : $signature.$content
]);
}
$endpoint = $this->buildEndpoint('sms', 'sms', $function);
$result = $this->request('post', $endpoint, $option);
if ($result['code']) {
throw new GatewayErrorException($result['msg'], $result['code'], $result);
}
return $result;
}
/**
* Build endpoint url.
*
* @param string $type
* @param string $resource
* @param string $function
*
* @return string
*/
protected function buildEndpoint($type, $resource, $function)
{
return sprintf(self::ENDPOINT_TEMPLATE, $type, self::ENDPOINT_VERSION, $resource, $function, self::ENDPOINT_FORMAT);
}
}
@@ -0,0 +1,123 @@
<?php
/*
* This file is part of the overtrue/easy-sms.
*
* (c) overtrue <i@overtrue.me>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Overtrue\EasySms\Gateways;
use Overtrue\EasySms\Contracts\MessageInterface;
use Overtrue\EasySms\Contracts\PhoneNumberInterface;
use Overtrue\EasySms\Exceptions\GatewayErrorException;
use Overtrue\EasySms\Support\Config;
use Overtrue\EasySms\Traits\HasHttpRequest;
/**
* Class YuntongxunGateway.
*
* @see Chinese Mainland: http://doc.yuntongxun.com/pe/5a533de33b8496dd00dce07c
* @see International: http://doc.yuntongxun.com/pe/604f29eda80948a1006e928d
*/
class YuntongxunGateway extends Gateway
{
use HasHttpRequest;
const ENDPOINT_TEMPLATE = 'https://%s:%s/%s/%s/%s/%s/%s?sig=%s';
const SERVER_IP = 'app.cloopen.com';
const DEBUG_SERVER_IP = 'sandboxapp.cloopen.com';
const DEBUG_TEMPLATE_ID = 1;
const SERVER_PORT = '8883';
const SDK_VERSION = '2013-12-26';
const SDK_VERSION_INT = 'v2';
const SUCCESS_CODE = '000000';
private $international = false; // if international SMS, default false means no.
/**
* @param \Overtrue\EasySms\Contracts\PhoneNumberInterface $to
* @param \Overtrue\EasySms\Contracts\MessageInterface $message
* @param \Overtrue\EasySms\Support\Config $config
*
* @return array
*
* @throws \Overtrue\EasySms\Exceptions\GatewayErrorException ;
*/
public function send(PhoneNumberInterface $to, MessageInterface $message, Config $config)
{
$datetime = date('YmdHis');
$data = [
'appId' => $config->get('app_id'),
];
if ($to->inChineseMainland()) {
$type = 'SMS';
$resource = 'TemplateSMS';
$data['to'] = $to->getNumber();
$data['templateId'] = (int) ($this->config->get('debug') ? self::DEBUG_TEMPLATE_ID : $message->getTemplate($this));
$data['datas'] = $message->getData($this);
} else {
$type = 'international';
$resource = 'send';
$this->international = true;
$data['mobile'] = $to->getZeroPrefixedNumber();
$data['content'] = $message->getContent($this);
}
$endpoint = $this->buildEndpoint($type, $resource, $datetime, $config);
$result = $this->request('post', $endpoint, [
'json' => $data,
'headers' => [
'Accept' => 'application/json',
'Content-Type' => 'application/json;charset=utf-8',
'Authorization' => base64_encode($config->get('account_sid').':'.$datetime),
],
]);
if (self::SUCCESS_CODE != $result['statusCode']) {
throw new GatewayErrorException($result['statusCode'], $result['statusCode'], $result);
}
return $result;
}
/**
* Build endpoint url.
*
* @param string $type
* @param string $resource
* @param string $datetime
* @param \Overtrue\EasySms\Support\Config $config
*
* @return string
*/
protected function buildEndpoint($type, $resource, $datetime, Config $config)
{
$serverIp = $this->config->get('debug') ? self::DEBUG_SERVER_IP : self::SERVER_IP;
if ($this->international) {
$accountType = 'account';
$sdkVersion = self::SDK_VERSION_INT;
} else {
$accountType = $this->config->get('is_sub_account') ? 'SubAccounts' : 'Accounts';
$sdkVersion = self::SDK_VERSION;
}
$sig = strtoupper(md5($config->get('account_sid').$config->get('account_token').$datetime));
return sprintf(self::ENDPOINT_TEMPLATE, $serverIp, self::SERVER_PORT, $sdkVersion, $accountType, $config->get('account_sid'), $type, $resource, $sig);
}
}
@@ -0,0 +1,187 @@
<?php
/*
* This file is part of the overtrue/easy-sms.
*
* (c) overtrue <i@overtrue.me>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Overtrue\EasySms\Gateways;
use Overtrue\EasySms\Contracts\MessageInterface;
use Overtrue\EasySms\Contracts\PhoneNumberInterface;
use Overtrue\EasySms\Exceptions\GatewayErrorException;
use Overtrue\EasySms\Support\Config;
use Overtrue\EasySms\Traits\HasHttpRequest;
/**
* Class YunxinGateway.
*
* @author her-cat <i@her-cat.com>
*
* @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
*/
class YunxinGateway extends Gateway
{
use HasHttpRequest;
const ENDPOINT_TEMPLATE = 'https://api.netease.im/%s/%s.action';
const ENDPOINT_ACTION = 'sendCode';
const SUCCESS_CODE = 200;
/**
* Send a short message.
*
* @param \Overtrue\EasySms\Contracts\PhoneNumberInterface $to
* @param \Overtrue\EasySms\Contracts\MessageInterface $message
* @param \Overtrue\EasySms\Support\Config $config
*
* @return array
*
* @throws GatewayErrorException
*/
public function send(PhoneNumberInterface $to, MessageInterface $message, Config $config)
{
$data = $message->getData($this);
$action = isset($data['action']) ? $data['action'] : self::ENDPOINT_ACTION;
$endpoint = $this->buildEndpoint('sms', $action);
switch ($action) {
case 'sendCode':
$params = $this->buildSendCodeParams($to, $message, $config);
break;
case 'verifyCode':
$params = $this->buildVerifyCodeParams($to, $message);
break;
case "sendTemplate":
$params = $this->buildTemplateParams($to, $message, $config);
break;
default:
throw new GatewayErrorException(sprintf('action: %s not supported', $action), 0);
}
$headers = $this->buildHeaders($config);
try {
$result = $this->post($endpoint, $params, $headers);
if (!isset($result['code']) || self::SUCCESS_CODE !== $result['code']) {
$code = isset($result['code']) ? $result['code'] : 0;
$error = isset($result['msg']) ? $result['msg'] : json_encode($result, JSON_UNESCAPED_UNICODE);
throw new GatewayErrorException($error, $code);
}
} catch (\Exception $e) {
throw new GatewayErrorException($e->getMessage(), $e->getCode());
}
return $result;
}
/**
* @param $resource
* @param $function
*
* @return string
*/
protected function buildEndpoint($resource, $function)
{
return sprintf(self::ENDPOINT_TEMPLATE, $resource, strtolower($function));
}
/**
* Get the request headers.
*
* @param Config $config
*
* @return array
*/
protected function buildHeaders(Config $config)
{
$headers = [
'AppKey' => $config->get('app_key'),
'Nonce' => md5(uniqid('easysms')),
'CurTime' => (string) time(),
'Content-Type' => 'application/x-www-form-urlencoded;charset=utf-8',
];
$headers['CheckSum'] = sha1("{$config->get('app_secret')}{$headers['Nonce']}{$headers['CurTime']}");
return $headers;
}
/**
* @param PhoneNumberInterface $to
* @param MessageInterface $message
* @param Config $config
*
* @return array
*/
public function buildSendCodeParams(PhoneNumberInterface $to, MessageInterface $message, Config $config)
{
$data = $message->getData($this);
$template = $message->getTemplate($this);
return [
'mobile' => $to->getUniversalNumber(),
'authCode' => array_key_exists('code', $data) ? $data['code'] : '',
'deviceId' => array_key_exists('device_id', $data) ? $data['device_id'] : '',
'templateid' => is_string($template) ? $template : '',
'codeLen' => $config->get('code_length', 4),
'needUp' => $config->get('need_up', false),
];
}
/**
* @param PhoneNumberInterface $to
* @param MessageInterface $message
*
* @return array
*
* @throws GatewayErrorException
*/
public function buildVerifyCodeParams(PhoneNumberInterface $to, MessageInterface $message)
{
$data = $message->getData($this);
if (!array_key_exists('code', $data)) {
throw new GatewayErrorException('"code" cannot be empty', 0);
}
return [
'mobile' => $to->getUniversalNumber(),
'code' => $data['code'],
];
}
/**
* @param PhoneNumberInterface $to
* @param MessageInterface $message
* @param Config $config
* @return array
*
*/
public function buildTemplateParams(PhoneNumberInterface $to, MessageInterface $message, Config $config)
{
$data = $message->getData($this);
$template = $message->getTemplate($this);
return [
'templateid'=>$template,
'mobiles'=>json_encode([$to->getUniversalNumber()]),
'params'=>array_key_exists('params',$data) ? json_encode($data['params']) : '',
'needUp'=>$config->get('need_up', false)
];
}
}
@@ -0,0 +1,121 @@
<?php
/*
* This file is part of the overtrue/easy-sms.
*
* (c) overtrue <i@overtrue.me>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Overtrue\EasySms\Gateways;
use Overtrue\EasySms\Contracts\MessageInterface;
use Overtrue\EasySms\Contracts\PhoneNumberInterface;
use Overtrue\EasySms\Exceptions\GatewayErrorException;
use Overtrue\EasySms\Support\Config;
use Overtrue\EasySms\Traits\HasHttpRequest;
/**
* Class YunzhixunGateway.
*
* @author her-cat <i@her-cat.com>
*
* @see http://docs.ucpaas.com/doku.php?id=%E7%9F%AD%E4%BF%A1:sendsms
*/
class YunzhixunGateway extends Gateway
{
use HasHttpRequest;
const SUCCESS_CODE = '000000';
const FUNCTION_SEND_SMS = 'sendsms';
const FUNCTION_BATCH_SEND_SMS = 'sendsms_batch';
const ENDPOINT_TEMPLATE = 'https://open.ucpaas.com/ol/%s/%s';
/**
* Send a short message.
*
* @param \Overtrue\EasySms\Contracts\PhoneNumberInterface $to
* @param \Overtrue\EasySms\Contracts\MessageInterface $message
* @param \Overtrue\EasySms\Support\Config $config
*
* @return array
*
* @throws GatewayErrorException
*/
public function send(PhoneNumberInterface $to, MessageInterface $message, Config $config)
{
$data = $message->getData($this);
$function = isset($data['mobiles']) ? self::FUNCTION_BATCH_SEND_SMS : self::FUNCTION_SEND_SMS;
$endpoint = $this->buildEndpoint('sms', $function);
$params = $this->buildParams($to, $message, $config);
return $this->execute($endpoint, $params);
}
/**
* @param $resource
* @param $function
*
* @return string
*/
protected function buildEndpoint($resource, $function)
{
return sprintf(self::ENDPOINT_TEMPLATE, $resource, $function);
}
/**
* @param PhoneNumberInterface $to
* @param MessageInterface $message
* @param Config $config
*
* @return array
*/
protected function buildParams(PhoneNumberInterface $to, MessageInterface $message, Config $config)
{
$data = $message->getData($this);
return [
'sid' => $config->get('sid'),
'token' => $config->get('token'),
'appid' => $config->get('app_id'),
'templateid' => $message->getTemplate($this),
'uid' => isset($data['uid']) ? $data['uid'] : '',
'param' => isset($data['params']) ? $data['params'] : '',
'mobile' => isset($data['mobiles']) ? $data['mobiles'] : $to->getNumber(),
];
}
/**
* @param $endpoint
* @param $params
*
* @return array
*
* @throws GatewayErrorException
*/
protected function execute($endpoint, $params)
{
try {
$result = $this->postJson($endpoint, $params);
if (!isset($result['code']) || self::SUCCESS_CODE !== $result['code']) {
$code = isset($result['code']) ? $result['code'] : 0;
$error = isset($result['msg']) ? $result['msg'] : json_encode($result, JSON_UNESCAPED_UNICODE);
throw new GatewayErrorException($error, $code);
}
return $result;
} catch (\Exception $e) {
throw new GatewayErrorException($e->getMessage(), $e->getCode());
}
}
}
@@ -0,0 +1,63 @@
<?php
/*
* This file is part of the overtrue/easy-sms.
*
* (c) overtrue <i@overtrue.me>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Overtrue\EasySms\Gateways;
use Overtrue\EasySms\Contracts\MessageInterface;
use Overtrue\EasySms\Contracts\PhoneNumberInterface;
use Overtrue\EasySms\Exceptions\GatewayErrorException;
use Overtrue\EasySms\Support\Config;
use Overtrue\EasySms\Traits\HasHttpRequest;
/**
* Class RongheyunGateway.
*
* @see https://zzyun.com/
*/
class ZzyunGateway extends Gateway
{
use HasHttpRequest;
const ENDPOINT_URL = 'https://zzyun.com/api/sms/sendByTplCode';
/**
* @param \Overtrue\EasySms\Contracts\PhoneNumberInterface $to
* @param \Overtrue\EasySms\Contracts\MessageInterface $message
* @param \Overtrue\EasySms\Support\Config $config
*
* @return array
*
* @throws \Overtrue\EasySms\Exceptions\GatewayErrorException ;
*/
public function send(PhoneNumberInterface $to, MessageInterface $message, Config $config)
{
$time = time();
$user_id = $config->get('user_id');
$token = md5($time . $user_id . $config->get('secret'));
$params = [
'user_id' => $user_id,
'time' => $time,
'token' => $token,
'mobiles' => $to->getNumber(),// 手机号码,多个英文逗号隔开
'tpl_code' => $message->getTemplate($this),
'tpl_params' => $message->getData($this),
'sign_name' => $config->get('sign_name'),
];
$result = $this->post(self::ENDPOINT_URL, $params);
if ('Success' != $result['Code']) {
throw new GatewayErrorException($result['Message'], $result['Code'], $result);
}
return $result;
}
}