/** * 订阅消息统一入口 * * ⚠️ 最重要的约束:wx.requestSubscribeMessage 和 wx.openSetting 必须由真实点击手势 * 直接触发(基础库 2.8.2+),且调用前不能有 await 等异步操作,否则手势上下文丢失、 * 调用必然失败(报 fail can only be invoked by user TAP gesture)。 * 所以本模块的 requestAndReport 必须在 bindtap 处理函数里同步调用, * 用户状态要提前用 getStatus 查好缓存起来,不能现查现用。 */ const sync = require('./sync') const TEMPLATE_ID = '6J7Stt-lu7DKU6jblJ0nZGq_D81z5glnksf7qWfy5Yw' /** * 查询用户当前的订阅设置状态 * itemSettings 只包含用户勾选过「总是保持以上选择」的模板,据此可判断调用会不会弹窗。 * @returns {Promise} * silent — 已长期同意,调用不弹窗,可在任意点击上搭车静默补额度 * willPrompt — 没勾过,调用会弹窗,只在用户主动点击时才调 * rejected — 已长期拒绝或被封禁,再调也没用,需引导去设置页 * mainSwitchOff — 通知总开关关闭,需引导去设置页 * unknown — 查询失败,按 willPrompt 保守处理 */ function getStatus() { return new Promise((resolve) => { wx.getSetting({ withSubscriptions: true, success: (res) => { const s = res.subscriptionsSetting || {} if (s.mainSwitch === false) return resolve('mainSwitchOff') const item = s.itemSettings && s.itemSettings[TEMPLATE_ID] if (item === 'accept') return resolve('silent') if (item === 'reject' || item === 'ban') return resolve('rejected') resolve('willPrompt') }, fail: () => resolve('unknown') }) }) } /** * 请求订阅并把新增额度上报后端 * 必须在 tap handler 中同步调用(本函数第一条语句就是 wx.requestSubscribeMessage) * @returns {Promise} 用户是否同意 */ function requestAndReport() { return new Promise((resolve) => { wx.requestSubscribeMessage({ tmplIds: [TEMPLATE_ID], success: (res) => { const accepted = res[TEMPLATE_ID] === 'accept' if (!accepted) return resolve(false) // 走同步队列上报:失败会自动入队,下次启动 flush。 // 不能静默吞掉——用户已经同意、微信侧额度确实 +1 了,我们漏记会导致余额低估, // 进而白白跳过本可以发出去的提醒。 sync.syncOrEnqueue({ kind: 'subscribe', action: 'grant', data: { count: 1 } }) .then(() => resolve(true)) }, fail: (err) => { console.warn('[subscribe] 订阅失败', err) resolve(false) } }) }) } module.exports = { TEMPLATE_ID, getStatus, requestAndReport }