113 lines
5.3 KiB
JavaScript
113 lines
5.3 KiB
JavaScript
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: '' })
|
||
}
|
||
})
|
||
}
|