node.js实现微信支付退款

缘起

有付款就会有退款

注意,退款支持部分退款

左口袋的钱退到右口袋罗

退款0.01元

这次发起的退款请求0.01元是实时到账的,因此,用户在小程序端发起的退款只是一个请求到后台,后台审核人员审核无误后才后微信发起退款操作。

引入第三方module

在package.json 中加入"weixin-pay": "^1.1.7"这一条

代码目录结构

目录结构如下

入参

{ transaction_id: '4200000005201712165508745023', // 交易
  out_trade_no: '5b97cba0ae164bd58dfe9e77891d3aaf', // 自己这头的交易号
  out_refund_no: '6f3240c353934105be34eb9f2d364cec', // 退款订单,自己生成
  total_fee: 1, // 退款总额
  nonce_str: '1xSZW0op0KcdKoMYxnyxhEuF1fAQefhU', // 随机串
  appid: 'wxff154ce14ad59a55', // 小程序 appid
  mch_id: '1447716902', // 微信支付商户id
  sign: '416FCB62F9B8F03C82E83052CC77524B' // 签名,weixin-pay这个module帮助生成 }

然后由wxpay为我们生成其余字段,比如nonce_str,sign,当然还少不了p12证书,
这个早选在wxpay初始代码里已经配置了,pfx: fs.readFileSync(__dirname + '/../../../cert/apiclient_cert.p12'), //微信商户平台证书

lib/wechat/utils/wxpay.js的源码

const WXPay = require('weixin-pay'); // 引入weixin-pay这个第三方模块
const {weapp} = require('../../../utils/config'); // 我自己的全局配置文件,包括了appid key 等
const fs = require('fs');

const wxpay = WXPay({
    appid: weapp.APPID,
    mch_id: weapp.MCHID,
    partner_key: weapp.KEY, //微信商户平台 API secret,非小程序 secret
    pfx: fs.readFileSync(__dirname + '/../../../cert/apiclient_cert.p12'), 
});

module.exports = wxpay;

另外还有一个util.js工具类

用于验证与错误回调

const wxpay = require('./wxpay');

const validateSign = results => {
  const sign = wxpay.sign(results);
  if (sign !== results.sign) {
    const error = new Error('微信返回参数签名结果不正确');
    error.code = 'INVALID_RESULT_SIGN';
    throw error;
  };
  return results;
};

const handleError = results => {
  if (results.return_code === 'FAIL') {
    throw new Error(results.return_msg);
  }
  if (results.result_code !== 'SUCCESS') {
    const error = new Error(results.err_code_des);
    error.code = results.err_code;
    throw error;
  }
  return results;
};

module.exports = {
  validateSign,
  handleError,
};

发起退款请求

退款逻辑是这样的,先从自己这边的Order数据表中查出transaction_id/out_trade_no/total_fee,再拼上自己生成的out_refund_no退款单号,本次退款部分金额refund_fee,最后由weixin-pay这个模块下的wxpay.refund调起就可以了,成功就把订单状态改成"退款成功"

// 退款
router.post('/refund', function(req, res) {
    Order.findById(req.body._id, (err, order) => {
        if (err) {
            console.log(err);
        }
        console.log(order);
        // 生成微信设定的订单格式
        var data = {
            transaction_id: order.transactionId,
            out_trade_no: order.tradeId,
            out_refund_no: uuid().replace(/-/g, ''),
            total_fee: order.amount,
            refund_fee: order.amount
        };
        console.log(data);
        // 先查询订单,再退订单
        wxpay.refund(data, (err, result) => {
            if (err) {
                console.log(err);
                res.send(
                    utils.json({
                        code: 500,
                        msg: '退款失败'
                    })
                );
            }
            // 返回退款请求成功后,要将订单状态改成REFUNDED
            if (result.result_code === 'SUCCESS') {
                console.log(result);
                order.status = 'REFUNDED';
                order.save((err, response) => {
                    res.send(
                        utils.json({
                            msg: '退款成功'
                        })
                    );
                });
            } else {
                res.send(
                    utils.json({
                        code: 500,
                        msg: result.err_code_des
                    })
                );
            }

        });
    });
});

入参的坑

1.这次遇到的坑是refund_fee忘记传值,也就是说微信退款是支持部分退款的,如果是全额退款,那么将它赋值为total_fee相同

2.网上说的op_user_id: weapp.MCHID这个参数是非必选的

3.transaction_id 与 out_trade_no 二选一即可,这样在没有记录transaction_id的情况(比如没有写支付成功的callback)下,也能发起退款;其中优先级前者大于后者,在我在分别前其一故意给错的过程中得到了验证。

4.报了一个appid与商户号不匹配的报错,return_code: 'FAIL', return_msg: '商户号mch_id与appid不匹配’原来是小程序还没绑定公众号微信支付,这真是一个乌龙。

成功退款微信返回的数据

1. appid:"wxff154ce14ad59a55"
2. cash_fee:"1"
3. cash_refund_fee:"1"
4. coupon_refund_count:"0"
5. coupon_refund_fee:"0"
6. mch_id:"1447716902"
7. nonce_str:"c44wOvB6a4bQJfRk"
8. out_refund_no:"9ace1466432a4d548065dc8df95d904a"
9. out_trade_no:"5b97cba0ae164bd58dfe9e77891d3aaf"
10. refund_channel:""
11. refund_fee:"1"
12. refund_id:"50000705182017121702756172970"
13. result_code:"SUCCESS"
14. return_code:"SUCCESS"
15. return_msg:"OK"
16. sign:"5C2E67B3250054E8A665BF1AE2E9BDA3"
17. total_fee:"1"
18. transaction_id:”4200000005201712165508745023”

重复退款将返回如下

1. appid:"wxff154ce14ad59a55"
2. err_code:"ERROR"
3. err_code_des:"订单已全额退款"
4. mch_id:"1447716902"
5. nonce_str:"KP1YWlU7a5viZEgK"
6. result_code:"FAIL"
7. return_code:"SUCCESS"
8. return_msg:"OK"
9. sign:”C2A7DED787BEA644C325E37D96E9F41C”
人工退款

最后

如果没有写退款功能或者不想写退款功能怎么办,其实可以从微信支付的后台pay.weixin.qq.com,也是能退款出去的,只是不想忘记了要人工将订单状态置为退款状态。

鸣谢

感谢weixin-pay作者提供这个module,上述源码移植于LeanCloud小程序支付的demo,git地址是https://github.com/leancloud/weapp-pay-getting-started.git,一并感谢。

praise
mp
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 230,791评论 6 545
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 99,795评论 3 429
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 178,943评论 0 384
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 64,057评论 1 318
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 72,773评论 6 414
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 56,106评论 1 330
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 44,082评论 3 450
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 43,282评论 0 291
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 49,793评论 1 338
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 41,507评论 3 361
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 43,741评论 1 375
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 39,220评论 5 365
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 44,929评论 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 35,325评论 0 28
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 36,661评论 1 296
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 52,482评论 3 400
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 48,702评论 2 380

推荐阅读更多精彩内容