true, 'enterprise' => true]; } public function validate_config() { $cfg = SettingsRuntime::get(); foreach (['secret_id', 'secret_key'] as $key) { if (empty($cfg['credentials'][$key])) { return new \WP_Error('aksrt_identity_tencent_credentials', __('腾讯云人脸核身凭证不完整', 'aksrt')); } } if (empty($cfg['personal_flow'])) { return new \WP_Error('aksrt_identity_tencent_rule', __('请配置腾讯云人脸核身 RuleId', 'aksrt')); } return true; } public function start_personal(array $request): ProviderResult { // Ref: https://cloud.tencent.com/document/product/1007/31816 $cfg = SettingsRuntime::get(); $payload = [ 'RuleId' => (string) $cfg['personal_flow'], 'TerminalType' => $this->terminal_type(), 'RedirectUrl' => esc_url_raw((string) ($request['return_url'] ?? '')), 'Extra' => (string) ($request['application_uuid'] ?? ''), ]; if (!empty($request['person_name'])) { $payload['Name'] = (string) $request['person_name']; } if (!empty($request['id_number'])) { $payload['IdCard'] = preg_replace('/\s+/', '', (string) $request['id_number']); } $response = $this->request('DetectAuth', $payload); if (is_wp_error($response)) { return ProviderResult::failed($response->get_error_code(), $response->get_error_message()); } $data = is_array($response['Response'] ?? null) ? $response['Response'] : []; if (!empty($data['Error'])) { return ProviderResult::failed((string) ($data['Error']['Code'] ?? 'tencent_error'), (string) ($data['Error']['Message'] ?? '')); } $biz_token = sanitize_text_field((string) ($data['BizToken'] ?? '')); $url = esc_url_raw((string) ($data['Url'] ?? '')); if ($biz_token === '' || $url === '') { return ProviderResult::failed('tencent_invalid_response', __('腾讯云未返回有效认证会话', 'aksrt')); } return ProviderResult::pending([ 'provider_session_id' => $biz_token, 'request_id' => sanitize_text_field((string) ($data['RequestId'] ?? '')), 'redirect_url' => $url, 'result_code' => 'session_created', 'expires_at' => gmdate('Y-m-d H:i:s', time() + ((int) $cfg['session_ttl_minutes'] * MINUTE_IN_SECONDS)), ]); } public function start_enterprise(array $request): ProviderResult { // Ref: https://cloud.tencent.com/document/product/866/129072 foreach (['company_name', 'company_code', 'person_name', 'id_number'] as $key) { if (empty($request[$key])) { return ProviderResult::failed('tencent_enterprise_fields', __('企业认证资料不完整', 'aksrt')); } } $response = $this->request_service('ocr', 'ocr.tencentcloudapi.com', '2018-11-19', 'VerifyBizLicenseEnterprise4', [ 'CreditCode' => preg_replace('/\s+/', '', (string) $request['company_code']), 'EntName' => (string) $request['company_name'], 'LrName' => (string) $request['person_name'], 'IdNum' => preg_replace('/\s+/', '', (string) $request['id_number']), ]); if (is_wp_error($response)) { return ProviderResult::failed($response->get_error_code(), $response->get_error_message()); } $data = is_array($response['Response'] ?? null) ? $response['Response'] : []; if (!empty($data['Error'])) { return ProviderResult::failed((string) ($data['Error']['Code'] ?? 'tencent_ocr_error'), (string) ($data['Error']['Message'] ?? '')); } if (!$this->enterprise_passed($data)) { return ProviderResult::failed('tencent_enterprise_mismatch', __('企业工商四要素或法人关系核验未通过', 'aksrt')); } return $this->start_personal($request); } public function query(array $application): ProviderResult { // Ref: https://cloud.tencent.com/document/product/1007/41957 $response = $this->request('GetDetectInfoEnhanced', [ 'BizToken' => (string) ($application['provider_session_id'] ?? ''), 'RuleId' => (string) ($application['provider_flow'] ?? ''), 'InfoType' => '0', ]); if (is_wp_error($response)) { return ProviderResult::pending(['result_code' => $response->get_error_code(), 'message' => __('认证结果暂未取回,请稍后重试', 'aksrt')]); } $data = is_array($response['Response'] ?? null) ? $response['Response'] : []; if (!empty($data['Error'])) { return ProviderResult::failed((string) ($data['Error']['Code'] ?? 'tencent_error'), (string) ($data['Error']['Message'] ?? '')); } $text = $data['Text'] ?? ($data['DetectInfo']['Text'] ?? []); if (is_string($text)) { $decoded_text = json_decode($text, true); $text = is_array($decoded_text) ? $decoded_text : []; } elseif (!is_array($text)) { $text = []; } $result = (string) ($text['ErrCode'] ?? ''); $live_status = (string) ($text['LiveStatus'] ?? ''); $compare_status = (string) ($text['Comparestatus'] ?? ''); if ($result === '0' && ($live_status === '' || $live_status === '0') && ($compare_status === '' || $compare_status === '0')) { return ProviderResult::passed(['request_id' => (string) ($data['RequestId'] ?? ''), 'result_code' => '0']); } $message = sanitize_text_field((string) ($text['ErrMsg'] ?? $text['LiveMsg'] ?? $text['Comparemsg'] ?? '')); if ($result === '') { return ProviderResult::pending(['result_code' => 'processing', 'message' => __('腾讯云仍在处理认证结果', 'aksrt')]); } return ProviderResult::failed($result, $message); } public function verify_callback(\WP_REST_Request $request): bool { unset($request); return false; } public function parse_callback(\WP_REST_Request $request): ProviderResult { unset($request); return ProviderResult::unsupported(__('腾讯云人脸核身使用服务端主动查询,不接收结果回调', 'aksrt')); } /** @return array|\WP_Error */ private function request(string $action, array $payload) { return $this->request_service(self::SERVICE, self::ENDPOINT, self::VERSION, $action, $payload); } /** @return array|\WP_Error */ private function request_service(string $service, string $host, string $version, string $action, array $payload) { $cfg = SettingsRuntime::get(); $endpoint = 'https://' . $host; $timestamp = time(); $date = gmdate('Y-m-d', $timestamp); $body = (string) wp_json_encode($payload, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); $canonical_headers = "content-type:application/json; charset=utf-8\nhost:{$host}\nx-tc-action:" . strtolower($action) . "\n"; $signed_headers = 'content-type;host;x-tc-action'; $canonical_request = "POST\n/\n\n{$canonical_headers}\n{$signed_headers}\n" . hash('sha256', $body); $scope = $date . '/' . $service . '/tc3_request'; $string_to_sign = "TC3-HMAC-SHA256\n{$timestamp}\n{$scope}\n" . hash('sha256', $canonical_request); $secret_key = (string) $cfg['credentials']['secret_key']; $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); $authorization = 'TC3-HMAC-SHA256 Credential=' . $cfg['credentials']['secret_id'] . '/' . $scope . ', SignedHeaders=' . $signed_headers . ', Signature=' . $signature; $headers = [ 'Authorization' => $authorization, 'Content-Type' => 'application/json; charset=utf-8', 'Host' => $host, 'X-TC-Action' => $action, 'X-TC-Timestamp' => (string) $timestamp, 'X-TC-Version' => $version, ]; if ($service !== self::SERVICE && !empty($cfg['region'])) { $headers['X-TC-Region'] = (string) $cfg['region']; } $response = wp_remote_post($endpoint, [ 'timeout' => 20, 'headers' => $headers, 'body' => $body, ]); if (is_wp_error($response)) { return new \WP_Error('tencent_network_error', __('腾讯云接口请求失败', 'aksrt')); } $decoded = json_decode((string) wp_remote_retrieve_body($response), true); return is_array($decoded) ? $decoded : new \WP_Error('tencent_invalid_json', __('腾讯云返回格式无效', 'aksrt')); } private function enterprise_passed(array $data): bool { if ((string) ($data['StatusCode'] ?? '') !== '0' || (string) ($data['VerifyResult'] ?? '') !== '1') { return false; } foreach (['IsCreditCodeConsistent', 'IsEntNameConsistent', 'IsLrNameConsistent', 'IsIdNumConsistent'] as $field) { $value = $data[$field] ?? null; if (!in_array($value, [true, 1, '1', 'true'], true)) { return false; } } $status = strtoupper((string) ($data['OperatingStatus'] ?? $data['RegStatus'] ?? '')); return !in_array($status, ['吊销', '注销', 'REVOKED', 'CANCELLED'], true); } private function terminal_type(): string { $ua = strtolower((string) ($_SERVER['HTTP_USER_AGENT'] ?? '')); return str_contains($ua, 'mobile') ? '1' : '2'; } }