PHP发送微信订阅消息

wen PHP项目 2

** PHP发送微信订阅消息完全指南:从API接入到模板管理实战

PHP发送微信订阅消息


目录导读

  1. 微信订阅消息与模板消息的区别
  2. PHP发送前的三大核心前置条件
  3. 获取Access Token的稳定代码方案
  4. 订阅消息发送的完整PHP代码实现
  5. 常见错误码与调试实战(含问答)
  6. 订阅消息的长期维护与用户退订策略

微信订阅消息是公众号触达用户的重要能力,尤其适合服务通知、订单状态、活动提醒等场景,与传统的“模板消息”不同,订阅消息需要用户主动点击“允许订阅”按钮,且一次性授权只能收到一次通知,许多PHP开发者在这条路上踩过坑,本文结合微信官方文档与社区实战经验,为你梳理一套严谨且可落地的发送方案。

微信订阅消息与模板消息的区别

很多新人混淆这两者,模板消息(已废弃)允许公众号在用户交互后7天内无限发送,而订阅消息分为“一次性订阅”和“长期订阅”(仅限部分政务/民生类目),对于普通开发者,99%的场景使用“一次性订阅”,即用户每次点击授权,你才能发送一条,PHP开发中,我们必须在用户点击“订阅按钮”时,通过前端把用户唯一的ticket参数传给后端,后端再用这个ticket去交换发送权限。

PHP发送前的三大核心前置条件

  • 条件1:已认证的公众号(个人订阅号无此权限)且开通了“订阅通知”功能。
  • 条件2:在公众平台挑选并添加合适的消息模板(订单发货提醒”),拿到模板ID,模板里的每个字段(如{{thing1.DATA}})要在代码中对应填入。
  • 条件3:用户的openid(微信用户唯一标识)和授权ticket(一次性有效,有效期约2小时)。

获取Access Token的稳定代码方案

Access Token(全局调用凭证)是每次发送请求的“钥匙”,且每日上限2000次(可缓存),强烈建议存入Redis或文件缓存,避免频繁刷接口,以下PHP示例使用文件缓存:

function getAccessToken($appid, $secret) {
    $file = '/tmp/wechat_token.json';  
    if (file_exists($file)) {
        $data = json_decode(file_get_contents($file), true);
        if ($data['expire_time'] > time()) return $data['access_token'];
    }
    $url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid={$appid}&secret={$secret}";
    $res = json_decode(file_get_contents($url), true);
    if (isset($res['access_token'])) {
        file_put_contents($file, json_encode([
            'access_token' => $res['access_token'],
            'expire_time' => time() + 7000  // 提前200秒过期,留缓冲
        ]));
        return $res['access_token'];
    }
    return false;  // 记录错误日志
}

订阅消息发送的完整PHP代码实现

关键接口:POST https://api.weixin.qq.com/cgi-bin/message/subscribe/send?access_token=ACCESS_TOKEN,请求体包含touser(用户openid)、template_idpage(跳转页面,可选)、data字段,以下为代码:

function sendSubscribeMsg($openid, $templateId, $data, $ticket, $page = '') {
    $accessToken = getAccessToken('你的APPID', '你的SECRET');
    $url = "https://api.weixin.qq.com/cgi-bin/message/subscribe/send?access_token={$accessToken}";
    $reqData = [
        'touser' => $openid,
        'template_id' => $templateId,
        'page' => $page,
        'data' => $data,
        'miniprogram_state' => 'formal', // 或'trial'
    ];
    // 重要:一次性订阅必须携带ticket(用户授权时产生的)
    if ($ticket) $reqData['ticket'] = $ticket;
    $options = ['http' => [
        'method' => 'POST',
        'header' => 'Content-Type: application/json',
        'content' => json_encode($reqData, JSON_UNESCAPED_UNICODE)
    ]];
    $context = stream_context_create($options);
    $result = file_get_contents($url, false, $context);
    return json_decode($result, true); // 正常返回 ['errcode'=>0,'errmsg'=>'ok']
}
// 调用示例:发送订单发货通知
$data = [
    'thing1' => ['value' => '您的包裹已打包'],
    'thing2' => ['value' => '顺丰速运'],
    'time3' => ['value' => '2025-01-15 14:00']
];
$res = sendSubscribeMsg('用户openid', '模板ID', $data, $ticketFrom前端, 'pages/order/detail');

常见错误码与调试实战(含问答)

问:调用接口总是返回 errcode 40003,提示 invalid openid,如何处理? 答:第一检查openid是否属于当前公众号(微信网页授权获取用户信息后的openid),第二,若你使用测试号,必须配置测试白名单,最重要的是——确认该用户是否真的点击了订阅按钮并生成有效的ticket,如果用户没授权,openid有效也会报错。

问:43101 错误(user refuse to accept the msg)是什么意思? 答:触发原因有二:一是用户点击了“拒收”或取消授权;二是你试图发送的ticket已经过期(有效期为2小时)或已被使用,这是最典型的坑。调试建议:在前端生成ticket后第一时间传给后端,后端应立即存储,并在几分钟内发起发送,如果业务逻辑是异步的(如用户订阅后10分钟才发货通知),你需要在用户授权后,立刻调用后台接口把ticket暂存(可用Redis设2小时过期),在发货的那一刻再取出使用。

问:发送返回 47003 参数错误,但我的data每个字段都填了? 答:重点检查模板字段类型,比如模板里是number类型,你传了字符串就会报错,使用JSON_UNESCAPED_UNICODE确保中文正常,模板内容中若包含“订单号”这种动态数据,注意值长度限制(如thing类型限20个字符),超出截断即可。

问:如何判断用户是否订阅过给重复消息? 答:没有主动查询接口,但你的业务系统可以维护一个subscribe_log表,记录openid、send_time和ticket_id,若用户没订阅,发送时会报43101,此时你可以引导用户再次点击订阅按钮。

订阅消息的长期维护与用户退订策略

一次性订阅模式下,每一次触达都来之不易,务必控制发送频率,只发高价值信息(如支付成功、物流异常),可以在公众号菜单设置“订阅管理”页面,展开其他通知项目的二次订阅,当用户不再需要时,可引导其点击“拒收”按钮,从产品层面减少骚扰,这也是微信生态健康运营的关键。


用PHP发送微信订阅消息看似繁琐,核心在于理解“授权-换取ticket-发送”的生命周期,建议在测试环境中使用微信公众平台的“接口调试工具”验证数据格式,实际部署时,务必把getAccessToken函数加上本地缓存与异常重试机制,同时将所有返回码记录到日志(如monolog库),方便排查问题,未来微信可能开放长期订阅权限给更多行业,届时PHP开发者只需调整ticket校验规则即可平滑升级,掌握这套底层逻辑,其他消息类型(如客服消息)也能触类旁通。

如果本文对你有帮助,欢迎收藏转发,让更多后端开发者落地微信订阅功能。

抱歉,评论功能暂时关闭!