前置准备与账号配置
在开始编写代码前,必须从微信商户平台获取以下核心参数。这些参数直接决定了支付请求能否被微信网关正确识别和验证。
- AppID:登录微信开放平台,在已审核通过的移动应用或网站应用详情页中获取。对于H5支付,通常使用微信开放平台的AppID。
- MchID:微信支付商户号,登录微信商户平台,在首页的右上角即可看到。
- APIv3 Key:在微信商户平台进入【账户中心】->【API安全】->【设置APIv3密钥】。这是一个32位的字符串,用于解密回调通知中的敏感信息。请妥善保存,不可泄露。
- 商户私钥:在【账户中心】->【API安全】->【申请商户API证书】中下载证书压缩包。解压后打开
apiclient_key.pem文件,这就是商户私钥,用于请求签名。
- 商户证书序列号:可以在证书下载页面直接查看,或者使用OpenSSL命令
openssl x509 -in apiclient_cert.pem -noout -serial获取。去除冒号后的字符串即为序列号。
目录结构建议:请在项目根目录下创建certs文件夹,并将下载的apiclient_key.pem和apiclient_cert.pem放入其中。
项目初始化与依赖安装
我们将使用Node.js作为开发环境,利用express搭建Web服务,并使用官方推荐的wechatpay-node-v3SDK来处理复杂的签名和证书加密逻辑。这个SDK是目前接入微信支付API v3最稳妥的方案。
打开终端,依次执行以下命令:
```bash
创建项目目录
mkdir wechat-pay-course-demo
cd wechat-pay-course-demo
初始化package.json
npm init -y
安装核心依赖
npm install express wechatpay-node-v3 body-parser
```
依赖说明:body-parser用于解析微信支付回调POST请求中的JSON数据;wechatpay-node-v3封装了所有与微信支付交互的底层逻辑。
核心代码实现
在项目根目录下创建server.js文件。我们将实现两个核心接口:/create-order用于发起支付,/notify用于接收支付结果。以下是完整的、可直接运行的代码。
```javascript
const express = require('express');
const { Pay } = require('wechatpay-node-v3');
const bodyParser = require('body-parser');
const fs = require('fs');
const path = require('path');
const app = express();
// 解析中间件配置
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
// ================= 配置区域开始 =================
const payConfig = {
appid: 'wx1234567890abcdef', // 替换为你的AppID
mchid: '1230000109', // 替换为你的商户号
private_key: fs.readFileSync(path.join(__dirname, 'certs/apiclient_key.pem')),
serial_no: '1A2B3C4D5E6F...', // 替换为你的证书序列号(去除冒号)
apiv3_private_key: 'your32bytesapiv3key...', // 替换为你的APIv3密钥
// 注意:notify_url必须是公网可访问的HTTPS地址,且不能包含端口号(除非是标准443)
notify_url: 'https://your-domain.com/notify'
};
// ================= 配置区域结束 =================
// 实例化支付对象
const pay = new Pay(payConfig);
/
接口1:创建H5支付订单
访问:http://localhost:3000/create-order
/
app.get('/create-order', async (req, res) => {
try {
// 生成商户订单号,实际业务中请结合数据库生成唯一订单号
const out_trade_no = 'COURSE' + Date.now();
// 构造下单参数
const params = {
appid: payConfig.appid,
mchid: payConfig.mchid,
description: '高级全栈架构师实战课程', // 商品描述,直接显示在支付界面
out_trade_no: out_trade_no,
notify_url: payConfig.notify_url,
amount: {
total: 100, // 订单金额,单位:分。100代表1元人民币
currency: 'CNY'
},
// H5支付场景配置
scene_info: {
payer_client_ip: '127.0.0.1', // 实际业务中应获取用户真实IP
h5_info: {
type: 'Wap', // 场景类型,固定为Wap
app_name: '技术博客商城',
package_name: 'com.techblog.store' // 网站名或包名
}
}
};
// 调用统一下单API
const result = await pay.transactions_h5(params);
if (result.status === 200) {
// 下单成功,返回h5_url给前端
// 前端拿到url后,可以通过window.location.href = url跳转
res.json({
code: 0,
msg: '下单成功',
pay_url: result.data.h5_url
});
} else {
res.json({
code: -1,
msg: '下单失败',
error: result
});
}
} catch (error) {
console.error('下单接口异常:', error);
res.json({
code: -1,
msg: '系统异常',
error: error.message
});
}
});
/
接口2:支付结果通知回调
路径必须与下单时的notify_url一致
/
app.post('/notify', async (req, res) => {
try {
const { headers, body } = req;
// 1. 验证签名并解密数据
// SDK会自动处理验签、解密AES-256-GCM加密的resource字段
const data = await pay.verify(headers, body);
// 2. 判断交易状态
if (data.event_type === 'TRANSACTION.SUCCESS') {
const resource = data.resource;
const out_trade_no = resource.out_trade_no; // 商户订单号
const transaction_id = resource.transaction_id; // 微信支付订单号
const total = resource.amount.total; // 支付金额
console.log(``);
console.log(`收到支付成功通知:`);
console.log(`商户订单号: ${out_trade_no}`);
console.log(`微信流水号: ${transaction_id}`);
console.log(`支付金额: ${total} 分`);
console.log(``);
// TODO: 核心业务逻辑
// 1. 检查数据库中订单号是否存在,且状态为“未支付”
// 2. 防止重复通知:如果订单已是“已支付”,直接返回成功
// 3. 更新订单状态为“已支付”
// 4. 发放课程权益(如:用户表增加course_id字段,或插入权限表)
// 3. 重要:必须返回200状态码和特定JSON结构,告知微信服务器处理成功
// 如果不返回成功,微信会在一定时间内通过重试机制反复通知
res.status(200).json({ code: 'SUCCESS', message: '成功' });
} else {
// 处理其他事件类型,如TRANSACTION.CLOSED等
res.status(200).json({ code: 'FAIL', message: '忽略非成功交易' });
}
} catch (error) {
console.error('回调处理异常:', error);
// 即使验签失败,也建议返回200,避免微信持续发送无效请求导致服务压力
res.status(200).json({ code: 'FAIL', message: '验签失败' });
}
});
// 启动服务
app.listen(3000, () => {
console.log('支付服务已启动,监听端口: 3000');
console.log('请在浏览器访问: http://localhost:3000/create-order');
});
```
本地测试与内网穿透配置

微信支付的回调接口notify_url严格要求必须是公网可访问的HTTPS地址。在本地开发阶段(localhost),微信服务器无法直接访问你的代码。必须使用内网穿透工具。
这里使用cpolar作为示例,它操作简单且免费版足够使用。你也可以使用ngrok或花生壳。
操作步骤:
- 注册并安装cpolar客户端。
- 在终端执行命令映射本地3000端口:
```bash
cpolar http 3000
```
执行后,终端会显示公网地址,例如:https://abc123.cpolar.io。
修改配置:将上述公网地址填入server.js中的notify_url,例如:
```javascript
notify_url: 'https://abc123.cpolar.io/notify'
```
修改后,务必使用node server.js重启Node服务。
支付全流程验证
完成上述配置后,按照以下步骤进行完整的支付闭环测试:
- 发起下单:在浏览器地址栏输入
http://localhost:3000/create-order并回车。
- 获取链接:浏览器页面会显示JSON数据,复制
pay_url对应的值(以https://wx.tenpay.com开头)。
- 模拟支付:
- 将此链接发送到手机上(微信环境内)打开。
- 或者使用微信开发者工具,切换到“Web调试”,粘贴链接打开。
- 完成支付:在弹出的微信收银台中,输入测试号密码或使用真实小额资金完成支付。
- 观察结果:
- 支付成功后,查看你的Node.js终端控制台。
- 你应该能看到打印出的“收到支付成功通知”日志,包含订单号和金额。
- 如果日志正常打印,说明
/notify接口逻辑已成功执行,你的课程转化闭环在技术上已经跑通。