前言
这里介绍微信小程序如何使用微信进行支付。并且提供前端代码以及后端代码(nodeJs)。这里后端使用nodeJs写的哈。
需要准备:
1、小程序(企业级或个体工商)的appId
2、微信支付商户的商户号,以及v2支付密钥
一、前端登录将code发送后端
提示:以下内容流程:前端进行uni.login登录,将code传给后端,后端根据code获取用户的openid
因为没有使用数据库,也没有使用Redis去缓存openid。所以这里拿到openid后,写死在config.js配置文件中
1、前端代码
<script setup>
const handleLogin = async () => {
const loginRes = await uni.login({provider: 'weixin'}); // 获取微信登录code
// 发送code到服务器 后端通过code获取到用户openid
const res = await uni.request({
url: 'http://127.0.0.1:3030/api/login',
method: 'POST',
data: {code: loginRes.code}
});
// 存储返回的 token
if (res.data.code === 200) {
uni.setStorageSync('token', res.data.token)
uni.showToast({
title: '登录成功',
success() {
uni.switchTab({url:'/pages/home/home'})
}
});
}
};
</script>
2、nodeJs代码
代码示例中提到的config.js代码文件 后面会贴出来
// src/services/login.js
const axios = require('axios');
const config = require('../../config/config'); // 将配置独立出来
// 获取openid session_key
async function getOpenid(code) {
const weixinRes = await axios.get('https://api.weixin.qq.com/sns/jscode2session', {
params: {
appid: config.appId,
secret: config.appSecret,
js_code: code,
grant_type: 'authorization_code'
}
})
return weixinRes;
}
// 用 code 换取 session_key 和 openid...
module.exports = { getOpenid };
// app.js
app.post('/api/login', async (req, res) => {
try {
const { code } = req.body
// 1. 用 code 换取 session_key 和 openid
const weixinRes = await getOpenid(code);
// 2. 生成自定义 token(使用 JWT)
const token = jwt.sign(
{ openid: weixinRes.data.openid },
'your-secret-key',
{ expiresIn: '7d' }
)
// 3. 登录成功 返回 token 给客户端
res.json({
code: 200,
token: token,
openid: weixinRes.data.openid
});
} catch (error) {
res.status(500).json({ code: 500, message: '登录失败' })
}
})
二、前端调用接口创建预支付订单
前端调用接口创建预支付订单,后端根据传递的商品信息和小程序appid,商户号,用户openid等参数,去调用微信提供的统一下单接口,最后返回uni.requestPayment所需要的参数
前端代码
<script setup>
import {ref, reactive} from 'vue';
// 商品信息
const productsInfo = reactive([{
title: 'xxx商品',
money: '0.01',
quantity: 1,
}]);
// 创建预支付订单
const handleCreateOrder = async (data) => {
const res = await uni.request({
url: 'http://127.0.0.1:3030/api/createOrder',
method: 'POST',
data: {...data}
});
if (res.data.code === 200) {
uni.showToast({title: '下单成功'});
// 创建订单成功后,唤起微信支付
const payParams = res.data.data;
uni.getProvider({ // 服务提供商
service: 'payment',
success(res) {
const provider = res.provider;
// 唤起微信支付
uni.requestPayment({
provider,
orderInfo: {...data},
timeStamp: payParams.timeStamp,
nonceStr: payParams.nonceStr,
package: payParams.package,
signType: payParams.signType,
paySign: payParams.paySign,
})
}
})
};
}
</script>
nodeJs代码
// src/services/wechat-pay.js
const crypto = require('crypto');
const axios = require('axios');
const { parseStringPromise } = require('xml2js'); // XML解析库
const config = require('../../config/config'); // 配置独立出来
// 生成签名
function createSign(params) {
const stringA = Object.keys(params)
.filter(key => params[key] !== '' && key !== 'sign') // 过滤空值和sign字段
.sort() // 按ASCII码升序排序
.map(key => `${key}=${params[key]}`)
.join('&');
const stringSignTemp = stringA + `&key=${config.apiKey}`;
const sign = crypto.createHash('md5').update(stringSignTemp).digest('hex').toUpperCase();
return sign;
}
// XML 解析
async function parseXml(xml) {
try {
const result = await parseStringPromise(xml);
const formatted = {};
for (const [key, value] of Object.entries(result.xml)) {
formatted[key] = value[0];
}
return formatted;
} catch (error) {
throw new Error('XML 解析失败');
}
}
// 统一下单接口
async function createOrder(orderData) {
const params = {
appid: config.appId, // 小程序appid
mch_id: config.mchId, // 商户号
nonce_str: Math.random().toString(36).substr(2, 15), // 随机字符串(确保无重复)
body: orderData.title, // 商品描述
out_trade_no: `ORDER_${Date.now()}`, // 商户订单号
total_fee: orderData.totalFee, // 商品金额(单位分)
spbill_create_ip: '192.0.0.1', // 用户端实际IP
notify_url: config.notifyUrl, // 回调地址
trade_type: 'JSAPI', // 交易类型
openid: config.openId, // 支付用户openid
};
params.sign = createSign(params);
// 调用微信提供的统一下单接口
const response = await axios.post(
'https://api.mch.weixin.qq.com/pay/unifiedorder',
`<xml>${Object.entries(params).map(([key, value]) => `<${key}>${key != 'sign'?'<![CDATA[':''}${value}${key != 'sign'?']]>':''}</${key}>`).join('')}</xml>`,
{ headers: { 'Content-Type': 'application/xml' } }
);
// 解析返回的XML数据
const result = await parseXml(response.data);
return result;
}
// 微信支付功能封装...
module.exports = { createOrder, createSign};
// config.js
const APIKEY = 'xxxxx';
const APPID = 'xxxxx';
const MCHID = 'xxxx';
const NOTIFY_URL = 'https://127.0.0.1:3030/api/payCallback';
const APPSECRET = 'xxxxx';
const OPENID = 'xxxx';
module.exports = {
appId: APPID, // 小程序appId
appSecret: APPSECRET, // 小程序密钥
mchId: MCHID, // 商户号
apiKey: APIKEY, // 支付密钥
notifyUrl: NOTIFY_URL, // 支付回调
openId: OPENID, // 用户openid
}
总结
// 调用微信提供的统一下单接口
const response = await axios.post(
'https://api.mch.weixin.qq.com/pay/unifiedorder',
`<xml>${Object.entries(params).map(([key, value]) => `<${key}>${key != 'sign'?'<![CDATA[':''}${value}${key != 'sign'?']]>':''}</${key}>`).join('')}</xml>`,
{ headers: { 'Content-Type': 'application/xml' } }
);
注意:这里sign签名除外,其他参数必须要使用'<![CDATA[ ]]>'包裹。例如:`<![CDATA[${value}]]>`
nodeJs后端用到依赖:
"axios": "^1.7.9",
"body-parser": "^1.20.3",
"dotenv": "^16.4.7",
"express": "^4.21.2",
"jsonwebtoken": "^9.0.2",
"xml2js": "^0.6.2"
"nodemon": "^3.1.9"