$now + 300) { return ['access_token' => self::$cachedAccessToken]; } $appId = config('wechat.app_id'); $appSecret = config('wechat.app_secret'); if (empty($appId) || empty($appSecret)) { return ['errcode' => -1, 'errmsg' => '未配置微信小程序 app_id 或 app_secret']; } $url = self::$tokenUrl . '?' . http_build_query([ 'grant_type' => 'client_credential', 'appid' => $appId, 'secret' => $appSecret, ]); $resp = @file_get_contents($url); if ($resp === false) { return ['errcode' => -2, 'errmsg' => '请求微信接口失败']; } $data = json_decode($resp, true); if (empty($data) || !is_array($data)) { return ['errcode' => -3, 'errmsg' => '微信接口返回异常']; } if (isset($data['errcode']) && $data['errcode'] !== 0) { return [ 'errcode' => (int) $data['errcode'], 'errmsg' => $data['errmsg'] ?? 'unknown', ]; } $token = $data['access_token'] ?? ''; $expiresIn = (int) ($data['expires_in'] ?? 7200); self::$cachedAccessToken = $token; self::$cachedAccessTokenExpire = $now + $expiresIn; return ['access_token' => $token]; } /** * 用 getPhoneNumber 回调里的 code 换取手机号 * @param string $code 小程序 button open-type="getPhoneNumber" 回调中的 detail.code * @return array{phoneNumber:string,purePhoneNumber:string,countryCode:string}|array{errcode:int,errmsg:string} */ public static function getPhoneNumber(string $code): array { $tokenResult = self::getAccessToken(); if (isset($tokenResult['errcode'])) { return $tokenResult; } $accessToken = $tokenResult['access_token']; $url = self::$getPhoneNumberUrl . '?access_token=' . urlencode($accessToken); $ctx = stream_context_create([ 'http' => [ 'method' => 'POST', 'header' => 'Content-Type: application/json', 'content' => json_encode(['code' => $code]), ], ]); $resp = @file_get_contents($url, false, $ctx); if ($resp === false) { return ['errcode' => -2, 'errmsg' => '请求微信接口失败']; } $data = json_decode($resp, true); if (empty($data) || !is_array($data)) { return ['errcode' => -3, 'errmsg' => '微信接口返回异常']; } if (isset($data['errcode']) && $data['errcode'] !== 0) { return [ 'errcode' => (int) $data['errcode'], 'errmsg' => $data['errmsg'] ?? 'unknown', ]; } $phoneInfo = $data['phone_info'] ?? []; $purePhoneNumber = $phoneInfo['purePhoneNumber'] ?? $phoneInfo['phoneNumber'] ?? ''; $phoneNumber = $phoneInfo['phoneNumber'] ?? $purePhoneNumber; $countryCode = $phoneInfo['countryCode'] ?? '86'; return [ 'phoneNumber' => $phoneNumber, 'purePhoneNumber' => $purePhoneNumber, 'countryCode' => $countryCode, ]; } /** * code 换取 openid、session_key(及 unionid) * @param string $code 小程序 wx.login 返回的 code * @return array{openid:string,session_key:string,unionid?:string}|array{errcode:int,errmsg:string} */ public static function jscode2session(string $code): array { $appId = config('wechat.app_id'); $appSecret = config('wechat.app_secret'); if (empty($appId) || empty($appSecret)) { return ['errcode' => -1, 'errmsg' => '未配置微信小程序 app_id 或 app_secret']; } $url = self::$jscode2sessionUrl . '?' . http_build_query([ 'appid' => $appId, 'secret' => $appSecret, 'js_code' => $code, 'grant_type' => 'authorization_code', ]); $resp = @file_get_contents($url); if ($resp === false) { return ['errcode' => -2, 'errmsg' => '请求微信接口失败']; } $data = json_decode($resp, true); if (empty($data) || !is_array($data)) { return ['errcode' => -3, 'errmsg' => '微信接口返回异常']; } if (isset($data['errcode']) && $data['errcode'] !== 0) { return [ 'errcode' => (int) $data['errcode'], 'errmsg' => $data['errmsg'] ?? 'unknown', ]; } return [ 'openid' => $data['openid'] ?? '', 'session_key' => $data['session_key'] ?? '', 'unionid' => $data['unionid'] ?? null, ]; } /** * 生成带参数的小程序码(永久有效),返回原始二进制或错误信息 * @param string $scene 最大 32 个可见字符,用于区分邀请人/渠道 * @param string $page 小程序页面路径,如 pages/index/index * @param int $width 小程序码宽度,默认 430 * @return array{binary:string}|array{errcode:int,errmsg:string} */ public static function getWxacodeUnlimited(string $scene, string $page, int $width = 430): array { $tokenResult = self::getAccessToken(); if (isset($tokenResult['errcode'])) { return $tokenResult; } $accessToken = $tokenResult['access_token']; $url = self::$getWxacodeUnlimitedUrl . '?access_token=' . urlencode($accessToken); $payload = [ 'scene' => mb_substr($scene, 0, 32), 'page' => $page, 'width' => $width, 'check_path' => false, ]; $ctx = stream_context_create([ 'http' => [ 'method' => 'POST', 'header' => 'Content-Type: application/json', 'content' => json_encode($payload, JSON_UNESCAPED_UNICODE), ], ]); $resp = @file_get_contents($url, false, $ctx); if ($resp === false) { return ['errcode' => -2, 'errmsg' => '请求微信接口失败']; } // 微信错误时返回 JSON,成功时返回图片二进制 $head = substr($resp, 0, 1); if ($head === '{' || $head === '[') { $data = json_decode($resp, true); if (is_array($data) && isset($data['errcode']) && $data['errcode'] !== 0) { return [ 'errcode' => (int) $data['errcode'], 'errmsg' => $data['errmsg'] ?? 'unknown', ]; } } return ['binary' => $resp]; } }