Files
ai-dimension/server/src/routes/wxmsg.js
T

113 lines
5.3 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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_]+)><!\[CDATA\[([\s\S]*?)\]\]><\/\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 {
// 数据格式推荐 JSON:express.json 已解析为对象;选 XML 时是字符串
let fields = {}
if (req.body && typeof req.body === 'object') {
fields = req.body
} else {
fields = xmlFields(String(req.body || ''))
}
const event = fields.Event || fields.event || ''
console.log('[wx-msg] 收到推送 Event=' + event, JSON.stringify(fields).slice(0, 500))
// 代币充值到账:改写用户星尘余额 + 订单状态
if (event === 'xpay_coin_pay_notify') {
try {
const { User, Order, CoinLedger } = require('../models')
const outTradeNo = fields.OutTradeNo || ''
// 微信回调格式:CoinInfo.Quantity(嵌套)——兼容 JSON 和 XML 两种
const coinInfo = fields.CoinInfo || fields.coinInfo || {}
const coins = parseInt(
(typeof coinInfo === 'object' ? coinInfo.Quantity : coinInfo) ||
fields.BuyQuantity || fields.buyQuantity || '0'
)
console.log(`[wx-msg] 充值回调解析: order=${outTradeNo} coins=${coins} coinInfo=${JSON.stringify(coinInfo)}`)
if (outTradeNo && coins > 0) {
// 先查订单,没有也直接给用户加币(Attach 里带了 uid)
const order = await Order.findOne({ orderNo: outTradeNo })
if (order && order.status === 'pending') {
await Order.updateOne({ _id: order._id }, { status: 'paid', paidAt: new Date() })
await User.updateOne({ _id: order.userId }, { $inc: { vpayCoins: coins } })
console.log(`[wx-msg] ✓ 星尘充值到账(有订单): ${outTradeNo} +${coins} → user=${order.userId}`)
} else {
// 订单可能未落库或已处理——从 Attach 里拿 uid 直接加
try {
const attach = typeof coinInfo === 'object' ? coinInfo.Attach || '' : ''
const uid = attach ? JSON.parse(attach).uid : ''
if (uid) {
await User.updateOne({ _id: uid }, { $inc: { vpayCoins: coins } })
if (order) await Order.updateOne({ _id: order._id }, { status: 'paid', paidAt: new Date() })
console.log(`[wx-msg] ✓ 星尘充值到账(从Attach): ${outTradeNo} +${coins} → user=${uid}`)
} else if (order) {
await Order.updateOne({ _id: order._id }, { status: 'paid', paidAt: new Date() })
await User.updateOne({ _id: order.userId }, { $inc: { vpayCoins: coins } })
console.log(`[wx-msg] ✓ 星尘充值到账(订单补偿): ${outTradeNo} +${coins}`)
}
} catch (e2) { console.error('[wx-msg] Attach解析失败:', e2.message) }
}
}
} catch (e) { console.error('[wx-msg] 充值到账失败:', e.message) }
}
// 退款:扣回星尘
if (event === 'xpay_refund_notify') {
try {
const { User, Order, CoinLedger } = require('../models')
const outTradeNo = fields.OutTradeNo || ''
const order = await Order.findOne({ orderNo: outTradeNo, status: 'paid' })
if (order) {
await Order.updateOne({ _id: order._id }, { status: 'refunded' })
const coins = (order.metadata || {}).coins || 0
if (coins > 0) await User.updateOne({ _id: order.userId }, { $inc: { vpayCoins: -coins } })
console.log(`[wx-msg] 星尘退款: ${outTradeNo} -${coins}`)
}
} catch (e) { console.error('[wx-msg] 退款处理失败:', e.message) }
}
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: '' })
}
})
}