微信支付接口对接教程 - 完整解决方案与实战教程
在电商、SaaS 或 O2O 项目中,对接微信支付几乎是绕不开的一环。无论是 Native 扫码支付、JSAPI 支付还是小程序支付,开发者在联调阶段最常遇到的场景是:本地代码逻辑自测通过,但一到真实环境就报“签名错误”或“回调验签失败”,导致订单状态无法同步,用户已付款但系统仍显示待支付。这类问题往往不是业务逻辑写错,而是对微信支付 V3 接口的签名机制、证书管理和回调验签细节理解不到位。本文将从真实排坑角度出发,带你完整走一遍微信支付接口对接流程。
问题现象:签名与回调的典型报错
在接入微信支付 V3 接口时,以下三类报错占据了 90% 以上的联调时间:
- 401 Unauthorized / 签名错误:请求头中的 Authorization 签名计算错误,常见于私钥格式不对或待签名串拼接错误。
- 回调通知验签失败:微信异步通知到达后,使用平台证书验签不通过,导致无法解密 resource 中的订单信息。
- 证书序列号不匹配:商户 API 证书序列号获取错误,或使用了错误的证书文件。
原因分析:V3 接口的三大坑
微信支付 V3 相比 V2 最大的变化是全面采用 SHA256-RSA 签名和 AES-256-GCM 解密。很多开发者习惯性沿用 V2 的 MD5 签名思路,导致以下问题:
- 私钥格式错误:从微信商户平台下载的 apiclient_key.pem 是 PKCS#8 格式,但部分语言(如 Java)默认需要 PKCS#8 且不能带多余头尾,Node.js 则要求去掉 BEGIN/END 行。若直接读取整个文件字符串,签名必错。
- 待签名串拼接错误:V3 要求按 HTTP 方法、URL、时间戳、随机串、请求体逐行拼接,每行以 \n 结尾,最后一行也要有 \n。少一个换行符就会导致签名不一致。
- 平台证书未更新:微信支付平台证书会定期轮换,如果代码中硬编码了旧证书,回调验签必然失败。必须通过 /v3/certificates 接口动态获取并缓存。
解决方案(附完整代码)
下面以 Node.js + Express 为例,实现 Native 支付下单与回调验签的完整流程。其他语言逻辑一致,仅 API 调用方式不同。
1. 准备工作与配置
- 在微信商户平台申请 API 证书,得到 apiclient_cert.pem、apiclient_key.pem 和证书序列号。
- 设置 APIv3 密钥(32 位),用于解密回调中的 resource 字段。
- 将商户号、appid、私钥路径、序列号、APIv3 密钥写入环境变量,切勿硬编码。
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. 排查步骤清单
- 使用微信官方提供的 签名验证工具 校验本地生成的签名串是否与官方一致。
- 检查私钥文件是否被正确读取,建议打印私钥长度(应为 1600+ 字符)。
- 确认请求头中的 serial_no 是商户证书序列号,而非平台证书序列号。
- 回调验签时,务必使用微信支付平台证书,而非商户证书。
- 所有时间戳单位是秒,不是毫秒。
对接微信支付 V3 的核心在于:签名串拼接必须严格逐行加 \n,私钥格式必须清洗干净,回调必须用平台证书验签并动态更新。只要把这三步做扎实,90% 的联调问题都会迎刃而解。建议在开发环境先使用微信支付的沙箱环境验证,再切换到生产环境。