const crypto = require('crypto') /** * 微信小程序统一消息推送入口(虚拟支付回调走此通道) * GET = 平台校验握手(echostr) * POST = 事件推送(XML:xpay_coin_pay_notify 代币支付 / xpay_goods_deliver_notify 道具发货 / 退款等) * * 环境变量: * WX_MSG_TOKEN 消息推送 Token(与 MP 后台「开发管理-消息推送」一致) * WX_MSG_AESKEY 安全模式 EncodingAESKey(暂用明文/兼容模式可不填) */ module.exports = function registerWxMsgRoutes(app) { const TOKEN = process.env.WX_MSG_TOKEN || '' function verifySignature(timestamp, nonce, signature) { if (!TOKEN) return false const sha1 = crypto.createHash('sha1') sha1.update([TOKEN, timestamp, nonce].sort().join('')) return sha1.digest('hex') === signature } /** 轻量 XML 字段提取(xpay 推送为扁平结构,够用且无外部依赖) */ function xmlFields(xml) { const out = {} const re = /<([A-Za-z0-9_]+)><\/\1>|<([A-Za-z0-9_]+)>([^<]*)<\/\3>/g let m while ((m = re.exec(xml))) out[m[1] || m[3]] = m[2] ?? m[4] ?? '' return out } app.get('/api/wx/msg', async (req, res) => { const { signature, timestamp, nonce, echostr } = req.query || {} if (verifySignature(timestamp, nonce, signature)) { return res.status(200).send(String(echostr || '')) } return res.status(403).send('forbidden') }) app.post('/api/wx/msg', async (req, res) => { try { const raw = typeof req.body === 'string' ? req.body : JSON.stringify(req.body || {}) const fields = xmlFields(String(raw)) const event = fields.Event || fields.event || '' console.log('[wx-msg] 收到推送 Event=' + event, JSON.stringify(fields).slice(0, 500)) // TODO(vpay接入):按事件分发改写用户星尘余额/发货状态 // xpay_coin_pay_notify 代币充值到账 // xpay_goods_deliver_notify 道具发货 // xpay_refund_notify 退款 return res.status(200).json({ ErrCode: 0, ErrMsg: '' }) } catch (e) { console.error('[wx-msg] 处理失败:', e.message) // 返回非0会在平台侧重推(最多15次) return res.status(200).json({ ErrCode: 0, ErrMsg: '' }) } }) }