nodejs实现微信支付退款


这里只涉及到node后端代码逻辑,前端只需要调用退款接口,传递订单号给后端。后端去根据订单号查询原订单信息

一、准备工作

1、商户证书配置
微信商户平台 → 账户中心 → API安全 → 下载以下文件:
API 证书(.p12 文件):用于退款接口双向认证
API 密钥(32位字符串):与支付功能共用
将证书文件(如 apiclient_cert.p12)保存到项目安全目录(如 certs/),切勿提交到代码仓库。
2、依赖安装

npm install axios xml2js fs https

二、退款接口核心实现

1.退款请求代码
// src/services/wechat-refund.js
const crypto = require('crypto');
const axios = require('axios');
const { parseStringPromise } = require('xml2js');
const fs = require('fs');
const path = require('path');
const config = require('../config/wechat-config');
// 加载商户证书(需绝对路径)
const certPath = path.resolve(__dirname, '../certs/apiclient_cert.p12');

/**
 * 微信支付退款
 * @param {Object} refundData 退款参数
 * @returns {Promise<Object>} 退款结果
 */
async function requestRefund(refundData) {
  // 基本参数
  const params = {
    appid: config.appId,
    mch_id: config.mchId,
    nonce_str: Math.random().toString(36).substr(2, 15), // 随机字符串生成函数
    out_trade_no: refundData.out_trade_no, // 原支付订单号
    out_refund_no: `REFUND_${Date.now()}`, // 退款单号(需唯一)
    total_fee: refundData.total_fee, // 原订单金额(单位:分)
    refund_fee: refundData.refund_fee, // 退款金额(单位:分)
    notify_url: config.refundNotifyUrl // 退款结果通知地址(可选)
  };
  // 生成签名
  params.sign = createSign(params);
  // 构建 XML 请求体
  const builder = new xml2js.Builder({ cdata: true, explicitArray: false });
  const xmlData = builder.buildObject({ xml: params });
  try {
    // 发送退款请求(需携带证书)
    const response = await axios.post(
      'https://api.mch.weixin.qq.com/secapi/pay/refund',
      xmlData,
      {
        headers: { 'Content-Type': 'application/xml' },
        httpsAgent: new https.Agent({
          pfx: fs.readFileSync(certPath),
          passphrase: config.mchId // 证书密码通常是商户号
        })
      }
    );
    // 解析 XML 响应
    const result = await parseXml(response.data);
    if (result.return_code === 'SUCCESS' && result.result_code === 'SUCCESS') {
      return { success: true, data: result };
    } else {
      return { success: false, error: result.err_code_des };
    }
  } catch (error) {
    throw new Error(`退款请求失败: ${error.message}`);
  }
}
// XML 解析
async function parseXml(xml) {
    try {
      const result = await parseStringPromise(xml);
      const formatted = {};
      for (const [key, value] of Object.entries(result.xml)) {
        formatted[key] = value[0];
      }
      return formatted;
    } catch (error) {
      throw new Error('XML 解析失败');
    }
}
// 生成签名
function createSign(params) {
  const stringA = Object.keys(params)
    .filter(key => params[key] !== '' && key !== 'sign') // 过滤空值和sign字段
    .sort() // 按ASCII码升序排序
    .map(key => `${key}=${params[key]}`)
    .join('&');
  
  const stringSignTemp = stringA + `&key=${config.apiKey}`;
  const sign = crypto.createHash('md5').update(stringSignTemp).digest('hex').toUpperCase();
  return sign;
}
module.exports = { requestRefund };

2.业务层调用实例

// app.js
const { requestRefund } = require('../services/wechat-refund');
app.post('/api/refund', async (req, res) => {
  try {
    const { orderId, refundAmount } = req.body;
    // 1. 查询原订单信息(从数据库获取)
    const order = await db.getOrderById(orderId);
    if (!order || order.status !== 'PAID') {
      return res.status(400).json({ code: -1, msg: '订单不可退款' });
    }
    // 2. 调用退款接口
    const refundResult = await requestRefund({
      out_trade_no: order.out_trade_no,
      total_fee: order.total_fee,
      refund_fee: refundAmount
    });
    if (refundResult.success) {
      // 3. 更新数据库退款状态
      await db.updateOrderRefund(orderId, 'PROCESSING');
      res.json({ code: 0, data: refundResult.data });
    } else {
      res.status(500).json({ code: -1, msg: refundResult.error });
    }
  } catch (error) {
    res.status(500).json({ code: -1, msg: error.message });
  }
});
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

F2E_zeke

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值