diff --git a/server/src/routes/admin.js b/server/src/routes/admin.js index 0ff243e..973aaae 100755 --- a/server/src/routes/admin.js +++ b/server/src/routes/admin.js @@ -1294,7 +1294,20 @@ router.post('/vpay/orders/:id/refund', requirePermission('orders:write'), async const accessToken = tokenRes.access_token if (!accessToken) throw new Error('获取access_token失败: ' + JSON.stringify(tokenRes)) - const refundBody = JSON.stringify({ offerId: OFFER_ID, outTradeNo: order.orderNo }) + // refund_order 正确参数(snake_case,官方文档 https://developers.weixin.qq.com/miniprogram/dev/server/API/VirtualPayment/api_refund_order.html) + const refundOrderId = `RF${Date.now()}${require('crypto').randomBytes(3).toString('hex').toUpperCase()}` + const refundFee = Math.round(order.amount * 100) // 退款金额(分)= 订单金额 + const refundBody = JSON.stringify({ + openid: user.openid, + order_id: order.orderNo, // 商户单号(下单时的 outTradeNo) + refund_order_id: refundOrderId, // 本次退款单号(8-32位) + left_fee: refundFee, // 剩余可退金额(分) + refund_fee: refundFee, // 本次退款金额(分),须 ≤ left_fee + biz_meta: JSON.stringify({ admin: req.admin.username, reason: 'user_refund' }), + refund_reason: 0, // 0=用户申请退款 + req_from: 1, // 1=商户后台发起 + env: ENV, + }) const paySig = require('crypto').createHmac('sha256', APPKEY).update('/xpay/refund_order&' + refundBody).digest('hex') const wxRes = await fetch(`https://api.weixin.qq.com/xpay/refund_order?access_token=${accessToken}&pay_sig=${paySig}`, {