微信瀏覽器中的微信支付,JSAPI支付,開發流程、常見問題

微信支付有兩種使用場景,一種是可以在微信之外的瀏覽器(如UC瀏覽器、手機自帶瀏覽器等)中使用,在微信外部喚醒微信進行支付;還有一種是在微信自帶內置瀏覽器中使用,比如微信公衆號裏邊的支付、給客戶在微信上發了一個支付鏈接等這類使用場景,這裏主要說的是後一種“JSAPI支付”(官方文檔地址:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=7_1),下面的內容默認你已經有商戶平臺和微信公衆號了。

JSAPI支付原理

直接調用微信的內置函數,就能直接喚起微信支付,難點在於微信支付需要的參數的獲取,下面是官方文檔,我們需要的就是這6個參數(這6個參數的拼寫是大小寫敏感的):
appId:就是微信公衆號的appid
timeStamp:時間戳(秒)
nonceStr:隨機串,這個隨意,隨機一個字符串就行了
package:這個裏邊存儲的是微信支付訂單的訂單號,這是一個難點,也是這6個參數裏邊唯一一個需要從微信官方獲取的參數,這個參數的值是"prepay_id=u802345jgfjsdfgsdg",是一個鍵值對,其中“u802345jgfjsdfgsdg888”是訂單號,這個是微信統一下單接口返回的,統一下單接口在具體實現流程裏邊說。

signType:參數簽名方式,就用MD5吧,與你進行簽名運算的參數保持一致

paySign:參數簽名,前面五個參數和值先排序(參數名按ASCII碼從小到大),然後進行拼接,組成的字符串進行MD5運算或者HMAC-SHA256運算的結果(微信參數簽名算法文檔:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=4_3

function onBridgeReady(){
   WeixinJSBridge.invoke(
      'getBrandWCPayRequest', {
         "appId":"wx24******",     //公衆號名稱,由商戶傳入     
         "timeStamp":"1395712654",         //時間戳,自1970年以來的秒數     
         "nonceStr":"e61463f8efa94090b1f366cccfbbb444", //隨機串     
         "package":"prepay_id=u802345jgfjsdfgsd",     
         "signType":"MD5",         //微信簽名方式:     
         "paySign":"70EA570631E4BB79628FBCA90534C63FF7FADD89" //微信簽名 
      },
      function(res){
      if(res.err_msg == "get_brand_wcpay_request:ok" ){
      // 使用以上方式判斷前端返回,微信團隊鄭重提示:
            //res.err_msg將在用戶支付成功後返回ok,但並不保證它絕對可靠。
      } 
   }); 
}
if (typeof WeixinJSBridge == "undefined"){
   if( document.addEventListener ){
       document.addEventListener('WeixinJSBridgeReady', onBridgeReady, false);
   }else if (document.attachEvent){
       document.attachEvent('WeixinJSBridgeReady', onBridgeReady); 
       document.attachEvent('onWeixinJSBridgeReady', onBridgeReady);
   }
}else{
   onBridgeReady();
}

JSAPI開發流程

1、商戶平臺和微信公衆號的設置

1.1商戶平臺(pay.weixin.qq.com)設置JSAPI支付支付目錄

即調用上面內置js函數的頁面目錄,設置路徑:商戶平臺-->產品中心-->開發配置。比如調用JSAPI支付的頁面路徑是www.abc.com/a/b/c/pay.html,你要設置的支付授權目錄是www.abc.com/a/b/c/,設置www.abc.com/a/是錯誤的,即頁面地址裏邊最後一個反斜槓之前的內容。

1.2在微信公衆號(mp.weixin.qq.com)裏邊配置能獲取用戶openid的域名

jsapi支付需要獲取用戶的openid,需要在微信統一下單接口中使用,設置路徑:公衆號設置-->功能設置-->網頁授權域名。用哪個域名獲取的openid就用設置哪個域名。

2、獲取用戶的openid

大致流程是打開特定格式的url,微信回調後會在頁面上添加一個code參數,然後用code參數獲取openid。scope可以使用snsapi_base模式,這種不用用戶手動授權,頁面跳轉無感知,缺點是隻能獲取到openid,不過用戶其他信息此處也不用獲取。程序上此處不再實現,這也是微信開發的基礎操作了(官方文檔:https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/Wechat_webpage_authorization.html)。

3、請求微信統一下單接口

封裝商戶id、公衆號id、訂單信息等數據,然後調用統一下單接口,注意在JSAPI支付中openid是必填的,統一接口的參數最好用SortedDictionary,可以自動對參數按照參數名進行排序,後續簽名方便,統一下單接口方法如下(官方文檔:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_1

string key = "123123在你的商戶平臺上設置";
SortedDictionary<string, object> strdata = new SortedDictionary<string, object>();
strdata.Add("appid", appid);//微信公衆賬號ID
strdata.Add("mch_id", mch_id);//商戶平臺上的商戶號
strdata.Add("nonce_str", nonce_str);//隨機字符串
strdata.Add("body", body);//商品描述,支付的時候顯示
strdata.Add("out_trade_no", out_trade_no);//訂單號,自己生成,可同時在數據庫添加訂單數據
strdata.Add("total_fee", total_fee);//總金額,單位是分,如果是元則換算成分(乘100)
strdata.Add("spbill_create_ip", spbill_create_ip);//終端ip	
strdata.Add("notify_url", notify_url);//微信異步通知的url鏈接
strdata.Add("trade_type", "JSAPI");//jsapi交易類型
strdata.Add("product_id", product_id);//商品ID,trade_type=NATIVE時的必填參數   
strdata.Add("openid", openid);//上一步獲取到的用戶的openid,trade_type=JSAPI時的必填參數
strdata.Add("sign_type", "MD5");//參數的簽名方法   
string strparam = ToUrl(strdata, key);//拼接參數成字符串
string sign = GetWxSign(strparam);//對字典數據進行MD5簽名
strdata.Add("sign", sign);//簽名
string retData = HttpPost("https://api.mch.weixin.qq.com/pay/unifiedorder", parseXML(strdata));

輔助函數:

/// <summary>
/// 拼接字典的參數和數值
/// </summary>
/// <param name="m_values"></param>
/// <param name="key"></param>
/// <returns></returns>
public string ToUrl(SortedDictionary<string, object> m_values, string key)
{
    string buff = "";
    foreach (KeyValuePair<string, object> pair in m_values)
    {
        if (pair.Value.ToString() != "")
        {
            buff += pair.Key + "=" + pair.Value + "&";
        }
    }
    buff += "key=" + key;
    return buff;
}
/// <summary>
/// 獲取變量簽名
/// </summary>
/// <param name="data">參數按英文字母排序後的字符串</param>
/// <returns></returns>
public string GetWxSign(string data)
{
    //MD5加密      
    var md5 = MD5.Create();
    var bs = md5.ComputeHash(Encoding.UTF8.GetBytes(data));
    var sb = new StringBuilder();
    foreach (byte b in bs)
    {
        sb.Append(b.ToString("x2"));
    }
    string sign = sb.ToString().ToUpper();
    return sign;
}
/// <summary>
/// post 參數 轉換成xml
/// </summary>
/// <param name="parameters"></param>
/// <returns></returns>
public string parseXML(SortedDictionary<string, object> parameters)
{
    StringBuilder sb = new StringBuilder();
    sb.Append("<xml>");
    foreach (string k in parameters.Keys)
    {
        string v = (string)parameters[k];
        if (Regex.IsMatch(v, @"^[0-9.]$"))
        {

            sb.Append("<" + k + ">" + v + "</" + k + ">");
        }
        else
        {
            sb.Append("<" + k + "><![CDATA[" + v + "]]></" + k + ">");
        }

    }
    sb.Append("</xml>");
    return sb.ToString();
}
/// <summary>
/// HTTP POST方式請求數據
/// </summary>
/// <param name="url">URL.</param>
/// <param name="param">POST的數據</param>
/// <returns></returns>
public string HttpPost(string url, string param)
{
    HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(url);
    request.Method = "POST";
    request.ContentType = "application/x-www-form-urlencoded";
    request.Accept = "*/*";
    request.Timeout = 15000;
    request.AllowAutoRedirect = false;
    StreamWriter requestStream = null;
    WebResponse response = null;
    string responseStr = null;
    try
    {
        requestStream = new StreamWriter(request.GetRequestStream(), Encoding.UTF8);
        requestStream.Write(param);
        requestStream.Close();

        response = request.GetResponse();
        if (response != null)
        {
            StreamReader reader = new StreamReader(response.GetResponseStream(), Encoding.UTF8);
            responseStr = reader.ReadToEnd();
            reader.Close();
        }
    }
    catch (Exception ex)
    {
        return ex.Message;
    }
    finally
    {
        request = null;
        requestStream = null;
        response = null;
    }

    return responseStr;
}

到這裏,我們終於獲取到了從返回的數據中可以獲取到prepay_id(預支付交易會話標識,也可以理解成微信的預支付訂單id),也就湊齊了JSAPI的6個參數,這6個參數裏邊還有個小問題,就是paySign是另外5個參數的簽名運算,這裏還在後端進行吧,還是使用MD5。

4、封裝JSAPI所需的6個參數

注意預支付訂單參數的拼接方式,另外這是後端代碼,最終的結果是一個字符串,js調用這個參數的時候注意將字符串轉化爲json格式,轉化方法不再贅述。

SortedDictionary<string, object> m_values = new SortedDictionary<string, object>();
m_values.Add("appId", appId);//微信公衆號的appid
m_values.Add("timeStamp", timeStamp);//時間戳
m_values.Add("nonceStr", nonce_str);//隨機字符串
m_values.Add("package", "prepay_id=" +prepay_id);//預支付訂單,注意參數的拼接
m_values.Add("signType", "MD5");//簽名算法
string wxdataStr = ToUrl(m_values, key);//拼接參數字符串
string paysign = GetWxSign(wxdataStr);//計算參數字符串的MD簽名
m_values.Add("paySign", paysign);
string json = JsonConvert.SerializeObject(m_values);//轉化成json格式

5、前端調取微信內置方法

本人第4步的實現是通過異步調取的,前端JS實現如下:

var wxData;
$.ajax({
    type: "post",
    async: false,
    url: '/ashx/WeiXinPay',
    data: { "method": "wxpaycreate" },
    success: function (data) {
        if (data && data != "") {
            wxData = JSON.parse(data);
            console.log(wxData);
            var agent = navigator.userAgent.toLowerCase();
            var indexStart = agent.indexOf("micromessenger/");
            var version = agent.substr(indexStart + 1, 1);

            if (parseInt(version) < 5) {
                alert('您的微信版本低於5.0,無法使用微信支付!');
                return false;
            }
            if (typeof WeixinJSBridge == "undefined") {
                if (document.addEventListener) {
                    document.addEventListener('WeixinJSBridgeReady', onBridgeReady, false);
                } else if (document.attachEvent) {
                    document.attachEvent('WeixinJSBridgeReady', onBridgeReady);
                    document.attachEvent('onWeixinJSBridgeReady', onBridgeReady);
                }
            } else {
                onBridgeReady();
            }
        }
    }
});
function onBridgeReady() {
    WeixinJSBridge.invoke(
        'getBrandWCPayRequest', wxData,
        function (res) {
            if (res.err_msg == "get_brand_wcpay_request:ok") {
                // 使用以上方式判斷前端返回,微信團隊鄭重提示:
                //res.err_msg將在用戶支付成功後返回ok,但並不保證它絕對可靠。
                alert('支付成功,返回訂單列表!');
                window.location.href = "onlinepay.html?forumid=" + localStorage.getItem("forumid") + "&companyid=" + localStorage.getItem("companyid") + "&ispay=1";
            } else if (res.err_msg == 'get_brand_wcpay_request:cancel') {
                alert('取消支付!');
            } else {
                alert("支付失敗!");
            }
        });
}

到這裏如果提示支付成功,則表示錢已經到你的微信賬戶了,但還少一步。目前只是前端提示支付成功,數據庫裏邊的訂單表中,訂單的支付狀態還沒變,這就要用到統一下單接口裏邊的notify_url參數了,這個你設置的接收微信的異步通知接口,如果支付成功,微信會請求你這個接口,下面就是最後一步,修改訂單的支付狀態。

7、微信請求異步接口,修改訂單裝

notify_url頁面接收到的數據的處理流程

// 接收從微信後臺POST過來的數據
System.IO.Stream s = Request.InputStream;
StringBuilder builder = new StringBuilder();
int count = 0;
byte[] buffer = new byte[1024];

while ((count = s.Read(buffer, 0, 1024)) > 0)
{
    builder.Append(Encoding.UTF8.GetString(buffer, 0, count));
}
s.Flush();
s.Close();
s.Dispose();
if (builder.ToString().Length > 0)
{
    XmlDocument xmldoc = new XmlDocument();
    xmldoc.LoadXml(builder.ToString());//返回的數據轉換成XML對象
    string result_code = xmldoc.SelectSingleNode("xml").SelectSingleNode("result_code").InnerText;    
    if (result_code == "SUCCESS")
    {
        //支付成功,根據微信返回的訂單號修改數據庫的訂單狀態 
        string trade_no= xmldoc.SelectSingleNode("xml").SelectSingleNode("out_trade_no").InnerText;//統一下單接口中你設置的訂單號
        string wxorderid = xmldoc.SelectSingleNode("xml").SelectSingleNode("transaction_id").InnerText;//微信支付訂單的訂單號
    }
    else
    {
        //支付失敗,做好支付失敗的日誌記錄
    }
}
else
{
   //數據異常,添加日誌記錄
}

其他注意事項

其實微信支付這塊,比較麻煩的還是參數傳遞、校驗這塊,微信的“接口簽名校驗工具”(https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=20_1)是個好東西。檢查下必填項是否全部填寫,再用工具校驗下你生成的數據跟校驗工具生成的數據是否一致,參數沒問題,一般也就沒有其他的問題了

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章