1. 前期準(zhǔn)備
需要用到的資料和賬號
· AppID(小程序ID),AppSecret(小程序密鑰)
· 商戶號(mchid)
· 微信支付證書源文件腺律,微信支付API證書序列號
· 商戶號APIv3秘鑰赞草,用于微信支付成功后回調(diào)
其中商戶號需要憑營業(yè)執(zhí)照才能申請,個(gè)人是無法接入微信支付的思灰。申請到商戶號之后還需要在微信小程序的管理平臺關(guān)聯(lián)一下商戶號。
然后還需要去申請公鑰和私鑰證書死遭。具體的申請流程可看下方微信官方的文檔:
https://pay.weixin.qq.com/wiki/doc/apiv3/open/pay/chapter2_8_1.shtml(小程序支付接入準(zhǔn)備)
2.開發(fā)必備插件
看了下微信支付的官方文檔鞠眉,微信官方只提供了java、php還有Go語言的sdk嫉沽,并沒有node.js辟犀,后面逛了一圈社區(qū)發(fā)現(xiàn)wechatpay-node-v3這款插件,是專門針對node后臺服務(wù)進(jìn)行微信支付的工具绸硕。具體可參考:
https://pay.weixin.qq.com/wiki/doc/apiv3/open/pay/chapter2_8_2.shtml(小程序支付開發(fā)指引)
3.插件引入和使用
根目錄安裝:
npm install wechatpay-node-v3 fs
fs用于讀取公鑰和私鑰證書堂竟。將下載好的證書放置項(xiàng)目內(nèi)同一目錄魂毁。
在路由內(nèi)引入:
const WxPay = require('wechatpay-node-v3'); // 支持使用require
const fs = require('fs');
const path = require("path");
const apiclient_cert = path.resolve(__dirname, 'apiclient_cert.pem');
const apiclient_key = path.resolve(__dirname, 'apiclient_key.pem');
const pay = new WxPay({
appid: 'wx4beb40ab8exxxxxx',//小程序appid
mchid: '1639xxxxxx',//商戶號
publicKey: fs.readFileSync(apiclient_cert), // 公鑰
privateKey: fs.readFileSync(apiclient_key), // 秘鑰
});
注意:本篇 node.js服務(wù)是基于Express應(yīng)用框架搭建。
3.小程序服務(wù)端預(yù)設(shè)微信下單數(shù)據(jù)
router.post('/order/wx/pay', async (req, res) => {
const userId = req.user._conditions.userId;
//自己生成訂單號(如果是待付款訂單再次支付跃捣,不再生成新訂單)
let orderNumber = req.body.orderNumber ? req.body.orderNumber : tools.orderNumber();
const params = {
appid: 'wx4beb40ab8exxxxxx',
mchid: '1639xxxxxx',
description: '訂單支付',
out_trade_no: orderNumber,
notify_url: 'https://lxxxxx.cn/web/api/notify_order',
amount: {
total: Math.ceil(Number(req.body.money)*100),//向上取整解決科學(xué)計(jì)數(shù)法問題
currency: "CNY"
},
payer: {
openid: userId
}
};
const result = await pay.transactions_jsapi(params);
//訂單詳情再次支付不再生成訂單
if(!req.body.orderNumber ){
let obj = {
orderNumber: orderNumber, //訂單號
createdTime: tools.createdTime(), //訂單時(shí)間
createdUser: userId,
goodsList: req.body.goodsList,//商品信息
money: req.body.money,//支付錢數(shù)
orderStatus: 0, //0 未支付 1已支付(未配送) 2已完成(已支付配送完成) 3訂單取消
discountMoney: req.body.discountMoney,//折扣信息
payType: req.body.payType,//支付方式 微信:1 余額: 2
delivery: req.body.delivery,//配送信息
address: req.body.address,//收貨地址信息
}
//生成未支付訂單
await order.create(obj);
}
res.send({
code: 200,
data: result,
message: "查詢成功",
});
});
說明:以上代碼需要特別注意的是notify_url參數(shù)對應(yīng)的地址漱牵; 該url是當(dāng)用戶成功支付之后微信服務(wù)器就會向這個(gè)回調(diào)url發(fā)送支付結(jié)果的信息,一般我們是在這個(gè)回調(diào)url里面進(jìn)行一些支付成功之后的業(yè)務(wù)處理疚漆,而且這個(gè)回調(diào)url是需要ssl證書認(rèn)證的也就是https酣胀,且在鏈接后面不能攜帶參數(shù)。url示例:
https://lxxxxx.cn/web/api/notify_order
注意:這個(gè)回調(diào)url必須能公網(wǎng)訪問的哦娶聘,不能是本地環(huán)境的鏈接
由于pay.transactions_jsapi返回的是一個(gè)promise對象闻镶,因此我們使用async和await函數(shù)進(jìn)行接收結(jié)果,其中result就是微信小程序api發(fā)起支付所需要的參數(shù)丸升。
例如我在項(xiàng)目里的回調(diào)處理大致如下:
router.post('/notify_order', async (req, res) => {
try {
// 申請的APIv3
let key = '3SdsdfdfGK2Yuehi67UH3xxxxxxxxx';
let {
ciphertext,
associated_data,
nonce
} = req.body.resource;
// 解密回調(diào)信息
const result = pay.decipher_gcm(ciphertext, associated_data, nonce, key);
if (result.trade_state === 'SUCCESS') {
const orderInfo = await order.findOne({
orderNumber: result.out_trade_no
});
if(orderInfo.orderStatus === 0){
await order.updateOne({
orderNumber: result.out_trade_no
}, {
$set: {
orderStatus: 1,
transactionId: result.transaction_id
}
})
//刪除購物車對應(yīng)商品
let _ids = [];
let domStr="";//發(fā)送訂單郵件使用
orderInfo.goodsList.forEach((v,i)=>{
_ids.push(v._id)
domStr += `<div>商品${i+1}:</div>
<div>
<div>商品名稱:${v.goodsName}</div>
<div>商品規(guī)格:${v.specification}</div>
<div>數(shù)量:${v.quantity}</div>
<div><image style="height:350px;width:350px" src=${v.mainImage}></image></div>
</div>`
})
//刪除購物車數(shù)據(jù)
await shopcart.remove({
userId: orderInfo.createdUser,
_id: {$in: _ids},
})
//發(fā)送郵件給商家提醒
sendMail("54357xxx@qq.com","您有新的訂單铆农!",
`訂單編號:${orderInfo.orderNumber}<br/>
下單時(shí)間:${orderInfo.createdTime}<br/>
訂單金額:${orderInfo.money}元<br/>
收貨人:${orderInfo.address.contactName},${orderInfo.address.mobile}<br/>
收貨地址:${orderInfo.address.mainAddress + orderInfo.address.detailAddress}<br/>
送達(dá)時(shí)間:${orderInfo.delivery.deliveryTime}<br/>
訂單備注:${orderInfo.delivery.remark || '無'}<br/>
商品詳情:` + domStr)
res.status(200);
res.send({
code: 'SUCCESS',
message: "成功",
});
}
}
} catch (error) {
res.status(500);
res.send({
code: 'FAIL',
message: "失敗",
});
}
});
根據(jù)上面代碼可以看出狡耻,我在微信支付回調(diào)的url中首先判斷處理狀態(tài)trade_state === 'SUCCESS';其次再根據(jù)訂單號查詢該訂單的支付信息墩剖,如果還是未支付狀態(tài)這個(gè)時(shí)候就可以修改成支付完成狀態(tài)了;最后發(fā)送郵件給商家告知有一筆新訂單夷狰。
4.小程序前端下單部分代碼
//微信支付 調(diào)用后端服務(wù)的 /order/wx/pay 接口
wechatPay() {
let params = {
address: this.info.addressInfo,
goodsList: this.info.shopcartInfo,
money: this.info.money,
discountMoney: this.info.discountMoney,
delivery: this.model,
payType: this.payType,
}
let _this = this;
wechatPay(params).then(res => {
if (res.code === 200) {
uni.requestPayment({
provider: 'wxpay',
timeStamp: res.data.timeStamp,
nonceStr: res.data.nonceStr,
package: res.data.package,
signType: 'RSA',
paySign: res.data.paySign,
success: function(res) {
_this.$refs.uToast.show({
type: 'success',
message: '支付成功',
})
setTimeout(() => {
_this.goBack();
}, 1500)
},
fail: function(err) {
uni.$u.toast("支付取消")
}
});
}
})
}
不難看出上面的代碼在調(diào)用接口成功后返回了微信支付需要的一系列參數(shù)岭皂;在小程序前端我使用的是uniapp的uni.requestPayment方法調(diào)取微信支付,該方法需要的參數(shù)也在后端接口進(jìn)行了返回沼头,至此微信小程序一整套的支付流程就結(jié)束了爷绘。
5.小程序微信支付退款
退款和支付類似也一樣有一個(gè)notify_url回調(diào)地址,代碼如下:
router.post('/order/wx/refund',async (req,res)=>{
let rNum = tools.refundOrderNumber()//自己生成退款單號
let params = {
out_trade_no: req.body.out_trade_no,//原訂單號
out_refund_no: rNum,
notify_url:'https://lxxxxx.cn/web/api/notify_refund',
amount:{
refund: Math.ceil(Number(req.body.money)*100),
total: Math.ceil(Number(req.body.money)*100),
currency: 'CNY'
}
}
const result = await pay.refunds(params);
res.send({
code: 200,
data: result,
message: "操作成功",
});
});
//微信支付退款回調(diào)通知
router.post('/notify_refund', async (req, res) => {
try {
// 申請的APIv3
let key = '3SdsdfdfGK2Yuehi67UH3xxxxxxxxx';
let {
ciphertext,
associated_data,
nonce
} = req.body.resource;
// 解密回調(diào)信息
const result = pay.decipher_gcm(ciphertext, associated_data, nonce, key);
// logger.info("解密回調(diào)參數(shù) result==",result)
if (result.refund_status === 'SUCCESS') {
const orderInfo = await order.findOne({
orderNumber: result.out_trade_no
});
if(orderInfo.orderStatus === 4){
await order.updateOne({
orderNumber: result.out_trade_no
}, {
$set: {
orderStatus: 5,//從退款中狀態(tài)修改為退款成功狀態(tài)
refundOrderNumber: result.out_refund_no
}
})
res.status(200);
res.send({
code: 'SUCCESS',
message: "成功",
});
}
}
} catch (error) {
res.status(500);
res.send({
code: 'FAIL',
message: "失敗",
});
}
});
小程序前端調(diào)用/order/wx/refund接口进倍,服務(wù)端在微信支付退款回調(diào)通知里處理訂單狀態(tài)土至,至此小程序微信支付退款也完成了。
如果文檔內(nèi)有描述有誤的地方還請各位指出猾昆,謝謝陶因!本篇over~
參考文檔:
https://www.npmjs.com/package/wechatpay-node-v3
https://blog.csdn.net/weixin_45952249/article/details/126216205