微信浏览器中的微信支付,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)是个好东西。检查下必填项是否全部填写,再用工具校验下你生成的数据跟校验工具生成的数据是否一致,参数没问题,一般也就没有其他的问题了

©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页