这篇文章主要介绍“如何使用nodejs搭建微信小程序支付接口”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“如何使用nodejs搭建微信小程序支付接口”文章能帮助大家解决问题。
支付流程
一、向后端服务器获取支付所需参数
二、用获得的参数调用小程序内置的的支付api
三、在回调的后端接口中处理业务逻辑
在微信小程序发起支付
查看微信小程序的官方文档,我们可以查到微信小程序发起支付的api
wx.requestPayment({ timeStamp: '', nonceStr: '', package: '', signType: 'MD5', paySign: '', success (res) { }, fail (res) { } })
可以看到这里需要我们携带五个参数( timeStamp, nonceStr,package,signType,,paySign)才能正常发起支付。
那我们要在哪里才能获取到这些参数呢,这就需要我们的nodejs上场了。
支付模块
使用npm安装
npm i wechatpay-node-v3
这个包集成了H5、App端的支付能力,更多详细的介绍可以去看一下这个包的官方文档,这里就只介绍在微信小程序的应用。
引入依赖包
const WxPay = require('wechatpay-node-v3'); const fs = require('fs'); const request = require('superagent'); const express = require('express');
其中fs是一个文件处理的内置模块模块,superagent是一个发起请求的模块,若没有的话使用npm提前安装一下,这里就不多做介绍了。因为我们需要搭建一个可供前端请求的接口,我们就需要用到express服务器搭建框架,同样也是需要使用npm提前安装一下的。
创建支付实例
const pay = new WxPay({ appid: '你的微信小程序appid', mchid: '商户号', publicKey: fs.readFileSync('./apiclient_cert.pem'), // 公钥 privateKey: fs.readFileSync('./apiclient_key.pem'), // 秘钥 });
其中商户号需要凭营业执照才能申请,个人是无法接入微信支付的。
申请到商户号之后还需要在微信小程序的管理平台关联一下商户号。
然后还需要去申请公钥和私钥证书。具体的申请流程可看下方微信官方的文档:微信支付接入前准备
之后把申请的公钥私钥证书文件放到同级目录下。
获取支付参数
async function payInfo(req,res){ const params = { description: 'Asnull的支付测试', // 订单描述 out_trade_no: '2022080711111111', // 订单号,一般每次发起支付都要不一样,可使用随机数生成 notify_url: 'https://pay.lipux.cn/notify_url', amount: { total: 1, // 支付金额,单位为分 }, payer: { openid: 'drEc8QfY', // 微信小程序用户的openid,一般需要前端发送过来 }, scene_info: { payer_client_ip: 'ip', // 支付者ip,这个不用理会也没有问题 }, }; const result = await pay.transactions_jsapi(params); console.log(result); }
其中的回调url是当用户成功支付之后微信服务器就会向这个回调url发支付结果的信息,一般我们是在这个回调url里面进行一些支付成功之后的业务处理,而且这个回调url是需要ssl证书认证的也就是https,且在链接后面不能携带参数。url示例:https://pay.lipux.cn/notify_url
注意:这个回调url必须能公网访问的哦,不能是本地环境的链接
由于pay.transactions_jsapi返回的是一个promise对象,因此我们使用async和await函数进行接收结果,其中result就是微信小程序api发起支付所需要的参数。
result的打印结果:
{
appId: 'drEc8QfY',
timeStamp: '1609918952',
nonceStr: 'y8aw9vrmx8c',
package: 'prepay_id=wx0615423208772665709493edbb4b330000',
signType: 'RSA',
paySign: 'JnFXsT4VNzlcamtmgOHhziw7JqdnUS9qJ5W6vmAluk3Q2nska7rxYB4hvcl0BTFAB1PBEnHEhCsUbs5zKPEig==
}
我们将这个结果使用express中的路由监听res.send()函数发送给前端就可以了。
然后我们就在前端解析这些数据,放到wx.requestPayment这个微信小程序的api中正式发起支付。
如果不出意外的话,在微信小程序发起支付这个功能我们就正式实现了。
处理业务逻辑
上面提到了,回调url就是一个处理支付成功之后的业务逻辑的接口。
当支付成功之后,微信服务器会向我们这个接口发送一个post请求,这个post请求携带了一些有关支付结果的参数
支付结果通知是以POST 方法访问商户设置的通知url,通知的数据以JSON 格式通过请求主体(BODY)传输。通知的数据包括了加密的支付结果详情。
通过官方微信支付文档的上面这个话可知,微信服务器向我们发送的是一段加密的数据,但别担心,这个模块的作者都帮我们解决了。
我们先来看一下微信服务器都给我们发送了什么数据:
{ "id": "091541fc-6sca-55v8-ab24-653a9v313500", "create_time": "2022-08-07T16:39:06+08:00", "resource_type": "encrypt-resource", "event_type": "TRANSACTION.SUCCESS", "summary": "支付成功", "resource": { "original_type": "transaction", "algorithm": "AEAD_AES_256_GCM", "ciphertext": "tMqPpq3VCxwt56hU2gfsPDJfcfESQ/kzPNmi2xYF0KqMV9ChIWu+n5iVXSVqwgsU9gYSSXeThhp3jm8i9pcrTiOagMxEM/IbJ+MfnN7fkr8Jy2tWOg49N4wy3vB2Qd/nJvD+Jz8K6c4rF8MOasgN+XEriut23sd6EqGUY5zTaKQ+yZC7Q5R+Q6UXa4HlsvHH7+wL6Uz71ZqNyawJ7BYGGh3aXwTu3DHMOullL/IoG3E1nRq1xQRmJsn0li4okegLRuTmlp3vvxZcNgHLOZSCmtdYcRYsZezB2wYdqsT5cCUmRgO8CdgctkGGQIOTjlgaKT8gogP7XUvw1bcFMAC4HqUJv2v28mfMTjFzhLNXXWCFDKJDWhCQg2ZTXw0pRJSYe/IiNBpuVsKX7DGahOyYly/Hn321fryiH7mpI5orC6Wb03Mc77hcnL9ALDV0jT8mrmYuB8pAMkxsFNcGcgnp5FrtKcA59CEYc4ccNU26wIiIszB0YIwvirvCEGys3eGStQaytFLvGw5qCmnZ6N5X3GPBOPEQXJa19CrVndWMjBm1PaeyJ/fgfN9mGrsChrToxDg==", "associated_data": "transaction", "nonce": "iOO0tvICpQFb" } }
我们可以看到其中有段信息是被加密了,我们需要解密其中的信息才能进一步的进行我们业务处理。
解密回调结果
我们需要通过在回调的路由监听的req.body拿到发送的数据,即上面那一段的json数据,把对应的ciphertext, associated_data, nonce参数传入下面的函数:
# key 用商户平台上设置的APIv3密钥【微信商户平台—>账户设置—>API安全—>设置APIv3密钥】,记为key; const result = pay.decipher_gcm(ciphertext, associated_data, nonce, key);
其中的key是APIv3密钥,需要我们去另外申请,具体申请流程请看下面的官方文档:什么是APIv3密钥?如何设置? (qq.com)
解密成功之后我们就拿到一个新的结果result,打印出来如下:
{ "mchid": "3526524578", "appid": "wxc2n10fbb6065d4f0", "out_trade_no": "2022080711111111", "transaction_id": "8520001545602207282059123413", "trade_type": "JSAPI", "trade_state": "SUCCESS", "trade_state_desc": "支付成功", "bank_type": "OTHERS", "attach": "", "success_time": "2022-08-07T16:55:20+08:00", "payer": { "openid": "drEc8QfY" }, "amount": { "total": 1, "payer_total": 1, "currency": "CNY", "payer_currency": "CNY" } }
其中的out_trade_no就是我们一开始设置的订单号,我们可以在一开始的时候就把这个订单号与我们的用户进行关联,然后在这里就可以通过订单号进行业务处理,比如说充值会员,充值金币等等的操作。
关于回调通知的具体参数说明可看文档:微信支付-开发者文档 (qq.com)
到这里,我们已经完成了整个微信小程序支付的流程,不出意外的话你应该可以正常拿到支付的结果
剩下的就是你的业务逻辑了!
完整代码
/* * Created by Asnull. * Website:https://lipux.cn/ */ const WxPay = require('wechatpay-node-v3'); const fs = require('fs'); const request = require('superagent'); const express = require('express'); // 创建服务器实例 const app = express(); // 配置解析表单数据的中间件 app.use(express.urlencoded({ extended: false })) app.use(express.json()) // 监听端口 app.listen(3031, () => { console.log('服务器启动成功!') }) // 创建支付实例 const pay = new WxPay({ appid: '你的微信小程序appid', mchid: '商户号', publicKey: fs.readFileSync('./apiclient_cert.pem'), // 公钥 privateKey: fs.readFileSync('./apiclient_key.pem'), // 秘钥 }); // 定义一个获取支付参数路由(get请求) app.get('/pay', payInfo); // 拿到支付所需参数 async function payInfo(req, res) { // 接收前端传过来的openid let openid = req.params.openid; const params = { description: 'Asnull的支付测试', // 订单描述 out_trade_no: randomNumber(), // 订单号,一般每次发起支付都要不一样,可使用随机数生成 notify_url: 'https://pay.lipux.cn/notify_url', amount: { total: 1, // 支付金额,单位为分 }, payer: { openid: openid, // 微信小程序用户的openid,一般需要前端发送过来 }, scene_info: { payer_client_ip: 'ip', // 支付者ip,这个不用理会也没有问题 }, }; const result = await pay.transactions_jsapi(params); console.log(result); // 将结果响应给微信小程序前端 res.send(result); } // 回调路由 app.post('/notify_url', async(req, res) => { // 申请的APIv3 let key = '45c18fdfdgd45f5bc5321201dfdf453f'; let { ciphertext, associated_data, nonce } = req.body.resource; // 解密回调信息 const result = pay.decipher_gcm(ciphertext, associated_data, nonce, key); // 拿到订单号 let { out_trade_no } = result; if (result.trade_state == 'SUCCESS') { // 支付成功之后需要进行的业务逻辑 } }) // 订单号生成函数 function randomNumber() { const now = new Date() let month = now.getMonth() + 1 let day = now.getDate() let hour = now.getHours() let minutes = now.getMinutes() let seconds = now.getSeconds() month = month < 10 ? "0" + month : month; day = day < 10 ? "0" + day : day; hour = hour < 10 ? "0" + hour : hour; minutes = minutes < 10 ? "0" + minutes : minutes; seconds = seconds < 10 ? "0" + seconds : seconds; let orderCode = now.getFullYear().toString() + month.toString() + day + hour + minutes + seconds + (Math.round(Math.random() * 1000000)).toString(); return orderCode; }