getMessage(), ['error' => 'config_incomplete'] ); } $endpoint = (string) config('sms.tencentcloud.endpoint'); $region = (string) config('sms.tencentcloud.region'); $secretId = (string) config('sms.tencentcloud.secret_id'); $secretKey = (string) config('sms.tencentcloud.secret_key'); $timeout = (int) config('sms.timeout', 5); $payload = [ 'PhoneNumberSet' => ['+86'.$record->mobile], 'SmsSdkAppId' => (string) config('sms.tencentcloud.sdk_app_id'), 'SignName' => (string) config('sms.tencentcloud.sign_name'), 'TemplateId' => (string) config('sms.tencentcloud.template_id'), 'TemplateParamSet' => [ (string) $record->code, (string) data_get($record->request_payload_json, 'ttl_minutes', '5'), ], ]; $body = json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); if ($body === false) { return SmsSendResult::failure( SmsVerification::PROVIDER_TENCENTCLOUD, null, 'PAYLOAD_ENCODE_FAILED', 'Tencent Cloud SMS payload encode failed.', ['error' => 'payload_encode_failed'] ); } $timestamp = time(); $headers = $this->buildHeaders($endpoint, $region, $secretId, $secretKey, $timestamp, $body); try { $response = Http::timeout($timeout) ->withHeaders($headers) ->withBody($body, 'application/json; charset=utf-8') ->post('https://'.$endpoint); } catch (\Throwable $e) { return SmsSendResult::failure( SmsVerification::PROVIDER_TENCENTCLOUD, null, 'NETWORK_ERROR', $e->getMessage(), ['error' => 'network_error'] ); } $json = $response->json(); if (! is_array($json)) { return SmsSendResult::failure( SmsVerification::PROVIDER_TENCENTCLOUD, null, 'INVALID_RESPONSE', 'Tencent Cloud SMS returned a non-json response.', ['http_status' => $response->status()] ); } $tencentResponse = data_get($json, 'Response', []); $requestId = data_get($tencentResponse, 'RequestId'); if (data_get($tencentResponse, 'Error.Code')) { return SmsSendResult::failure( SmsVerification::PROVIDER_TENCENTCLOUD, is_string($requestId) ? $requestId : null, (string) data_get($tencentResponse, 'Error.Code'), (string) data_get($tencentResponse, 'Error.Message'), $this->responseSummary($json) ); } $status = data_get($tencentResponse, 'SendStatusSet.0', []); $providerCode = (string) data_get($status, 'Code', ''); $providerMessage = (string) data_get($status, 'Message', ''); if ($response->successful() && $providerCode === 'Ok') { return SmsSendResult::success( SmsVerification::PROVIDER_TENCENTCLOUD, is_string($requestId) ? $requestId : null, $providerCode, $providerMessage, $this->responseSummary($json) ); } return SmsSendResult::failure( SmsVerification::PROVIDER_TENCENTCLOUD, is_string($requestId) ? $requestId : null, $providerCode !== '' ? $providerCode : 'HTTP_'.$response->status(), $providerMessage !== '' ? $providerMessage : 'Tencent Cloud SMS send failed.', $this->responseSummary($json) ); } /** * @return array */ private function buildHeaders( string $host, string $region, string $secretId, string $secretKey, int $timestamp, string $payload, ): array { $date = gmdate('Y-m-d', $timestamp); $canonicalHeaders = "content-type:application/json; charset=utf-8\nhost:{$host}\n"; $signedHeaders = 'content-type;host'; $canonicalRequest = implode("\n", [ 'POST', '/', '', $canonicalHeaders, $signedHeaders, hash('sha256', $payload), ]); $credentialScope = $date.'/'.self::SERVICE.'/tc3_request'; $stringToSign = implode("\n", [ self::ALGORITHM, (string) $timestamp, $credentialScope, hash('sha256', $canonicalRequest), ]); $secretDate = hash_hmac('sha256', $date, 'TC3'.$secretKey, true); $secretService = hash_hmac('sha256', self::SERVICE, $secretDate, true); $secretSigning = hash_hmac('sha256', 'tc3_request', $secretService, true); $signature = hash_hmac('sha256', $stringToSign, $secretSigning); $authorization = self::ALGORITHM .' Credential='.$secretId.'/'.$credentialScope .', SignedHeaders='.$signedHeaders .', Signature='.$signature; return [ 'Authorization' => $authorization, 'Content-Type' => 'application/json; charset=utf-8', 'Host' => $host, 'X-TC-Action' => self::ACTION, 'X-TC-Region' => $region, 'X-TC-Timestamp' => (string) $timestamp, 'X-TC-Version' => self::VERSION, ]; } /** * @param array $response * @return array */ private function responseSummary(array $response): array { return [ 'request_id' => data_get($response, 'Response.RequestId'), 'error' => data_get($response, 'Response.Error'), 'send_status_set' => data_get($response, 'Response.SendStatusSet'), ]; } }