微信支付接口对接教程 - 完整解决方案与实战教程

在电商、SaaS 或 O2O 项目中,对接微信支付几乎是绕不开的一环。无论是 Native 扫码支付、JSAPI 支付还是小程序支付,开发者在联调阶段最常遇到的场景是:本地代码逻辑自测通过,但一到真实环境就报“签名错误”或“回调验签失败”,导致订单状态无法同步,用户已付款但系统仍显示待支付。这类问题往往不是业务逻辑写错,而是对微信支付 V3 接口的签名机制、证书管理和回调验签细节理解不到位。本文将从真实排坑角度出发,带你完整走一遍微信支付接口对接流程。

问题现象:签名与回调的典型报错

在接入微信支付 V3 接口时,以下三类报错占据了 90% 以上的联调时间:

原因分析:V3 接口的三大坑

微信支付 V3 相比 V2 最大的变化是全面采用 SHA256-RSA 签名和 AES-256-GCM 解密。很多开发者习惯性沿用 V2 的 MD5 签名思路,导致以下问题:

  1. 私钥格式错误:从微信商户平台下载的 apiclient_key.pem 是 PKCS#8 格式,但部分语言(如 Java)默认需要 PKCS#8 且不能带多余头尾,Node.js 则要求去掉 BEGIN/END 行。若直接读取整个文件字符串,签名必错。
  2. 待签名串拼接错误:V3 要求按 HTTP 方法、URL、时间戳、随机串、请求体逐行拼接,每行以 \n 结尾,最后一行也要有 \n。少一个换行符就会导致签名不一致。
  3. 平台证书未更新:微信支付平台证书会定期轮换,如果代码中硬编码了旧证书,回调验签必然失败。必须通过 /v3/certificates 接口动态获取并缓存。

解决方案(附完整代码)

下面以 Node.js + Express 为例,实现 Native 支付下单与回调验签的完整流程。其他语言逻辑一致,仅 API 调用方式不同。

1. 准备工作与配置

2. 核心代码:下单与签名

const crypto = require('crypto');
const fs = require('fs');
const axios = require('axios');

// 读取商户私钥,注意去除 PEM 头尾和换行
const privateKey = fs.readFileSync('./cert/apiclient_key.pem', 'utf8')
  .replace(/-----BEGIN PRIVATE KEY-----/, '')
  .replace(/-----END PRIVATE KEY-----/, '')
  .replace(/\n/g, '');

const mchid = process.env.MCHID;           // 商户号
const appid = process.env.APPID;           // 公众号或小程序 appid
const serialNo = process.env.SERIAL_NO;    // 商户证书序列号
const apiV3Key = process.env.API_V3_KEY;   // APIv3 密钥

// 生成随机字符串
function getNonceStr() {
  return crypto.randomBytes(16).toString('hex');
}

// 生成签名
function buildSignature(method, url, timestamp, nonceStr, body) {
  const message = `${method}\n${url}\n${timestamp}\n${nonceStr}\n${body}\n`;
  const sign = crypto.createSign('RSA-SHA256');
  sign.update(message);
  return sign.sign(privateKey, 'base64');
}

// 构造 Authorization 头
function buildAuthHeader(method, url, body) {
  const timestamp = Math.floor(Date.now() / 1000);
  const nonceStr = getNonceStr();
  const signature = buildSignature(method, url, timestamp, nonceStr, body);
  return `WECHATPAY2-SHA256-RSA2048 mchid="${mchid}",nonce_str="${nonceStr}",timestamp="${timestamp}",serial_no="${serialNo}",signature="${signature}"`;
}

// Native 下单
async function createNativeOrder(outTradeNo, total, description) {
  const url = '/v3/pay/transactions/native';
  const body = JSON.stringify({
    mchid,
    out_trade_no: outTradeNo,
    appid,
    description,
    notify_url: 'https://yourdomain.com/api/wxpay/notify',
    amount: { total, currency: 'CNY' }
  });

  const authHeader = buildAuthHeader('POST', url, body);

  const res = await axios.post(`https://api.mch.weixin.qq.com${url}`, body, {
    headers: {
      'Authorization': authHeader,
      'Content-Type': 'application/json',
      'Accept': 'application/json'
    }
  });
  return res.data.code_url; // 返回给前端生成二维码
}

3. 回调验签与解密

回调处理是排坑重点。微信会发送一个加密的 resource,需要用 APIv3 密钥进行 AES-256-GCM 解密,同时用平台证书验签。

// 获取平台证书(实际项目应缓存,避免频繁请求)
async function getPlatformCert(serial) {
  const url = '/v3/certificates';
  const authHeader = buildAuthHeader('GET', url, '');
  const res = await axios.get(`https://api.mch.weixin.qq.com${url}`, {
    headers: { 'Authorization': authHeader, 'Accept': 'application/json' }
  });
  // 找到对应序列号的证书,解密 encrypt_certificate
  const certItem = res.data.data.find(item => item.serial_no === serial);
  const ciphertext = certItem.encrypt_certificate.ciphertext;
  const nonce = certItem.encrypt_certificate.nonce;
  const associatedData = certItem.encrypt_certificate.associated_data;
  const certPem = decryptAesGcm(ciphertext, nonce, associatedData, apiV3Key);
  return certPem; // 返回平台证书公钥
}

// AES-256-GCM 解密
function decryptAesGcm(ciphertext, nonce, associatedData, key) {
  const cipher = crypto.createDecipheriv('aes-256-gcm', key, nonce);
  cipher.setAAD(Buffer.from(associatedData));
  const decoded = Buffer.from(ciphertext, 'base64');
  const authTag = decoded.slice(-16);
  const data = decoded.slice(0, -16);
  cipher.setAuthTag(authTag);
  let decrypted = cipher.update(data);
  decrypted = Buffer.concat([decrypted, cipher.final()]);
  return decrypted.toString('utf8');
}

// 回调处理
app.post('/api/wxpay/notify', async (req, res) => {
  const { id, create_time, resource_type, resource, event_type } = req.body;
  // 1. 验签(简化:实际需用平台证书对 headers 和 body 验签)
  // 2. 解密 resource
  const { ciphertext, nonce, associated_data } = resource;
  const decrypted = decryptAesGcm(ciphertext, nonce, associated_data, apiV3Key);
  const orderInfo = JSON.parse(decrypted);
  // 3. 处理业务逻辑,更新订单状态
  console.log('订单支付成功:', orderInfo.out_trade_no);
  // 4. 必须返回 200,否则微信会重复通知
  res.status(200).json({ code: 'SUCCESS', message: '成功' });
});

4. 排查步骤清单

对接微信支付 V3 的核心在于:签名串拼接必须严格逐行加 \n,私钥格式必须清洗干净,回调必须用平台证书验签并动态更新。只要把这三步做扎实,90% 的联调问题都会迎刃而解。建议在开发环境先使用微信支付的沙箱环境验证,再切换到生产环境。