142 lines
6.1 KiB
PHP
142 lines
6.1 KiB
PHP
<?php
|
|
/**
|
|
* AKSRT SMS Provider — 腾讯云短信
|
|
*
|
|
* 通过腾讯云短信 API (v2021-01-11) 发送验证码。
|
|
* 使用 TC3-HMAC-SHA256 签名,不引入重量级 SDK。
|
|
*
|
|
* @package AKSRT
|
|
* @link https://cloud.tencent.com/document/product/382/55981
|
|
*/
|
|
|
|
defined('ABSPATH') || exit;
|
|
|
|
class AKSRT_SMS_Provider_Tencent implements AKSRT_SMS_Provider {
|
|
|
|
public function get_key(): string {
|
|
return 'tencent';
|
|
}
|
|
|
|
public function get_label(): string {
|
|
return __('腾讯云短信', 'aksrt');
|
|
}
|
|
|
|
public function validate_config(array $config): array {
|
|
$missing = [];
|
|
if (empty($config['sms_access_key'])) $missing[] = 'sms_access_key';
|
|
if (empty($config['sms_access_secret'])) $missing[] = 'sms_access_secret';
|
|
if (empty($config['sms_tencent_app_id'])) $missing[] = 'sms_tencent_app_id';
|
|
if (empty($config['sms_sign_name'])) $missing[] = 'sms_sign_name';
|
|
if (empty($config['sms_template_code'])) $missing[] = 'sms_template_code';
|
|
return $missing;
|
|
}
|
|
|
|
public function send_code(string $phone, string $code, array $config): array {
|
|
$secret_id = (string) ($config['sms_access_key'] ?? '');
|
|
$secret_key = (string) ($config['sms_access_secret'] ?? '');
|
|
$app_id = (string) ($config['sms_tencent_app_id'] ?? '');
|
|
$sign_name = (string) ($config['sms_sign_name'] ?? '');
|
|
$template_id = (string) ($config['sms_template_code'] ?? '');
|
|
$region = (string) ($config['sms_tencent_region'] ?? 'ap-guangzhou');
|
|
|
|
$result = $this->request($secret_id, $secret_key, $app_id, $sign_name, $template_id, $region, $phone, $code);
|
|
|
|
if (is_wp_error($result)) {
|
|
$this->log('error', $phone, $result->get_error_message());
|
|
return ['success' => false, 'message' => $result->get_error_message(), 'transport' => 'tc3_http'];
|
|
}
|
|
|
|
$body = json_decode(wp_remote_retrieve_body($result), true);
|
|
$response = $body['Response'] ?? [];
|
|
|
|
if (isset($response['SendStatusSet'][0]['Code']) && $response['SendStatusSet'][0]['Code'] === 'Ok') {
|
|
$this->log('sent', $phone, 'Ok');
|
|
return ['success' => true, 'message' => __('验证码已发送', 'aksrt'), 'transport' => 'tc3_http'];
|
|
}
|
|
|
|
$err_code = $response['Error']['Code'] ?? ($response['SendStatusSet'][0]['Code'] ?? 'Unknown');
|
|
$err_msg = $response['Error']['Message'] ?? ($response['SendStatusSet'][0]['Message'] ?? __('未知错误', 'aksrt'));
|
|
$this->log('error', $phone, "{$err_code}: {$err_msg}");
|
|
return ['success' => false, 'message' => sprintf('腾讯云: %s', $err_msg), 'transport' => 'tc3_http'];
|
|
}
|
|
|
|
public function get_status(array $config): array {
|
|
$missing = $this->validate_config($config);
|
|
return [
|
|
'provider' => $this->get_key(),
|
|
'configured' => empty($missing),
|
|
'available' => !empty($config['sms_enabled']) && empty($missing),
|
|
'verified' => !empty($config['sms_tencent_verified']),
|
|
'transport' => 'tc3_http',
|
|
'missing_fields' => $missing,
|
|
];
|
|
}
|
|
|
|
/**
|
|
* TC3-HMAC-SHA256 签名请求
|
|
*/
|
|
private function request(string $secret_id, string $secret_key, string $app_id, string $sign_name, string $template_id, string $region, string $phone, string $code) {
|
|
$service = 'sms';
|
|
$host = "{$service}.tencentcloudapi.com";
|
|
$action = 'SendSms';
|
|
$version = '2021-01-11';
|
|
$endpoint = "https://{$host}";
|
|
|
|
$payload = wp_json_encode([
|
|
'PhoneNumberSet' => [$phone],
|
|
'SmsSdkAppId' => $app_id,
|
|
'SignName' => $sign_name,
|
|
'TemplateId' => $template_id,
|
|
'TemplateParamSet' => [$code],
|
|
]);
|
|
|
|
$timestamp = time();
|
|
$date = gmdate('Y-m-d', $timestamp);
|
|
|
|
// Step 1: CanonicalRequest
|
|
$http_request_method = 'POST';
|
|
$canonical_uri = '/';
|
|
$canonical_querystring = '';
|
|
$content_type = 'application/json; charset=utf-8';
|
|
$canonical_headers = "content-type:{$content_type}\nhost:{$host}\nx-tc-action:" . strtolower($action) . "\n";
|
|
$signed_headers = 'content-type;host;x-tc-action';
|
|
$hashed_payload = hash('sha256', $payload);
|
|
$canonical_request = "{$http_request_method}\n{$canonical_uri}\n{$canonical_querystring}\n{$canonical_headers}\n{$signed_headers}\n{$hashed_payload}";
|
|
|
|
// Step 2: StringToSign
|
|
$algorithm = 'TC3-HMAC-SHA256';
|
|
$credential_scope = "{$date}/{$service}/tc3_request";
|
|
$hashed_request = hash('sha256', $canonical_request);
|
|
$string_to_sign = "{$algorithm}\n{$timestamp}\n{$credential_scope}\n{$hashed_request}";
|
|
|
|
// Step 3: Signature
|
|
$secret_date = hash_hmac('sha256', $date, "TC3{$secret_key}", true);
|
|
$secret_service = hash_hmac('sha256', $service, $secret_date, true);
|
|
$secret_signing = hash_hmac('sha256', 'tc3_request', $secret_service, true);
|
|
$signature = hash_hmac('sha256', $string_to_sign, $secret_signing);
|
|
|
|
// Step 4: Authorization header
|
|
$authorization = "{$algorithm} Credential={$secret_id}/{$credential_scope}, SignedHeaders={$signed_headers}, Signature={$signature}";
|
|
|
|
return wp_remote_post($endpoint, [
|
|
'timeout' => 10,
|
|
'headers' => [
|
|
'Authorization' => $authorization,
|
|
'Content-Type' => $content_type,
|
|
'Host' => $host,
|
|
'X-TC-Action' => $action,
|
|
'X-TC-Version' => $version,
|
|
'X-TC-Timestamp' => (string) $timestamp,
|
|
'X-TC-Region' => $region,
|
|
],
|
|
'body' => $payload,
|
|
]);
|
|
}
|
|
|
|
private function log(string $level, string $phone, string $message): void {
|
|
if (!defined('WP_DEBUG') || !WP_DEBUG) return;
|
|
$masked = substr($phone, 0, 3) . '****' . substr($phone, -4);
|
|
error_log(sprintf('[AKSRT SMS] [tencent] [%s] Phone: %s — %s', $level, $masked, $message));
|
|
}
|
|
}
|