微信服务号支付

环境

微信认证服务号,已开通微信支付

支付域名:wxpay.cyrilxu.cn

语言:nodejs

支付流程

配置

  • 网页授权,为了获取openid。详见上一篇
  • 配置商户,在公众号的微信支付页面,关联微信商户号
  • 在微信支付商户设置,产品中心 —> 开发配置 —>支付配置—>JSAPI授权目录。现在支持根目录了
  • 在微信商户设置中,账户中心—>API安全—>API密钥,设置API密钥,记住。牢记,以后只能修改不能查看。存放在服务器端

具体流程

  1. 获取openid,详见上一篇
  2. 生成订单
  3. 调用统一下单API获取预付单信息(prepay_id)
  4. 生成jsapi付款参数并签名
  5. 付款页付款,成功则跳转成功页
  6. 后台接收付款信息
  7. 前台请求付款信息

Talk is cheap, show you the code

  1. 获取openid,微信获取用户信息

  2. 生成订单信息:

    1
    2
    3
    4
    5
    6
    {
    openid:openid,
    order_num: 自己系统的订单号,
    pay_money: 付款金额,单位为分,
    attach: 产品名称
    }
  1. 获取prepay_id:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    /**
    * 获取微信统一下单的接口数据
    */
    getPrepayId: async function(obj, config){
    // var that = this;
    // 生成统一下单接口参数
    var UnifiedorderParams = {
    appid : config.wxappid,
    attach : obj.attach,
    body : obj.body,
    mch_id : config.mch_id,
    nonce_str: obj.nonce_str,
    notify_url : obj.notify_url,// 微信付款后的回调地址
    openid : obj.openid,
    out_trade_no : obj.order_num, //订单号
    // spbill_create_ip : obj.spbill_create_ip,
    total_fee : obj.total_fee,
    trade_type : 'JSAPI',
    // sign : getSign(),
    };
    UnifiedorderParams.sign = this.getSign(UnifiedorderParams); //签名算法,见附录
    var postBody = this.getUnifiedorderXmlParams(UnifiedorderParams); //生成xml
    var url = 'https://api.mch.weixin.qq.com/pay/unifiedorder';

    const opt = {
    url: url,
    body: postBody,
    method: 'POST',
    // json: true,
    resolveWithFullResponse: true,
    }
    const {statusCode, body} = await request(opt);
    if (statusCode === 200) {
    var prepay_id = this.getXMLNodeValue('prepay_id', body.toString("utf-8"));
    var tmp = prepay_id.split('[');
    var tmp1 = tmp[2].split(']');
    prepay_id = tmp1[0];
    return prepay_id;
    }
    return null;
    },
  1. 生成jsapi参数:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    /**
    * 微信支付的所有参数
    * @param req 请求的资源, 获取必要的数据
    * @returns {appId: string, timeStamp: Number, nonceStr: *, package: string, signType: string, paySign: *}
    */
    getBrandWCPayParams: async function(obj, config){
    obj.nonce_str = this.createNonceStr(); //随机字符串
    //统一下单id
    const prepay_id = await this.getPrepayId(obj);
    var wcPayParams = {
    "appId" : config.wxappid, //公众号名称,由商户传入
    "timeStamp" : parseInt(new Date().getTime() / 1000).toString(), //时间戳,自1970年以来的秒数
    "nonceStr" : obj.nonce_str, //随机串
    // 通过统一下单接口获取
    "package" : "prepay_id="+prepay_id,
    "signType" : "MD5" //微信签名方式:
    };
    wcPayParams.paySign = this.getSign(wcPayParams); //微信支付签名
    return wcPayParams;
    },
  1. 付款页js:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    var wxJsApiParam;//微信下单参数
    var errMsg = '';//微信返回支付错误信息
    var wexin_state = 1;//微信按钮初始状态为1,点击可下单付款,2时点击不能下单仅付款,3时不能点击

    $('#paylink').on('click', function () {
    console.log('wexin_state', wexin_state);
    if (wexin_state == 1){
    wexin_state = 3;
    goWepay();
    } else if (wexin_state == 2) {
    callpay();
    } else {
    return;
    }
    });
    //下单
    function goWepay() {
    $.ajax({
    url: 'dopay',//获取微信支付jsapi参数:wcPayParams
    type: 'POST',
    dataType: 'json',
    data:{
    openid:openid,
    order_num: order_num,
    attach:attach,
    pay_money:pay_money
    },
    success:function(data){
    console.log('data:', data);
    wexin_state = data.state;
    wxJsApiParam = data.param;
    callpay();
    }
    });
    }

    //调用微信JS api 支付
    function callpay() {
    console.log('WeixinJSBridge:', WeixinJSBridge);
    if (typeof WeixinJSBridge == "undefined") {
    if (document.addEventListener) {
    document.addEventListener('WeixinJSBridgeReady', jsApiCall,false);
    } else if (document.attachEvent) {
    document.attachEvent('WeixinJSBridgeReady', jsApiCall);
    document.attachEvent('onWeixinJSBridgeReady', jsApiCall);
    }
    } else {
    onBridgeReady();
    }
    }
    function onBridgeReady(){
    console.log('wxJsApiParam:',wxJsApiParam);
    if (wxJsApiParam != null) {//如果订单生成成功 %>
    WeixinJSBridge.invoke(
    'getBrandWCPayRequest',
    wxJsApiParam,//json串
    function(res){
    if(res.err_msg == "get_brand_wcpay_request:ok" ) {
    console.log("ok");
    window.location.href = "success?order=" + order_num;
    }
    // 使用以上方式判断前端返回,微信团队郑重提示:res.err_msg将在用户支付成功后返回
    // ok,但并不保证它绝对可靠。
    else {
    console.log("fail");
    console.log(res);
    // window.location.href = "/weixinpay/failed"
    // alert("支付失败")
    }
    WeixinJSBridge.log(res.err_msg);
    }
    );
    }
    }
  1. 接收付款信息:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    router.post('/notify', async function (req, res, next) {
    // console.log('notify post:', body);
    req.on("data",async function (data) {
    xmlParser.parseString(data.toString(), async function(err, result) {
    var body = result.xml;
    var keys = Object.keys(body);
    var parseBody = {};
    keys.forEach(function (key) {
    if (key!='sign') {
    parseBody[key] = body[key][0];
    }
    });
    // console.log(parseBody);
    if (parseBody.result_code == 'SUCCESS' && wx.getSign(parseBody) == body.sign[0]){
    //业务代码
    //发送成功接收
    var xml = {xml:{
    return_code:'SUCCESS',
    return_msg:'OK'
    }};
    res.send(xmlBuilder.buildObject(xml))
    }
    });
    })
    });
  1. ajax查询付款信息:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    function checkCode() {
    $.ajax({
    url: 'checkOrder?order=<%=order_num%>',
    method: 'GET',
    success: (data)=>{
    if (data.state == 200) {
    $('#check').html(data.data);
    } else {
    setTimeout(checkCode, 3000);
    }
    },
    error: ()=>{
    setTimeout(checkCode, 3000);
    }
    })
    }
    $(()=>{checkCode()})

附录

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
/**
* 获取随机的NonceStr
*/
createNonceStr: function() {
return Math.random().toString(36).substr(2, 15);
},
/**
* 获取微信支付的签名
* @param payParams
*/
getSign: function(args, config = aijia){
var keys = Object.keys(args);
keys = keys.sort();
var newArgs = {};
keys.forEach(function (key) {
if (args[key] && args[key]!=='')
newArgs[key] = args[key];
});

var string = '';
for (var k in newArgs) {
string += '&' + k + '=' + newArgs[k];
}
string = string.substr(1) + '&key='+config.wxpaykey;
return crypto.createHash('md5').update(string,'utf8').digest('hex').toUpperCase();
},

/**
* 生成微信统一下单参数
*/
getUnifiedorderXmlParams : function(obj, config = aijia){
var body = '<xml> ' +
'<appid>'+config.wxappid+'</appid> ' +
'<attach>'+obj.attach+'</attach> ' +
'<body>'+obj.body+'</body> ' +
'<mch_id>'+config.mch_id+'</mch_id> ' +
'<nonce_str>'+obj.nonce_str+'</nonce_str> ' +
'<notify_url>'+obj.notify_url+'</notify_url>' +
'<openid>'+obj.openid+'</openid> ' +
'<out_trade_no>'+obj.out_trade_no+'</out_trade_no>'+
'<spbill_create_ip></spbill_create_ip> ' +
'<total_fee>'+obj.total_fee+'</total_fee> ' +
'<trade_type>JSAPI</trade_type> ' +
'<sign>'+obj.sign+'</sign> ' +
'</xml>';
return body;
},

/**
* 获取xml中node值
*/
getXMLNodeValue: function(node_name, xml) {
var tmp = xml.split("<" + node_name + ">");
var _tmp = tmp[1].split("</" + node_name + ">");
return _tmp[0];
},