node.js之微信小程序支付和退款

1. 前期准备

需要用到的资料和账号

· AppID(小程序ID),AppSecret(小程序密钥)

· 商户号(mchid)

· 微信支付证书源文件,微信支付API证书序列号

· 商户号APIv3秘钥,用于微信支付成功后回调

其中商户号需要凭营业执照才能申请,个人是无法接入微信支付的。申请到商户号之后还需要在微信小程序的管理平台关联一下商户号。

image.png

然后还需要去申请公钥和私钥证书。具体的申请流程可看下方微信官方的文档:
https://pay.weixin.qq.com/wiki/doc/apiv3/open/pay/chapter2_8_1.shtml(小程序支付接入准备)

2.开发必备插件

看了下微信支付的官方文档,微信官方只提供了java、php还有Go语言的sdk,并没有node.js,后面逛了一圈社区发现wechatpay-node-v3这款插件,是专门针对node后台服务进行微信支付的工具。具体可参考:

https://pay.weixin.qq.com/wiki/doc/apiv3/open/pay/chapter2_8_2.shtml(小程序支付开发指引)

3.插件引入和使用

根目录安装:

npm install wechatpay-node-v3 fs

fs用于读取公钥和私钥证书。将下载好的证书放置项目内同一目录。

image.png

在路由内引入:

 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服务是基于Express应用框架搭建。

3.小程序服务端预设微信下单数据

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),//向上取整解决科学计数法问题
                currency: "CNY"
            },
            payer: {
                openid: userId
            }
        };
        const result = await pay.transactions_jsapi(params);
       
         //订单详情再次支付不再生成订单
        if(!req.body.orderNumber ){
            let obj = {
                orderNumber: orderNumber, //订单号
                createdTime: tools.createdTime(), //订单时间
                createdUser: userId,
                goodsList: req.body.goodsList,//商品信息
                money: req.body.money,//支付钱数
                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参数对应的地址; 该url是当用户成功支付之后微信服务器就会向这个回调url发送支付结果的信息,一般我们是在这个回调url里面进行一些支付成功之后的业务处理,而且这个回调url是需要ssl证书认证的也就是https,且在链接后面不能携带参数。url示例:
https://lxxxxx.cn/web/api/notify_order

注意:这个回调url必须能公网访问的哦,不能是本地环境的链接

由于pay.transactions_jsapi返回的是一个promise对象,因此我们使用async和await函数进行接收结果,其中result就是微信小程序api发起支付所需要的参数。

例如我在项目里的回调处理大致如下:

 router.post('/notify_order', async (req, res) => {
        try {
            // 申请的APIv3
            let key = '3SdsdfdfGK2Yuehi67UH3xxxxxxxxx';
            let {
                ciphertext,
                associated_data,
                nonce
            } = req.body.resource;
            // 解密回调信息
            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
                        }
                    })
                    //删除购物车对应商品
                    let _ids = [];
                    let domStr="";//发送订单邮件使用
                    orderInfo.goodsList.forEach((v,i)=>{
                        _ids.push(v._id)
                        domStr += `<div>商品${i+1}:</div>
                        <div>
                        <div>商品名称:${v.goodsName}</div>
                        <div>商品规格:${v.specification}</div>
                        <div>数量:${v.quantity}</div>
                        <div><image style="height:350px;width:350px" src=${v.mainImage}></image></div>
                        </div>`
                    })

                    //删除购物车数据
                    await shopcart.remove({
                        userId: orderInfo.createdUser,
                        _id: {$in: _ids},
                    })

                    //发送邮件给商家提醒
                    sendMail("54357xxx@qq.com","您有新的订单!",
                    `订单编号:${orderInfo.orderNumber}<br/> 
                    下单时间:${orderInfo.createdTime}<br/> 
                    订单金额:${orderInfo.money}元<br/> 
                    收货人:${orderInfo.address.contactName},${orderInfo.address.mobile}<br/> 
                    收货地址:${orderInfo.address.mainAddress + orderInfo.address.detailAddress}<br/> 
                    送达时间:${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: "失败",
            });
        }
    });

根据上面代码可以看出,我在微信支付回调的url中首先判断处理状态trade_state === 'SUCCESS';其次再根据订单号查询该订单的支付信息,如果还是未支付状态这个时候就可以修改成支付完成状态了;最后发送邮件给商家告知有一笔新订单。

4.小程序前端下单部分代码

//微信支付 调用后端服务的 /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("支付取消")
                            }
                        });
                    }
                })
            }

不难看出上面的代码在调用接口成功后返回了微信支付需要的一系列参数;在小程序前端我使用的是uniapp的uni.requestPayment方法调取微信支付,该方法需要的参数也在后端接口进行了返回,至此微信小程序一整套的支付流程就结束了。

5.小程序微信支付退款

退款和支付类似也一样有一个notify_url回调地址,代码如下:

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: "操作成功",
         });
     });

    //微信支付退款回调通知
    router.post('/notify_refund', async (req, res) => {
        try {
            // 申请的APIv3
            let key = '3SdsdfdfGK2Yuehi67UH3xxxxxxxxx';
            let {
                ciphertext,
                associated_data,
                nonce
            } = req.body.resource;
            // 解密回调信息
            const result = pay.decipher_gcm(ciphertext, associated_data, nonce, key);
            // logger.info("解密回调参数 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,//从退款中状态修改为退款成功状态
                            refundOrderNumber: result.out_refund_no
                        }
                    })
                    res.status(200);
                    res.send({
                        code: 'SUCCESS',
                        message: "成功",
                    });
                }
            }
            
        } catch (error) {
            res.status(500);
            res.send({
                code: 'FAIL',
                message: "失败",
            });
        }
    });

小程序前端调用/order/wx/refund接口,服务端在微信支付退款回调通知里处理订单状态,至此小程序微信支付退款也完成了。
如果文档内有描述有误的地方还请各位指出,谢谢!本篇over~

参考文档:
https://www.npmjs.com/package/wechatpay-node-v3
https://blog.csdn.net/weixin_45952249/article/details/126216205

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容