微信支付已成为移动端最主流的支付方式之一。本文将手把手带你完成微信支付的接入,涵盖公众号支付、扫码支付、小程序支付等场景,包括前端调起支付、后端签名、支付回调处理等核心环节。
📦 一、准备工作
在开始编码之前,你需要完成以下准备工作:
1. 注册商户号
- 前往 微信支付商户平台 注册,提交营业执照等资料,审核通过后获得商户号(
mchid)。
2. 获取 API 密钥(APIv2)
- 在商户平台「账户中心」->「API安全」中设置 APIv2 密钥(32位字符串),用于签名。
3. 配置应用
根据支付场景,需要配置对应的应用:
- JSAPI 支付(公众号支付):在公众号后台配置网页授权域名、支付授权目录。
- 扫码支付:配置支付回调 URL。
- 小程序支付:在小程序后台绑定微信支付商户号。
4. 下载证书(可选)
如需使用退款等高级接口,需要下载商户证书(p12 或 pem 格式)。
🔁 二、支付流程概览
微信支付的标准流程如下:
用户 -> 商户前端 -> 商户后端 -> 微信支付API -> 商户后端 -> 前端调起支付 -> 用户支付 -> 支付结果回调
- 前端请求下单:用户点击支付按钮,前端向商户后端发起订单创建请求。
- 后端统一下单:商户后端调用微信支付【统一下单】接口,获取预支付交易会话标识
prepay_id。 - 返回支付参数:后端根据
prepay_id生成前端调起支付所需的签名参数,返回给前端。 - 前端调起支付:前端调用微信 JSAPI / 小程序 API 发起支付。
- 用户支付:用户输入密码完成支付。
- 支付结果通知:微信服务器异步通知商户后端支付结果,商户更新订单状态。
⚙️ 三、后端统一下单(以 Node.js + JSAPI 为例)
1. 安装依赖
npm install axios crypto xml2js
2. 生成签名函数
微信支付使用 MD5 或 HMAC-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_id或out_trade_no幂等处理,避免重复更新订单。
3. 商户端防篡改
- 前端返回的支付参数不要信任,必须后端签名。
- 金额等关键信息在后端下单时确定,不可由前端传递。
4. 超时处理
- 统一下单接口可设置
time_expire参数,超过时间未支付则关闭订单。
5. 日志记录
- 记录支付请求、回调的详细日志,便于排查问题。
6. 证书安全
- APIv2 密钥和证书切勿提交到代码仓库,应使用环境变量或密钥管理服务。
📚 七、总结
本文以 Node.js 为例,详细介绍了微信支付的接入流程,从统一下单到前端调起支付,再到异步通知处理。虽然代码以 JSAPI 支付为主,但扫码支付、小程序支付的思路类似,只需调整 trade_type 和前端调用方式即可。
微信支付官方提供了丰富的文档和 SDK,但在实际开发中,仍需要注意签名、安全、异常处理等细节。希望本文能帮助你快速接入微信支付,让资金流转更加顺畅。
参考资料
🚀 前端系统进阶
一站式前端面试解决方案
别再零散刷题了,构建你的前端知识体系
1000+ 精选面试题 | 22+ 技术领域 | 大厂真题全覆盖
✨ 核心优势
- 📚 海量题库:JavaScript、Vue、React、TS、算法……一网打尽
- 🎓 深度解析:不只是答案,更有思路与方法
- 🗺️ 系统学习:从基础到源码,构建完整知识框架
- 🏆 大厂真题:阿里、腾讯、字节真实面试题
🎯 适合人群
应届生 · 跳槽者 · 转行开发者 · 冲刺大厂
立即开始
让每一次面试,都成为展示实力的舞台!
👉 开始学习