current(); if (($app['app_id'] ?? '') === '' || ($app['app_secret'] ?? '') === '') { UtilsService::getInstance()->errorThrow('小程序 AppID/AppSecret 未配置'); } $result = Http::asJson()->get($this->baseUrl . 'sns/jscode2session', [ 'appid' => $app['app_id'], 'secret' => $app['app_secret'], 'js_code' => $code, 'grant_type' => 'authorization_code', ])->json(); if (!empty($result['errcode'])) { UtilsService::getInstance()->errorThrow('微信登录失败:' . ($result['errmsg'] ?? '未知错误')); } if (empty($result['openid'])) { UtilsService::getInstance()->errorThrow('微信登录失败:未拿到 openid'); } $result['app_code'] = (string) ($app['code'] ?? ''); return $result; } /** * 手机号快速验证:前端拿到的 code 换真实号码 */ public function getPhoneNumber(string $phoneCode): string { $token = $this->accessToken(); $result = Http::asJson()->post( $this->baseUrl . 'wxa/business/getuserphonenumber?access_token=' . $token, ['code' => $phoneCode] )->json(); if (!empty($result['errcode'])) { UtilsService::getInstance()->errorThrow('获取手机号失败:' . ($result['errmsg'] ?? '未知错误')); } return (string) ($result['phone_info']['phoneNumber'] ?? ''); } /** * 接口调用凭证,按 appid 缓存 */ public function accessToken(): string { $app = WxAppService::getInstance()->current(); $redis = RedisService::getInstance()->init(config('nl.redis.wechat_token')); $cached = $redis->get($app['app_id']); if (!empty($cached)) { return (string) $cached; } $result = Http::asJson()->get($this->baseUrl . 'cgi-bin/token', [ 'grant_type' => 'client_credential', 'appid' => $app['app_id'], 'secret' => $app['app_secret'], ])->json(); if (empty($result['access_token'])) { UtilsService::getInstance()->errorThrow('获取 access_token 失败:' . ($result['errmsg'] ?? '未知错误')); } // 提前 300 秒过期,避免临界点上拿到即将失效的 token $redis->set($app['app_id'], $result['access_token'], max(60, (int) ($result['expires_in'] ?? 7200) - 300)); return (string) $result['access_token']; } /** * 发送订阅消息。模板字段必须与公众平台里的关键词一致,对不上微信会拒收。 * 这里只尽力发送,失败不抛错,避免挡住收款/发货。 */ public function sendSubscribe(string $openId, string $templateId, string $page, array $data): bool { if ($openId === '' || $templateId === '') { return false; } try { $token = $this->accessToken(); $result = Http::asJson()->post( $this->baseUrl . 'cgi-bin/message/subscribe/send?access_token=' . $token, [ 'touser' => $openId, 'template_id' => $templateId, 'page' => $page, 'data' => $data, 'miniprogram_state' => 'formal', 'lang' => 'zh_CN', ] )->json(); return empty($result['errcode']); } catch (\Throwable) { return false; } } }