$appid, 'secret' => $secret, 'js_code' => $code, 'grant_type' => 'authorization_code', ]); $ch = curl_init($url); curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_CONNECTTIMEOUT => 5, CURLOPT_TIMEOUT => 10, CURLOPT_SSL_VERIFYPEER => true, CURLOPT_SSL_VERIFYHOST => 2, CURLOPT_FOLLOWLOCATION => false, ]); $raw = curl_exec($ch); $status = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); if ($raw === false || $status !== 200) { throw new ApiException(50302, '微信登录服务暂时不可用', 503); } $data = json_decode($raw, true); if (!is_array($data)) { throw new ApiException(50302, '微信登录服务暂时不可用', 503); } $openid = $data['openid'] ?? null; if (!is_string($openid) || $openid === '' || strlen($openid) > 128) { throw $this->failure((int) ($data['errcode'] ?? 0)); } // session_key and unionid are deliberately neither returned nor logged. return [ 'appid' => $appid, 'openid' => $openid, ]; } /** * WeChat reports credential problems with HTTP 200 plus errcode, so an expired code, a * reused code and an over-quota app would otherwise all collapse into one message and * leave nothing to search for in the logs. */ private function failure(int $errcode): ApiException { Log::warning('mini_api.wechat_login', ['errcode' => $errcode]); return match ($errcode) { 40029, 40163 => new ApiException(40102, '微信登录凭据已失效,请重新登录', 401), 40226, 45009, 45011 => new ApiException(42901, '微信登录请求过于频繁,请稍后再试', 429, ['retry_after' => 60]), 40013, 40125 => new ApiException(50301, '微信登录配置无效,请联系平台管理员', 503), default => new ApiException(40102, '微信登录凭据无效,请重新登录', 401), }; } }