微信支付接入实战:从零到上线,前端+后端完整指南

0 阅读6分钟

微信支付已成为移动端最主流的支付方式之一。本文将手把手带你完成微信支付的接入,涵盖公众号支付、扫码支付、小程序支付等场景,包括前端调起支付、后端签名、支付回调处理等核心环节。


📦 一、准备工作

在开始编码之前,你需要完成以下准备工作:

1. 注册商户号

2. 获取 API 密钥(APIv2)

  • 在商户平台「账户中心」->「API安全」中设置 APIv2 密钥(32位字符串),用于签名。

3. 配置应用

根据支付场景,需要配置对应的应用:

  • JSAPI 支付(公众号支付):在公众号后台配置网页授权域名、支付授权目录。
  • 扫码支付:配置支付回调 URL。
  • 小程序支付:在小程序后台绑定微信支付商户号。

4. 下载证书(可选)

如需使用退款等高级接口,需要下载商户证书(p12 或 pem 格式)。


🔁 二、支付流程概览

微信支付的标准流程如下:

用户 -> 商户前端 -> 商户后端 -> 微信支付API -> 商户后端 -> 前端调起支付 -> 用户支付 -> 支付结果回调
  1. 前端请求下单:用户点击支付按钮,前端向商户后端发起订单创建请求。
  2. 后端统一下单:商户后端调用微信支付【统一下单】接口,获取预支付交易会话标识 prepay_id
  3. 返回支付参数:后端根据 prepay_id 生成前端调起支付所需的签名参数,返回给前端。
  4. 前端调起支付:前端调用微信 JSAPI / 小程序 API 发起支付。
  5. 用户支付:用户输入密码完成支付。
  6. 支付结果通知:微信服务器异步通知商户后端支付结果,商户更新订单状态。

⚙️ 三、后端统一下单(以 Node.js + JSAPI 为例)

1. 安装依赖

npm install axios crypto xml2js

2. 生成签名函数

微信支付使用 MD5HMAC-SHA256 签名。这里以 MD5 为例:

const crypto = require('crypto');

function sign(params, apiKey) {
  // 1. 按参数名 ASCII 排序
  const sortedKeys = Object.keys(params).sort();
  let stringA = '';
  for (const key of sortedKeys) {
    if (params[key] !== '' && params[key] !== undefined && key !== 'sign') {
      stringA += `${key}=${params[key]}&`;
    }
  }
  // 2. 拼接 key
  const stringSignTemp = stringA + `key=${apiKey}`;
  // 3. MD5 签名并转大写
  return crypto.createHash('md5').update(stringSignTemp, 'utf8').digest('hex').toUpperCase();
}

3. 统一下单接口

const axios = require('axios');
const { parseString } = require('xml2js');

// 微信支付统一下单 URL
const UNIFIED_ORDER_URL = 'https://api.mch.weixin.qq.com/pay/unifiedorder';

async function unifiedOrder(orderParams) {
  const { appid, mch_id, nonce_str, body, out_trade_no, total_fee, spbill_create_ip, trade_type, openid, notify_url } = orderParams;
  
  const params = {
    appid,
    mch_id,
    nonce_str,
    body,
    out_trade_no,
    total_fee,
    spbill_create_ip,
    notify_url,
    trade_type,
    openid,  // trade_type=JSAPI 时必传
  };

  // 生成签名
  params.sign = sign(params, API_KEY);

  // 将参数转为 XML
  const xml = buildXML(params);

  // 发送请求
  const response = await axios.post(UNIFIED_ORDER_URL, xml, {
    headers: { 'Content-Type': 'text/xml' }
  });

  // 解析返回的 XML
  const result = await parseXML(response.data);
  if (result.xml.return_code[0] === 'SUCCESS' && result.xml.result_code[0] === 'SUCCESS') {
    // 返回 prepay_id 给前端
    return {
      prepay_id: result.xml.prepay_id[0],
      nonce_str: result.xml.nonce_str[0],
    };
  } else {
    throw new Error(result.xml.return_msg[0] || result.xml.err_code_des[0]);
  }
}

辅助函数(XML 构建与解析):

function buildXML(params) {
  let xml = '<xml>';
  for (const key in params) {
    xml += `<${key}>${params[key]}</${key}>`;
  }
  xml += '</xml>';
  return xml;
}

function parseXML(xml) {
  return new Promise((resolve, reject) => {
    parseString(xml, { explicitArray: true, trim: true }, (err, result) => {
      if (err) reject(err);
      else resolve(result);
    });
  });
}

4. 在路由中调用

app.post('/api/create-order', async (req, res) => {
  const { openid, totalFee, orderNo } = req.body;
  const nonce_str = generateNonceStr();
  const ip = req.ip; // 获取客户端 IP

  const orderParams = {
    appid: APPID,
    mch_id: MCHID,
    nonce_str,
    body: '商品描述',
    out_trade_no: orderNo,
    total_fee: totalFee,  // 单位:分
    spbill_create_ip: ip,
    notify_url: 'https://yourdomain.com/api/pay-callback',
    trade_type: 'JSAPI',
    openid,
  };

  try {
    const { prepay_id, nonce_str: wxNonceStr } = await unifiedOrder(orderParams);
    // 生成前端调起支付所需的参数
    const payParams = {
      appId: APPID,
      timeStamp: Math.floor(Date.now() / 1000).toString(),
      nonceStr: wxNonceStr,
      package: `prepay_id=${prepay_id}`,
      signType: 'MD5',
    };
    // 对前端参数签名
    payParams.paySign = sign(payParams, API_KEY);
    res.json({ success: true, payParams });
  } catch (err) {
    res.status(500).json({ success: false, message: err.message });
  }
});

注意generateNonceStr 可随机生成 32 位字符串,例如 Math.random().toString(36).substr(2)


📱 四、前端调起支付

1. 公众号网页(JSAPI 支付)

在页面中引入微信 JS-SDK:

<script src="https://res.wx.qq.com/open/js/jweixin-1.6.0.js"></script>

调用后端接口获取支付参数后,使用 WeixinJSBridge 调起支付:

async function pay() {
  const { data } = await axios.post('/api/create-order', {
    openid: getOpenId(),
    totalFee: 100,  // 1元
    orderNo: generateOrderNo()
  });

  if (data.success) {
    const { appId, timeStamp, nonceStr, package: prepayPackage, signType, paySign } = data.payParams;
    // 调用微信支付
    WeixinJSBridge.invoke(
      'getBrandWCPayRequest',
      {
        appId,
        timeStamp,
        nonceStr,
        package: prepayPackage,
        signType,
        paySign,
      },
      (res) => {
        if (res.err_msg === 'get_brand_wcpay_request:ok') {
          alert('支付成功');
        } else {
          alert('支付失败');
        }
      }
    );
  }
}

2. 小程序支付

在小程序端调用 wx.requestPayment

wx.request({
  url: 'https://yourdomain.com/api/create-order',
  method: 'POST',
  data: {
    openid: app.globalData.openid,
    totalFee: 100,
    orderNo: 'ORDER_' + Date.now()
  },
  success(res) {
    const payParams = res.data.payParams;
    wx.requestPayment({
      timeStamp: payParams.timeStamp,
      nonceStr: payParams.nonceStr,
      package: payParams.package,
      signType: payParams.signType,
      paySign: payParams.paySign,
      success: () => {
        console.log('支付成功');
      },
      fail: (err) => {
        console.error('支付失败', err);
      }
    });
  }
});

3. 扫码支付(PC 网站)

  • 统一下单时 trade_type 设为 NATIVE,返回的 code_url 是一个二维码链接。
  • 前端使用 QRCode 库生成二维码,用户扫码支付后,轮询查询订单状态。

🔔 五、支付结果通知

微信支付完成后,会向 notify_url 发起 POST 请求(XML 格式),商户需返回 <xml><return_code><![CDATA[SUCCESS]]></return_code></xml> 表示接收成功。

1. 回调接口示例

app.post('/api/pay-callback', async (req, res) => {
  // 获取 XML 数据
  let xml = '';
  req.on('data', chunk => xml += chunk);
  req.on('end', async () => {
    const result = await parseXML(xml);
    const { return_code, out_trade_no, transaction_id, total_fee } = result.xml;
    if (return_code[0] === 'SUCCESS') {
      // 验证签名
      const sign = result.xml.sign[0];
      const params = { ...result.xml };
      delete params.sign;
      const isValid = sign === sign(params, API_KEY);
      if (isValid) {
        // 更新订单状态
        await updateOrderStatus(out_trade_no[0], transaction_id[0], total_fee[0]);
        // 返回成功应答
        res.send('<xml><return_code><![CDATA[SUCCESS]]></return_code></xml>');
      } else {
        res.send('<xml><return_code><![CDATA[FAIL]]></return_code></xml>');
      }
    } else {
      res.send('<xml><return_code><![CDATA[FAIL]]></return_code></xml>');
    }
  });
});

注意:回调接口必须支持公网访问,且使用 HTTPS。


🔒 六、安全与注意事项

1. 签名算法统一

  • 建议使用 HMAC-SHA256,比 MD5 更安全。在统一下单时传递 sign_type=HMAC-SHA256
  • 签名函数需相应修改。

2. 防止重复通知

  • 微信可能会多次通知,必须通过 transaction_idout_trade_no 幂等处理,避免重复更新订单。

3. 商户端防篡改

  • 前端返回的支付参数不要信任,必须后端签名。
  • 金额等关键信息在后端下单时确定,不可由前端传递。

4. 超时处理

  • 统一下单接口可设置 time_expire 参数,超过时间未支付则关闭订单。

5. 日志记录

  • 记录支付请求、回调的详细日志,便于排查问题。

6. 证书安全

  • APIv2 密钥和证书切勿提交到代码仓库,应使用环境变量或密钥管理服务。

📚 七、总结

本文以 Node.js 为例,详细介绍了微信支付的接入流程,从统一下单到前端调起支付,再到异步通知处理。虽然代码以 JSAPI 支付为主,但扫码支付、小程序支付的思路类似,只需调整 trade_type 和前端调用方式即可。

微信支付官方提供了丰富的文档和 SDK,但在实际开发中,仍需要注意签名、安全、异常处理等细节。希望本文能帮助你快速接入微信支付,让资金流转更加顺畅。


参考资料


🚀 前端系统进阶

一站式前端面试解决方案


别再零散刷题了,构建你的前端知识体系

1000+ 精选面试题 | 22+ 技术领域 | 大厂真题全覆盖


✨ 核心优势

  • 📚 海量题库:JavaScript、Vue、React、TS、算法……一网打尽
  • 🎓 深度解析:不只是答案,更有思路与方法
  • 🗺️ 系统学习:从基础到源码,构建完整知识框架
  • 🏆 大厂真题:阿里、腾讯、字节真实面试题

🎯 适合人群

应届生 · 跳槽者 · 转行开发者 · 冲刺大厂


立即开始

让每一次面试,都成为展示实力的舞台!

👉 开始学习


image.png