$conf['applets_appId'], 'secret' => $conf['applets_appSecret'], // 返回数据类型 array | xml 'response_type' => 'array', ]; self::$app = Factory::miniProgram($config); } return self::$app; } /** * 生成微信小程序链接 * * @param string $path * @param string $sourceCode * @param bool $tokenRefresh 是否强制刷新access_token 默认false 非特殊条件请勿设为true * @return string * @throws GuzzleException * @throws HttpException * @throws InvalidArgumentException * @throws InvalidConfigException * @throws RuntimeException * @throws \Psr\SimpleCache\InvalidArgumentException */ public static function generateActivityUrl(string $path, string $sourceCode, bool $tokenRefresh = false): string { $accessToken = self::getInstance()->access_token; $client = new Client(); $url = 'https://api.weixin.qq.com/wxa/generate_urllink?access_token='; $params = []; $query = ''; $params['path'] = 'pages/tabbar/pagehome/pagehome';//首页路径 if (!empty($path)) { $pathArr = explode('?', $path); $query = isset($pathArr[1]) ? $pathArr[1].'&' : ''; $params['path'] = $pathArr[0]; } $params['query'] = $query.'channel=activity&source_code='.$sourceCode; $jsonStr = !empty($params) ? json_encode($params, JSON_UNESCAPED_UNICODE) : ''; Log::info('【小程序链接生成请求参数】'.$jsonStr); $response = $client->request('POST', $url.($accessToken->getToken($tokenRefresh)['access_token'] ?? ''), [ 'body' => $jsonStr ]); $res = json_decode($response->getBody(), true); Log::info('【小程序链接生成响应】'.json_encode($res, JSON_UNESCAPED_UNICODE)); if ($res['errcode'] == 0) { return $res['url_link'] ?? ''; } if ($res['errcode'] == 40001 && $tokenRefresh == false) { // 可能是token过期 刷新一次 // tokenRefresh 防止无限循环 return self::generateActivityUrl($path, $sourceCode, true); } throw new Exception('链接生成失败 '.$res['errmsg'] ?? ''); } /** * 发送订阅消息 * * 注1:订阅消息需要在用户预约后订阅成功后才能发送,每次订阅只能推送一条订阅消息; * 注2:用户取消订阅后无法下发订阅消息 * * @param string $openid 接收者openid * @param array $data ['template_id' => '模版ID', 'msg' => msgBody, 'page' => '小程序跳转页面', 'state' => '小程序类型'] * 消息体 样例:msgBody=['date1'=>date('Y年m月d日'), 'thing2'=>'植发', 'character_string12'=>'8:30 ~ 9:30'] * state 跳转小程序类型。 developer为开发版;trial=体验版;formal为正式版;默认为正式版 * @return boolean * @throws RepositoryException|GuzzleException */ public static function sendSubscribeMessage(string $openid, array $data): bool { try { $wxApp = self::getInstance(); $msgBody = $data['msg'] ?? []; $templateId = $data['template_id'] ?? ''; $page = $data['page'] ?? ''; $state = $data['state'] ?? 'formal'; $state = in_array($state, ['developer', 'trial', 'formal']) ? $state : ''; if (empty($msgBody)) { throw new RepositoryException('订阅消息不能为空'); } if (empty($templateId)) { throw new RepositoryException('消息模版不能为空'); } $data = [ // 所需下发的订阅模板id 'template_id' => $templateId, // 接收者(用户)的 openid 'touser' => $openid, // 点击模板卡片后的跳转页面,仅限本小程序内的页面。支持带参数,(示例index?foo=bar)。该字段不填则模板无跳转。 'page' => $page, //跳转小程序类型:developer=开发版;trial=体验版;formal=正式版;默认为正式版 'miniprogram_state' => $state, // 模板内容,格式形如 { "key1": { "value": any }, "key2": { "value": any } } 'data' => $msgBody ]; Log::info('[发送订阅消息]:'.json_encode($data, JSON_UNESCAPED_UNICODE)); $res = $wxApp->subscribe_message->send($data); Log::info('[发送回执]'.json_encode($res, JSON_UNESCAPED_UNICODE)); if (isset($res['errcode']) && $res['errcode'] == 0) { return true; } //40003 touser字段openid为空或者不正确 //40037 订阅模板id为空不正确 //43101 用户拒绝接受消息,如果用户之前曾经订阅过,则表示用户取消了订阅关系 //47003 模板参数不准确,可能为空或者不满足规则,errmsg会提示具体是哪个字段出错 //41030 page路径不正确,需要保证在现网版本小程序中存在,与app.json保持一致 if ($res['errcode'] == '43101') { throw new RepositoryException('用户未订阅或订阅次数已用完'); } throw new RepositoryException('【订阅消息发送失败】code:'.$res['errcode'].' msg:'.$res['errmsg']); } catch (RepositoryException $e) { throw $e; } catch (Exception $e) { Log::error('【订阅消息发送失败】:'.$e->getMessage()); return false; } } /** * 批量发送订阅消息 * * 注1:订阅消息需要在用户预约后订阅成功后才能发送,每次订阅只能推送一条订阅消息; * 注2:用户取消订阅后无法下发订阅消息 * * @param array $openidList 接收者openid列表 * @param array $data ['template_id' => '模版ID', 'msg' => msgBody, 'page' => '小程序跳转页面', 'state' => '小程序类型', 'message_id' => '消息ID'] * 消息体 样例:msgBody=['date1'=>date('Y年m月d日'), 'thing2'=>'植发', 'character_string12'=>'8:30 ~ 9:30'] * state 跳转小程序类型。 developer为开发版;trial=体验版;formal为正式版;默认为正式版 * @throws GuzzleException * @throws InvalidArgumentException * @throws InvalidConfigException * @throws RepositoryException */ public static function sendBatchSubscribeMessage(int $messageId, array $openidList, array $data) { try { $wxApp = self::getInstance(); $msgBody = $data['msg'] ?? []; $templateId = $data['template_id'] ?? ''; $page = $data['page'] ?? ''; $state = $data['state'] ?? 'formal'; $state = in_array($state, ['developer', 'trial', 'formal']) ? $state : ''; if (empty($msgBody)) { throw new RepositoryException('订阅消息不能为空'); } if (empty($templateId)) { throw new RepositoryException('消息模版不能为空'); } if (empty($openidList)) { throw new RepositoryException('消息模版接受人列表不能为空'); } $resData = []; $now = date('Y-m-d H:i:s'); $resInfo = [ 'openid' => '', 'status' => SubscribeMessageLog::STATUS_SUCCESS, 'message_id' => $messageId, 'template_id' => $templateId, 'created_at' => $now, 'data' => json_encode($msgBody, JSON_UNESCAPED_UNICODE), ]; foreach ($openidList as $openid) { $resInfo['openid'] = $openid; $sendData = [ // 所需下发的订阅模板id 'template_id' => $templateId, // 接收者(用户)的 openid 'touser' => $openid, // 点击模板卡片后的跳转页面,仅限本小程序内的页面。支持带参数,(示例index?foo=bar)。该字段不填则模板无跳转。 'page' => $page, //跳转小程序类型:developer=开发版;trial=体验版;formal=正式版;默认为正式版 'miniprogram_state' => $state, // 模板内容,格式形如 { "key1": { "value": any }, "key2": { "value": any } } 'data' => $msgBody ]; Log::info('[发送订阅消息]:'.json_encode($data, JSON_UNESCAPED_UNICODE)); $res = $wxApp->subscribe_message->send($sendData); Log::info('[发送回执]'.json_encode($res, JSON_UNESCAPED_UNICODE)); if (isset($res['errcode']) && $res['errcode'] == 0) { $resData[] = $resInfo; continue; } $resInfo['status'] = SubscribeMessageLog::STATUS_FAIL; //40003 touser字段openid为空或者不正确 //40037 订阅模板id为空不正确 //43101 用户拒绝接受消息,如果用户之前曾经订阅过,则表示用户取消了订阅关系 //47003 模板参数不准确,可能为空或者不满足规则,errmsg会提示具体是哪个字段出错 //41030 page路径不正确,需要保证在现网版本小程序中存在,与app.json保持一致 if ($res['errcode'] == '43101') { $resInfo['remarks'] = '用户未订阅或订阅次数已用完'; $resData[] = $resInfo; continue; } $resInfo['remarks'] = $res['errcode'].' msg:'.$res['errmsg']; $resData[] = $resInfo; (new SubscribeMessageLog())->saveAll($resData); throw new RepositoryException('【订阅消息发送失败】code:'.$res['errcode'].' msg:'.$res['errmsg']); } (new SubscribeMessageLog())->saveAll($resData); } catch (RepositoryException $e) { throw $e; } catch (Exception $e) { Log::error('【订阅消息发送失败】:'.$e->getMessage()); throw $e; } } // 订阅消息模版 public static function msgTemplateList(): array { return [ ['name' => '新活动通知', 'value' => self::SUBSCRIBE_TPL_ACTIVITY], ['name' => '未读消息通知', 'value' => self::SUBSCRIBE_TPL_NEW_INFO_NOTICE], ]; } // 订阅消息参数列表 public static function msgTemplateParams(): array { return [ self::SUBSCRIBE_TPL_ACTIVITY => [ [ 'name' => '活动名称', 'type' => 'string', 'value' => 'thing1', ], [ 'name' => '活动时间', 'type' => 'datetime', 'value' => 'time4', ], [ 'name' => '温馨提示', 'type' => 'string', 'value' => 'thing5', ], ], self::SUBSCRIBE_TPL_NEW_INFO_NOTICE => [ [ 'name' => '消息内容', 'type' => 'string', 'value' => 'thing2', ], [ 'name' => '时间', 'type' => 'datetime', 'value' => 'time3', ], [ 'name' => '备注', 'type' => 'string', 'value' => 'thing5', ], ], ]; } }