zoukankan      html  css  js  c++  java
  • 微信支付[v3]

    V2升级V3 顺便记录一下 ,文档: http://pay.weixin.qq.com/wiki/doc/api/index.html

    !!!

    • 支付授权目录与测试人的微信帐号白名单(出现access_denied或access_not_allow错误,请检查是否设置正确)
    • 微信签名(用于jssdk调用支付,查询订单,异步参数签名等)
    • 数字证书,用于微信退款

    image

    官方:

    微信内网页支付时序图

    参数签名

    对所有传入参数按照字段名的ASCII 码从小到大排序(字典序)后,使用URL 键值对的格式(即key1=value1&key2=value2…)拼接成字符串string1,注意:值为空的参数不参与签名 在string1 最后拼接上key=Key( 商户支付密钥) 得到stringSignTemp 字符串, 并对stringSignTemp 进行md5 运算,再将得到的字符串所有字符转换为大写,得到sign 值signValue

            /// <summary>
            /// 创建微信Sign
            /// </summary>
            /// <param name="key">微信商户支付密钥</param>
            /// <param name="dict">参数字典</param>
            /// <returns></returns>
            public string GenerateWxSign(string key, SortedDictionary<string, string> dict)
            {
                try
                {
                    string str = string.Empty;
                    foreach (var item in dict)
                    {
                        if (item.Key != "sign" && item.Value.ToString() != "")
                        {
                            str += item.Key + "=" + item.Value + "&";
                        }
                    }
                    str = str.Trim('&');
                    //在string后加入API KEY
                    str += "&key=" + key;
                    //MD5加密
                    var md5 = System.Security.Cryptography.MD5.Create();
                    var bs = md5.ComputeHash(Encoding.UTF8.GetBytes(str));
                    var sb = new StringBuilder();
                    foreach (byte b in bs)
                    {
                        sb.Append(b.ToString("x2"));
                    }
                    //所有字符转为大写
                    return sb.ToString().ToUpper();
                }
                catch (Exception)
                {
                    return string.Empty;
                }
            }

    异步通知

    基本按照文档来,返回的参数要使用流的方式来读取XML,另外验证参数的合法性可以验证签名或调用一次微信订单查询接口

        /// <summary>
        /// 微信支付异步回调控制器【V3版本】
        /// </summary>
        public class WeixinController : Controller
        {
            private static readonly Logger logger = LogManager.GetCurrentClassLogger();
    
            /// <summary>
            /// 订单服务
            /// </summary>
            private readonly IOrderService _orderService;
    
            /// <summary>
            /// 微信服务
            /// </summary>
            private readonly IWeixinService _weixinService;
    
            /// <summary>
            /// 构造函数
            /// </summary>
            /// <param name="orderService">订单服务</param>
            /// <param name="weixinService">微信服务</param>
            public WeixinController(IOrderService orderService, IWeixinService weixinService)
            {
                _orderService = orderService;
                _weixinService = weixinService;
            }
    
            /// <summary>
            /// 微信Wap支付异步通知
            /// </summary>
            /// <returns></returns>
            public async Task<ActionResult> Notify()
            {
                int intLen = Convert.ToInt32(Request.InputStream.Length);
                if (intLen <= 0)
                {
                    return Content(AjaxResult.Error("WeixinController NO Wap Notify PARAMS!").ToStringSafe());
                }
                try
                {
                    var dict = new SortedDictionary<string, string>();
                    //dict = @"appid=wxqe353dd942e01&attach=F&bank_type=CFT&cash_fee=1&fee_type=CNY&is_subscribe=Y&mch_id=10036766&nonce_str=e9605038cccbffe5ed2b2eb095345d18&openid=oIO3gjsiGqv7eeuAKhbB_V83V528&out_trade_no=68047857&result_code=SUCCESS&return_code=SUCCESS&sign=85A469F874619EAB924FA5B7EB779444&time_end=20150526183632&total_fee=1&trade_type=JSAPI&transaction_id=1001640542201505260168707842".ConvertStringToSortedDictionary();
                    var xmlDoc = new XmlDocument();
                    xmlDoc.Load(Request.InputStream);
                    var root = xmlDoc.SelectSingleNode("xml");
                    XmlNodeList xnl = root.ChildNodes;
                    foreach (XmlNode xnf in xnl)
                    {
                        dict.Add(xnf.Name, xnf.InnerText);
                    }
                    if (dict.Count <= 0)
                    {
                        return Content(AjaxResult.Error("NO Notify PARAMS!").ToStringSafe());
                    }
                    logger.Info("【 WeixinController Wap Notify SDKUtil.ConvertDictionaryToString : 请求报文=[" + dict.ConvertDictionaryToString() + "]
    ");
                    //验证参数签名
                    string signK = _weixinService.GenerateWxSign(WxPayConfig.KEY, dict);
                    if (signK != dict["sign"])
                    {
                        logger.Info("WeixinController Wap Notify Verify WxSign Error : 请求报文=[signK " + signK + " : dict['sign'] " + dict["sign"] + "]
    ");
                        return Content(AjaxResult.Error("WeixinController Wap Notify Verify WxSign Error " + "]
    ").ToStringSafe());
                    }
                    //验证通信标识
                    string return_code = dict["return_code"];
                    if (!return_code.Equals("SUCCESS", StringComparison.OrdinalIgnoreCase))
                    {
                        string return_msg = dict["return_msg"];
                        logger.Info("WeixinController Wap Notify return_code Error : 请求报文=[" + return_code + " : " + return_msg + "]
    ");
                        return Content(AjaxResult.Error("WeixinController Wap Notify return_code Error " + return_code + "]
    ").ToStringSafe());
                    }
                    //验证交易标识
                    string result_code = dict["result_code"];
                    if (!result_code.Equals("SUCCESS", StringComparison.OrdinalIgnoreCase))
                    {
                        string err_code = dict["err_code"];
                        string err_code_des = dict["err_code_des"];
                        logger.Info("WeixinController Wap Notify return_code Error : 请求报文=[" + err_code + " : " + err_code_des + "]
    ");
                        return Content(AjaxResult.Error("WeixinController Wap Notify return_code Error " + result_code + "]
    ").ToStringSafe());
                    }
                    //公众账号ID
                    string appid = dict["appid"];
                    //商户号
                    string mch_id = dict["mch_id"];
                    //随机字符串
                    string nonce_str = dict["nonce_str"];
                    //签名
                    string sign = dict["sign"];
                    //用户在商户appid 下的唯一标识
                    string openid = dict["openid"];
                    //用户是否关注公众账
                    string is_subscribe = dict["is_subscribe"];
                    //交易类型
                    string trade_type = dict["trade_type"];
                    //付款银行
                    string bank_type = dict["bank_type"];
                    //订单总金额,单位为分
                    string total_fee = dict["total_fee"];
                    //金额分转元
                    string total_fee_rmb = (float.Parse(total_fee) / 100).ToString();
                    //商户订单号
                    string out_trade_no = dict["out_trade_no"];
                    //微信支付订单号
                    string transaction_id = dict["transaction_id"];
                    //商家数据包
                    string attach = dict["attach"];
                    //支付完成时间
                    string time_end = dict["time_end"];
                    //支付方式
                    string paymethod = attach;
                   //微信订单查询【使用签名验证废弃】
                    //var wxorder = await _weixinService.Query(WxPayConfig.KEY, WxPayConfig.APPID, WxPayConfig.MCHID, transaction_id, out_trade_no);
                   //检查订单是否已经支付
                     var order = await _orderService.Query(out_trade_no);
                   if (order.IsNull() || order.Total_fee.IsNotNullOrEmpty()){
                        return Content(AjaxResult.Error("ORDERNO STATUS ERROR !!!").ToStringSafe());
                   }
                    //更新支付金额与订单状态
                     var orderFlag = await _orderService.Update(crsResv);
                    if (orderFlag.IsError)
                    {
                        logger.Fatal("WeixinController Wap Notify  Update Order Error  crsResv : " + crsResv.SerializeJson(System.Text.Encoding.UTF8));
                        return Json(new { IsError = true, Msg = "更新订单数据错误", Data = string.Empty }, JsonRequestBehavior.AllowGet);
                    }
                    //微信支付发货通知
                     var wxNotify = await _weixinService.WeixinNotify(openid, transaction_id, out_trade_no);
                    //记录DB
                    var save = await _weixinService.Save(wxAsync);
                    //构造返回成功XML
                    var wxdict = new SortedDictionary<string, string>();
                    wxdict.Add("return_code", "SUCCESS");
                    wxdict.Add("return_msg", "PAY_SUCCESS");
                    string wxRXml = wxdict.ConvertWxDictToString();
                    logger.Info("WeixinController Wap Notify  Success wxRXml : " + wxRXml + "");
                    return Content(wxRXml);
                }
                catch (Exception ex)
                {
                    logger.Fatal("WeixinController Wap Notify Exception : " + ex.Message + "", ex);
                    return Content(AjaxResult.Error("WeixinController Wap Notify Exception").ToStringSafe());
                }
            }
    
            /// <summary>
            /// 字典转微信返回XML
            /// </summary>
            /// <typeparam name="TKey"></typeparam>
            /// <typeparam name="TValue"></typeparam>
            /// <param name="wxdict"></param>
            /// <returns></returns>
            public static string ConvertWxDictToString<TKey, TValue>(this IDictionary<TKey, TValue> wxdict)
            {
                try
                {
                    string xml = "<xml>";
                    wxdict.ForEach(wd =>
                    {
                        if (wd.Value.GetType() == typeof(int))
                        {
                            xml += "<" + wd.Key + ">" + wd.Value + "</" + wd.Key + ">";
                        }
                        else if (wd.Value.GetType() == typeof(string))
                        {
                            xml += "<" + wd.Key + ">" + "<![CDATA[" + wd.Value + "]]></" + wd.Key + ">";
                        }
                    });
                    xml += "</xml>";
                    return xml;
                }
                catch (Exception)
                {
                    return string.Empty;
                }
            }

    订单查询

            /// <summary>
            /// 微信订单查询
             /// </summary>
            /// <param name="appid">公众账号ID</param>
            /// <param name="mch_id">商户号</param>
            /// <param name="transaction_id">微信的订单号</param>
            /// <param name="out_trade_no">商户订单号</param>
            /// <param name="nonce_str">随机字符串</param>
            /// <param name="sign">签名</param>
            /// <returns></returns>
            public async Task<WebAPIResponse> Query(string appid, string mch_id, string transaction_id, string out_trade_no, string nonce_str, string sign)
            {
                if (appid.IsEmpty() || mch_id.IsEmpty() || transaction_id.IsEmpty())
                {
                    return new WebAPIResponse { IsError = true, Msg = "参数有误!!!" };
                }
                try
                {
                    var dict = new Dictionary<string, string>();
                    dict.Add("appid", appid);
                    dict.Add("mch_id", mch_id);
                    dict.Add("transaction_id", transaction_id);
                    dict.Add("out_trade_no", out_trade_no);
                    dict.Add("nonce_str", nonce_str);
                    dict.Add("sign", sign);
                    string xml = "<xml>";
                    foreach (var pair in dict)
                    {
                        if (pair.Value.GetType() == typeof(int))
                        {
                            xml += "<" + pair.Key + ">" + pair.Value + "</" + pair.Key + ">";
                        }
                        else if (pair.Value.GetType() == typeof(string))
                        {
                            xml += "<" + pair.Key + ">" + "<![CDATA[" + pair.Value + "]]></" + pair.Key + ">";
                        }
                    }
                    xml += "</xml>";
                    logger.Info("WeixinRepository WeixinNotify Req xml: " + xml);
                    var data = await @"https://api.mch.weixin.qq.com/pay/orderquery".PostStringAsync(xml).ReceiveString();
                    logger.Info("WeixinRepository WeixinNotify Resp xml: " + data);
                    if (data.IsNotNullOrEmpty() && data.Contains("SUCCESS", StringComparison.OrdinalIgnoreCase))
                    {
                        return new WebAPIResponse { IsError = false, Data = "查询订单成功!!!" };
                    }
                    return new WebAPIResponse { IsError = true, Msg = "微信订单查询失败!!!" };
                }
                catch (Exception ex)
                {
                    logger.Error("WeixinRepository WeixinNotify ConvertDictionaryToJson Exception :" + ex.Message, ex);
                    return new WebAPIResponse { IsError = true, Msg = ex.Message };
                }
            }

    微信退款

    !!!需注意签名与证书

            /// <summary>
            /// 微信申请退款V3
            /// </summary>
            /// <param name="orderNo">订单号</param>
            /// <returns></returns>
            public bool RefundWxV3(string orderNo)
            {
                try
                {
                    //查询支付信息
                      var orderAsync =await _orderService.Query(orderNo);
                    var dict = new SortedDictionary<string, string>();
                    dict.Add("appid", WxPayConfig.APPID);
                    dict.Add("mch_id", WxPayConfig.MCHID);
                    dict.Add("nonce_str", ConstantKey.NonceStr);
                    dict.Add("transaction_id", orderAsync.transaction_id);
                    dict.Add("out_trade_no", orderAsync.out_trade_no);
                    dict.Add("out_refund_no", WxPayConfig.GenerateOutTradeNo);
                    dict.Add("total_fee", orderAsync.total_fee);
                    dict.Add("refund_fee", orderAsync.total_fee);
                    dict.Add("op_user_id", WxPayConfig.MCHID);
                    string sign = GenerateWxSign(WxPayConfig.KEY, dict);
                    dict.Add("sign", sign);
                    string xml = "<xml>";
                    foreach (var pair in dict)
                    {
                        if (pair.Value.GetType() == typeof(int))
                        {
                            xml += "<" + pair.Key + ">" + pair.Value + "</" + pair.Key + ">";
                        }
                        else if (pair.Value.GetType() == typeof(string))
                        {
                            xml += "<" + pair.Key + ">" + "<![CDATA[" + pair.Value + "]]></" + pair.Key + ">";
                        }
                    }
                    xml += "</xml>";
                    logger.Info("RefundWxV3 Req xml: " + xml);
                    var data = HttpWxCert(@"https://api.mch.weixin.qq.com/secapi/pay/refund", "POST", xml, "text/xml", isUseCert: true);
                    logger.Info("RefundWxV3 Resp xml: " + data);
                    if (data.IsNotEmpty() && data.Contains("SUCCESS", StringComparison.OrdinalIgnoreCase))
                    {
                        return true;
                    }
                    return false;
                }
                catch (Exception ex)
                {
                    logger.Error("CancelResvForWeb RefundWxV3 Exception :" + ex.Message, ex);
                    return false;
                }
            }
            /// <summary>
            /// 创建HTTP请求
             /// </summary>
            /// <param name="url">请求URL</param>
            /// <param name="method">请求方式</param>
            /// <param name="data">请求参数</param>
            /// <param name="contentType">设置HTTP contentType类型</param>
            /// <param name="isUseCert">是否使用证书</param>
            /// <param name="timeout">请求超时时间</param>
            /// <returns></returns>
            public static string HttpWxCert(string url, string method = "GET", string data = null, string contentType = "application/x-www-form-urlencoded", bool isUseCert = false, int timeout = 15000)
            {
                if (url.IsEmpty())
                    throw new ArgumentNullException("url");
                HttpWebRequest req = null;
                HttpWebResponse resp = null;
                StreamReader sr = null;
                Stream stream = null;
                try
                {
                    if (url.StartsWith("https", StringComparison.OrdinalIgnoreCase))
                    {
                        System.Net.ServicePointManager.ServerCertificateValidationCallback += (se, cert, chain, sslError) =>
                        {
                            return true;
                        };
                    }
                    //设置最大连接数
                    System.Net.ServicePointManager.DefaultConnectionLimit = 200;
                    req = (HttpWebRequest)WebRequest.Create(url);
                    req.Method = method.ToUpper();
                    req.Timeout = timeout;
                    req.ContentType = contentType;
                    //是否使用证书
                    if (isUseCert)
                    {
                        string pfxPath = WxPayConfig.SSLCERT_PATH;
                        logger.Info("wxV3.signCert.path pfxPath : " + pfxPath + " File Exists: " + File.Exists(pfxPath));
                        //string path = HttpContext.Current.Request.PhysicalApplicationPath;
                        //调用证书
                        var cert = new X509Certificate2(pfxPath, WxPayConfig.SSLCERT_PASSWORD, X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.MachineKeySet);
                        req.ClientCertificates.Add(cert);
                    }
    
                    if (method.ToUpper() == "POST" && !data.IsEmpty())
                    {
                        byte[] postBytes = UTF8Encoding.UTF8.GetBytes(data);
                        req.ContentLength = postBytes.Length;
                        using (stream = req.GetRequestStream())
                        {
                            stream.Write(postBytes, 0, postBytes.Length);
                        }
                    }
                    resp = (HttpWebResponse)req.GetResponse();
                    using (sr = new StreamReader(resp.GetResponseStream(), Encoding.UTF8))
                    {
                        return sr.ReadToEnd().Trim();
                    }
                }
                catch (Exception ex)
                {
                    logger.Error(string.Format("HttpWxCert Exception url: {0} 
     data: {1}", url, data), ex);
                    return string.Empty;
                }
                finally
                {
                    if (sr != null)
                    {
                        sr.Close();
                        sr = null;
                    }
                    if (stream != null)
                    {
                        stream.Close();
                        stream = null;
                    }
                    if (resp != null)
                    {
                        resp.Close();
                        resp = null;
                    }
                    if (req != null)
                    {
                        req.Abort();
                        req = null;
                    }
                }
            }

    Refer:
    微信支付接口退款时:System.Security.Cryptography.CryptographicException
    http://www.cnblogs.com/jys509/p/4499978.html
    http://www.cnblogs.com/happyframework/p/3627486.html

    https://pay.weixin.qq.com/wiki/doc/api/index.html

  • 相关阅读:
    leetcode 268. Missing Number
    DBSCAN
    python二维数组初始化
    leetcode 661. Image Smoother
    leetcode 599. Minimum Index Sum of Two Lists
    Python中的sort() key含义
    leetcode 447. Number of Boomerangs
    leetcode 697. Degree of an Array
    滴滴快车奖励政策,高峰奖励,翻倍奖励,按成交率,指派单数分级(1月3日)
    北京Uber优步司机奖励政策(1月2日)
  • 原文地址:https://www.cnblogs.com/Irving/p/4563883.html
Copyright © 2011-2022 走看看