前言
自己實現(xiàn)一個帶支付功能的小程序摄职,前端使用uniapp
几颜,后端使用Node.js
,將實現(xiàn)微信小程序支付功能的全流程詳細記錄下來穷娱。使用的是全新的微信支付 APIv3
用戶付款流程
如圖1绑蔫,用戶通過分享或掃描二維碼進入商戶小程序,用戶選擇購買泵额,完成選購流程配深。
-
如圖3,調起微信支付控件嫁盲,用戶開始輸入支付密碼篓叶。
WX20240827-202827.png 如圖4,密碼驗證通過羞秤,支付成功缸托。商戶后臺得到支付成功的通知。
如圖5瘾蛋,返回商戶小程序俐镐,顯示購買成功。
-
如圖6哺哼,微信支付公眾號下發(fā)支付憑證佩抹。
WX20240827-202944.png
業(yè)務流程圖
6_2.png
具體操作流程
- 用戶通過分享或掃描二維碼進入商戶小程序,用戶選擇購買取董,完成選購流程
- 調用 wx.login 獲取用戶臨時登錄憑證
code
棍苹,發(fā)送到后端服務器換取openId
- 在下單時,小程序需要將購買的商品Id甲葬,商品數(shù)量廊勃,以及用戶的
openId
傳送到服務器 - 服務器在接收到商品Id懈贺、商品數(shù)量经窖、
openId
后坡垫,生成服務期訂單數(shù)據(jù),同時經(jīng)過一定的簽名算法画侣,向微信支付發(fā)送請求冰悠,獲取預付單信息prepay_id
,同時將獲取的數(shù)據(jù)再次進行相應規(guī)則的簽名配乱,向小程序端響應必要的信息 - 小程序端在獲取對應的參數(shù)后溉卓,調用wx.requestPayment發(fā)起微信支付,喚醒支付工作臺搬泥,進行支付
- 接下來的一些列操作都是由用戶來操作的包括了微信支付密碼桑寨,指紋等驗證,確認支付之后執(zhí)行鑒權調起支付
- 鑒權調起支付:在微信后臺進行鑒權忿檩,微信后臺直接返回給前端支付的結果尉尾,前端收到返回數(shù)據(jù)后對支付結果進行展示
- 推送支付結果:微信后臺在給前端返回支付的結果后,也會向后臺也返回一個支付結果燥透,后臺通過這個支付結果來更新訂單的狀態(tài)
準備工作
- appid: 微信公眾平臺分配的小程序AppID沙咏。
- secret: 微信公眾平臺分配的小程序AppSecret。
- mchid: 微信支付商戶號班套。
- mchKey: 微信支付商戶密鑰肢藐。
- serial_no: 商戶API證書序列號。
- publicKey: 公鑰吱韭。
- privateKey: 秘鑰吆豹。
- 微信支付文檔
前端 uni-app 實現(xiàn)
<template>
<view style="display: flex;align-items: center;justify-content: center;height: 100vh;">
<button @click="wechatPay">微信支付</button>
</view>
</template>
<script setup>
import { ref } from 'vue'
const openId = ref(null)
/**
* 微信支付
*/
const wechatPay = async () => {
console.log('wechatPay',openId.value);
if (!openId.value) {
await wechatLogin()
}
await submitOrder()
}
/**
* 微信登錄
*/
function wechatLogin() {
return new Promise((resolve, reject) => {
// 1、獲取臨時登錄憑證 code
uni.login({
provider: "weixin",
success: ({ code }) => {
// 2理盆、將獲取到的 code 發(fā)送到后端瞻讽,用于后端向微信獲取 openId 。
uni.request({
url: 'http://yourdomain.com/api/weChatMp/login',
method: 'POST',
data: { code },
success: (loginRes) => {
// 3熏挎、將后端返回的 openId 進行保存
openId.value = loginRes.data.data.openId
resolve(loginRes)
},
fail: () => {
reject('登錄失斔儆隆!')
uni.showToast({
title: '登錄失斂补铡烦磁!',
icon: 'error',
duration: 1500
});
}
})
},
fail: () => {
reject('登錄出錯')
uni.showToast({
title: '登錄出錯!',
icon: 'error',
duration: 1500
});
}
})
})
}
/**
* 提交訂單并支付
*/
function submitOrder() {
return new Promise((resolve, reject) => {
// 1哼勇、將 openId 以及相應需要的商品信息發(fā)送到后端
uni.request({
url: 'http://yourdomain.com/api/weChatMp/createOrder',
method: 'POST',
data: {
openId: openId.value, // 后臺返回的 openId
productNum: 2, // 假設這是商品數(shù)量
productId: '12345678', // 假設這是商品 id都伪,
productName: '小米13Pro白色6G版', // 假設這是商品名稱,
productPrice: '9.9' // 假設這是商品價格积担,
},
success(ores) {
// 2陨晶、將后端返回的信息填寫到 wx.requestPayment,喚起微信小程序支付
const result = ores.data.data
uni.requestPayment({
provider: 'wxpay',
// orderInfo: result,
nonceStr: result.nonceStr, // 隨機字符串,長度為32個字符以下
package: result.package, // 統(tǒng)一下單接口返回的 prepay_id 參數(shù)值先誉,提交格式如:prepay_id=***
paySign: result.paySign, // 簽名湿刽,具體見微信支付文檔
signType: result.signType, // 簽名算法,應與后臺下單時的值一致
timeStamp: result.timeStamp, // 時間戳褐耳,從 1970 年 1 月 1 日 00:00:00 至今的秒數(shù)诈闺,即當前的時間
success(payRes) {
console.log('支付成功', payRes);
uni.showModal({
title: '支付提示',
content: '支付成功',
showCancel: false,
})
resolve(payRes)
},
fail(payErr) {
if(payErr?.errMsg === 'requestPayment:fail cancel'){
reject('支付已取消!')
uni.showToast({
title: '支付已取消铃芦!',
icon: 'error',
duration: 1500
});
}else{
console.log('支付失敗雅镊,請重新嘗試!', payErr);
reject('支付失敗刃滓,請重新嘗試仁烹!')
uni.showModal({
title: '支付提示',
content: '支付失敗,請重新嘗試',
showCancel: false,
})
}
}
});
},
fail(err) {
reject('訂單出錯咧虎!', err)
uni.showToast({
title: '訂單出錯晃危!',
icon: 'error',
duration: 1500
});
}
})
})
}
</script>
后端 Nodejs 實現(xiàn)
// 微信支付
const express = require('express'); // 導入 Express 模塊
const cors = require('cors'); // 導入 CORS 模塊,用于處理跨域請求
const fs = require('node:fs'); // node內置模塊老客,用于文件系統(tǒng)操作僚饭。
const path = require('node:path');//node內置模塊,用于處理文件路徑胧砰。
const axios = require('axios'); // 導入 Axios 模塊鳍鸵,用于發(fā)起 HTTP 請求
const app = express(); // 創(chuàng)建 Express 應用實例
const WxPay = require('wechatpay-node-v3'); // 導入微信支付模塊
const bodyParser = require('body-parser'); // 中間件,用于解析req.body
const crypto = require('crypto'); // 加密模塊
app.use(cors()); // 使用 CORS 中間件解決跨越請求
app.use(bodyParser.json()); // 使用中間件解析JSON數(shù)據(jù)
// 配置靜態(tài)文件服務尉间,使得/public路徑下的文件可以直接訪問偿乖,如果沒有請手動創(chuàng)建
app.use('/public', express.static(path.join(__dirname, 'public')));
const port = 3000; // 設置應用監(jiān)聽的端口號
// 微信小程序配置信息
const wxConfig = {
appid: 'your_app_id',
secret: 'your_app_secret',
mchid: 'your_mch_id', //商戶號
serial_no: 'your_serial_no', //API證書 - 商戶API證書序列號
publicKey: fs
.readFileSync(`${__dirname}/certificate/apiclient_cert.pem`)
.toString(), // 公鑰
privateKey: fs
.readFileSync(`${__dirname}/certificate/apiclient_key.pem`)
.toString(), // 秘鑰
apiV3Key: 'your_apiV3Key', //apiV3 秘鑰值
mchKey: 'your_mch_key', // 微信支付商戶密鑰
}
const wechatPay = new WxPay({
appid: wxConfig.appid,
mchid: wxConfig.mchid,
publicKey: wxConfig.publicKey, // 公鑰
privateKey: wxConfig.privateKey, // 秘鑰
});
// 微信用戶登陸
app.post('/api/weChatMp/login', async (req, res) => {
const code = req.body?.code
if (!code) {
throw new Error('code參數(shù)不能為空')
}
const url = `https://api.weixin.qq.com/sns/jscode2session`;
const response = await axios({
method: "GET",
url,
params: {
appid: wxConfig.appid,
secret: wxConfig.secret,
js_code: code,
grant_type: 'authorization_code',
},
});
if (response?.errcode) {
throw new Error(JSON.stringify(response))
}
res.send({
msg: '獲取openid成功',
data: {
openId: response.data.openid,
},
})
})
// 創(chuàng)建訂單
app.post('/api/weChatMp/createOrder', async (req, res) => {
const openId = req.body.openId // openId
const productId = req.body.productId // 假設這是商品 id,
const productName = req.body.productName // 假設這是商品名稱哲嘲,
let productPrice = req.body.productPrice // 假設這是商品價格
const productNum = req.body.productNum || 1 // 假設這是商品數(shù)量
if (!openId || !openId || !productId || !productName || !productPrice) {
throw new Error('必要參數(shù)不能為空')
}
if (typeof productPrice === 'string') {
productPrice = Number(productPrice)
}
productPrice = Math.round(productPrice * 100); // 將元轉換為分
const wechatPay = new WxPay({
appid: wxConfig.appid,
mchid: wxConfig.mchid,
publicKey: wxConfig.publicKey, // 公鑰
privateKey: wxConfig.privateKey, // 秘鑰
});
const params = {
description: `購買商品:${productName} x${productNum}`, // 商品描述
out_trade_no: Date.now().toString(), // 商戶訂單號
notify_url: 'http://yourdomain.com/api/weChatMp/notify', // 通知地址
amount: { // 訂單金額信息
total: productPrice, // 訂單總金額贪薪,單位為分。
},
payer: { //【支付者】 支付者信息眠副。
openid: openId, // 【用戶標識】 用戶在普通商戶AppID下的唯一標識画切。 下單前需獲取到用戶的OpenID
},
scene_info: { // 【場景信息】 支付場景描述
payer_client_ip: '14.23.150.211', // 【用戶終端IP】 用戶的客戶端IP,支持IPv4和IPv6兩種格式的IP地址囱怕。
},
};
// 調用小程序下單接口
const result = await wechatPay.transactions_jsapi(params);
res.send(result)
})
// 通知回調處理
app.get('/api/weChatMp/notify', async (req, res) => {
res.send({
msg: "支付成功",
data: null
})
})
// 監(jiān)聽端口
app.listen(port, () => {
console.log(`Example app listening on port ${port}`, `is open url http://127.0.0.1:${port}`)
})
效果演示
效果演示 | |
---|---|
WechatIMG379.jpg
|
WechatIMG378.jpg
|
1.gif
|
結尾
通過以上步驟霍弹,你可以實現(xiàn)uni-app和Node.js環(huán)境下的微信小程序支付功能。確保所有配置信息正確無誤娃弓,并且按照微信支付的官方文檔進行操作典格。