微信开发——H5微信支付

4,194 阅读2分钟

微信H5支付,分享下自己实际使用中踩过的坑

调用微信支付,我们首先需要获取到微信用户的基本资料,在微信开发——网页H5授权,获取微信用户资料一文中介绍到了如何获取微信用户信息,获取到后,我们保存在webstorage中,其他的页面也都能使用了,本文记录一下微信H5支付的配置以及需要注意的问题。

微信支付配置

在公众号里面接入支付申请

注意:公众号需要关联商户号,否则提示 appid和mch_id不匹配

商户秘钥需要动态获取,商户支付密钥自己进行配置32位字母

进入pay.weixin.qq.com配置微信支付的相关开发项

支付目录的配置,一般到控制器层就好了比如:http://www.testxxx.com/ticketSys/

注意:如果使用阿里云ECS,调用微信【统一下单】支付接口可能存在超时问题,是DNS解析的问题,将服务器的DNS配置为腾讯公共DNS:119.29.29.29

阿里云dns: 首选:10.202.72.116 备选:10.202.72.118

微信H5支付接口调用

调用微信支付,首先要调用【统一下单】接口,微信接口地址参考

调用微信统一下单接口,需要appId,商户号,商户支付秘钥,但是我们是在html前台中进行调用,这些秘钥信息是不能暴露在客户端的,需要服务端去调用,前端传递公开信息(金额,备注)就行

支付,前端请求代码,获取微信客户端支付需要的参数(都是服务端生成好的参数返回给前端JsPayApiParams):

$.ajax({
    type: "post",
    data: {
        totalfee: vChargeVal,               // 用户支付金额
        openId: app.wxUser.openId           // 当前用户的openId,必须
    },  
    url: "/Home/GetJsPayApi",
    dataType: "json",
    success: function (data) {
        if (data.isSuccess) {
            // 获取到后端返回的微信jsApi支付参数,后面代码有详解,调用微信客户端支付
            onBridgeReady(data.data);
        }
        else {
            alert("调用微信支付模块失败,可能网络服务器原因,请您重试:" + data.msg);
        }
    },
    error: function () {
        alert("调用微信支付模块失败,可能网络服务器原因,请您重试");
    }
})

//调用微信支付模块   WeixinJSBridge 这个是在微信客户端浏览器才会有的对象
function onBridgeReady(json) {
    WeixinJSBridge.invoke(
        'getBrandWCPayRequest', {
            "appId": json.appId,                                                //公众号名称,由商户传入
            "timeStamp": json.timeStamp,                                         //时间戳,自1970年以来的秒数
            "nonceStr": json.nonceStr,                                                  //随机串
            "package": json.package,
            "signType": "MD5",                                                          //微信签名方式:
            "paySign": json.paySign                                                 //微信签名
        },
        function (res) {
            if (res.err_msg == "get_brand_wcpay_request:ok") {
                alert('支付成功,请稍后查询,正在保存订票信息,如有疑问,请联系管理员..')
                
                // 提交用户数据到数据库操作...
                
                location.href = "/Home/MyTicket";
                //不要在这里提交最后的数据到数据库,这里可能不正常(微信团队郑重提示:res.err_msg将在用户支付成功后返回ok,但并不保证它绝对可靠。)
            }
            else {
                console.log('关闭了微信支付')
            }
        });
}

再看关键的服务端代码,返回微信H5JSAPI支付需要的参数:

/// <summary>
/// 预下单,获取jspay api参数
/// </summary>
/// <returns></returns>
[HttpPost]
public async Task<JsonResult> GetJsPayApi(UserOrder buyer)
{
    try
    {
        #region 获取客户端参数 & 校验
        string strTotal_fee = Request.Form["totalfee"];
        string strFee = (double.Parse(strTotal_fee) * 100).ToString();
        //JSAPI支付预处理
        //若传递了相关参数,则调统一下单接口,获得后续相关接口的入口参数

        string openId = string.Empty;         //注意这里需要当前用户openId

        if (Session["openid"] != null)
        {
            openId = Session["openid"].ToString();         //注意这里需要当前用户openId
        }

        int totalFee = int.Parse(strFee);
        var wxPayConfig = new WeiXinPayConfig()
        {
            AppId = WxPayConfig.APPID,
            MchId = WxPayConfig.MCHID,
            OpenId = openId,
            NotifyUrl = WxPayConfig.NOTIFY_URL,
            MchPayKey = WxPayConfig.KEY,
            ServerIp = WxPayConfig.IP,
            TradeType = "JSAPI",
            OutTradeNo = Guid.NewGuid().ToString("N").ToUpper()
        };

        #endregion
        buyer.PayStatus = false;
        buyer.PayOrderNo = wxPayConfig.OutTradeNo;
        _userOrder.Insert(buyer);
        var unifiedOrderData = await WxPayHelper.UnifiedOrder(wxPayConfig, totalFee);
        var jsApiPayData = WxPayHelper.GetJsApiParams(wxPayConfig.MchPayKey, unifiedOrderData);
        return Success(jsApiPayData);
    }
    catch (Exception ex)
    {
        return Fail(ex.Message);
    }
}

代码说明:

await WxPayHelper.UnifiedOrder(wxPayConfig, totalFee) 这个是封装的调用微信支付的【统一下单接口】,相当于预下单,参考接口说明,比较简单,需要注意的是这里的金额是为单位,这样的设计在电商中比较普遍,避免小数点的很多问题。

WxPayHelper.GetJsApiParams(wxPayConfig.MchPayKey, unifiedOrderData) 这个是获取H5客户端JSAPI支付需要的参数,参考接口文档,也就是上面调用onBridgeReady方法唤起支付需要的参数,

微信H5内唤起支付package参数需要【统一下单】的返回值,所以要预先调用这个接口了

这样我们拿到JsApiPayParamsJS支付的参数,就可以直接调用起支付了

如上就可以通过微信内H5唤起微信客户端的支付功能进行付款了,但是这样就好了吗?想的太美了

实际遇到的问题

在上线后,陆陆续续的发现部分安卓机支付了,后台没有数据,检查了日志,服务端也没有报错,那么肯定的是,微信客户端没有执行我回调成功的代码

if (res.err_msg == "get_brand_wcpay_request:ok") { // 提交用户数据到数据库... }

去查阅了微信支付这块的文档,//不要在这里提交最后的数据到数据库,这里可能不正常(微信团队郑重提示:res.err_msg将在用户支付成功后返回ok,但并不保证它绝对可靠。),表示微信客户端显示支付成功,并不一定可靠啊,微信自己都不相信自己,我信你个鬼啊,所以部分机器没有执行这里的回调逻辑...

改进程序逻辑

在统一下单的时候,我们会传递一个回调地址给微信,如果真正支付成功了,微信会将支付成功的回调信息传递给我们,双方确认,这是异构系统双方识别成功的重要条件吧

根据微信的回调,我们才能判断哪些订单真正支付成功了,然后更新我们数据库的数据,参考微信支付结果通知接口文档

在我们调用【统一下单】的时候,我们会自动生成一个订单号 out_trade_no传递给微信,我们将客户下单的信息和这个订单号关联起来(之前代码不是这么写的)

微信支付成功结果通知给我们,会把订单号带上,我们就可以根据这个订单号进行判断,如果有回调通知,就表示支付成功了,那么改变这个订单号的状态为支付成成功就好了

微信支付通知回调报文:

<xml><appid><![CDATA[wx1393620e886ef***]]></appid>
<attach><![CDATA[附加信息:]]></attach>
<bank_type><![CDATA[ABC_DEBIT]]></bank_type>
<cash_fee><![CDATA[9950]]></cash_fee>
<device_info><![CDATA[WEB]]></device_info>
<fee_type><![CDATA[CNY]]></fee_type>
<is_subscribe><![CDATA[N]]></is_subscribe>
<mch_id><![CDATA[1546577***]]></mch_id>
<nonce_str><![CDATA[804b9ff2ad7d4f2180a872694c608***]]></nonce_str>
<openid><![CDATA[oysLsw_FjFSgiAx0R6PBniyS1***]]></openid>
<out_trade_no><![CDATA[C8D24B9622B144A4B5AFF5D4DD8F3***]]></out_trade_no>
<result_code><![CDATA[SUCCESS]]></result_code>
<return_code><![CDATA[SUCCESS]]></return_code>
<sign><![CDATA[3D10FCE425D8D104A551C1800CEC2***]]></sign>
<time_end><![CDATA[20190727204913]]></time_end>
<total_fee>9950</total_fee>
<trade_type><![CDATA[JSAPI]]></trade_type>
<transaction_id><![CDATA[4200000384201907273227620***]]></transaction_id>
</xml>

解析通知的xml报文,服务端代码示例:

/// <summary>
/// 微信支付回调
/// </summary>
/// <returns></returns>
[Route("notify")]
public ActionResult Notify()
{
    string requestData = GetPostedString();
    _log.Info("收到微信notify支付回调通知:" + requestData);
    WeiXinPayData notifyResut = new WeiXinPayData();
    notifyResut.FromXml(requestData);
    if (!notifyResut.IsSet("transaction_id") || notifyResut.Get("transaction_id").ToString() == "")                      //若微信支付订单号不存在,直接响应错误给微信
    {
        return Content(GetNotifyMsg(false, "微信订单号不存在,响应失败!"));
    };
    string outTradeNo = notifyResut.Get("out_trade_no").ToString();                                                     //商户系统内部订单号
    string payResultCode = notifyResut.Get("result_code").ToString();
    if (!string.IsNullOrEmpty(outTradeNo) && payResultCode == "SUCCESS")
    {
        var userOrder = _userOrder.Find(c => c.PayOrderNo == outTradeNo);
        userOrder.PayStatus = true;
        _userOrder.SaveChanges();
        _log.Info($"微信回调商户支付单号:【{outTradeNo}】,购票人openId:{userOrder.OpenId},姓名:{userOrder.Name},电话:{userOrder.MobileNo},总金额:【{userOrder.Amount}】,支付确认成功!");
    }
    _log.Info("回调成功信息响应给微信:" + GetNotifyMsg(true, "OK"));   
    //订单验证成功后,将成功消息应答给微信回调  相当于再次给微信确认信息,表示已经收到了通知,不然微信会以为你没有收到通知消息,会再重试发给你几次
    return Content(GetNotifyMsg(true, "OK"));
}

/// <summary>
/// 生成成功/失败信息应答给微信支付回调
/// </summary>
/// <param name="errorMsg"></param>
/// <returns></returns>
private string GetNotifyMsg(bool isSuccess, string msg)
{
    WeiXinPayData replyToWx = new WeiXinPayData();
    replyToWx.Set("return_code", isSuccess ? "SUCCESS" : "FAIL");
    replyToWx.Set("return_msg", msg);
    // _log.Info("生成回推微信消息:" + replyToWx.ToXml());
    return replyToWx.ToXml();
}

这样服务端双方的通知就真正表示支付成功了。