微信開發——H5微信支付

微信H5支付,分享下本身實際使用中踩過的坑

調用微信支付,咱們首先須要獲取到微信用戶的基本資料,在微信開發——網頁H5受權,獲取微信用戶資料一文中介紹到了如何獲取微信用戶信息,獲取到後,咱們保存在webstorage中,其餘的頁面也都能使用了,本文記錄一下微信H5支付的配置以及須要注意的問題。php

微信支付配置

在公衆號裏面接入支付申請html

注意:公衆號須要關聯商戶號,不然提示 appid和mch_id不匹配 前端

商戶祕鑰須要動態獲取,商戶支付密鑰本身進行配置32位字母web

進入pay.weixin.qq.com配置微信支付的相關開發項ajax

支付目錄的配置,通常到控制器層就行了好比:http://www.testxxx.com/ticketSys/數據庫

注意:若是使用阿里雲ECS,調用微信【統一下單】支付接口可能存在超時問題,是DNS解析的問題,將服務器的DNS配置爲騰訊公共DNS:119.29.29.29 json

阿里雲dns: 首選:10.202.72.116 備選:10.202.72.118後端

微信H5支付接口調用

調用微信支付,首先要調用【統一下單】接口,微信接口地址參考

調用微信統一下單接口,須要appId,商戶號,商戶支付祕鑰,可是咱們是在html前臺中進行調用,這些祕鑰信息是不能暴露在客戶端的,須要服務端去調用,前端傳遞公開信息(金額,備註)就行api

支付,前端請求代碼,獲取微信客戶端支付須要的參數(都是服務端生成好的參數返回給前端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();
}
複製代碼

這樣服務端雙方的通知就真正表示支付成功了。

相關文章
相關標籤/搜索