微信支付接入的總結 —— NATIVE & MWEB & JSAPI

        這段時間工做中須要對接微信支付,並且要多個端同時進行接入,web端,手機瀏覽器,微信瀏覽器,因此研究了下。不一樣場景選擇合適的接入方式是必須的。https://pay.weixin.qq.com/wiki/doc/api/index.html 官網上對這些都有說明。基礎配置的流程就不寫了。下面直接進入代碼環節。php

     /// <summary>
        /// 微信支付
        /// </summary>
        [HttpPost]
        [Route("api/shopping/WeChatPay")]
        public ResponseMessage WeChatPay(ReqWeChatPay req)
        {
            var response = new ResponseMessage { Msg = false, Message = CommonMessage.GetFailure, Info = 0 };
            if (req == null) return response;
            //將訂單號給商品描述,用於頁面展現
            req.Subjects = req.OutTradeNo;
            //獲取到訂單的相關信息
            var orderInfo = CourseService.GetCourseByOrderNo(req.OutTradeNo);
            var item = orderInfo.FirstOrDefault() ?? new CourseInfo { TrainingInstitutionId = 0, BuyPrice = Convert.ToDecimal(0.01) };
            //獲取機構下的微信支付信息
            var weChatModel = MyShoppingCartService.GetMSiteWeChatPayAccount(item.TrainingInstitutionId);
            //修改微信配置信息
            SetWxPayConfig(weChatModel);
            var weChatPay = new NativePay();
            //構造返回實體
            var respModel = new RespWeChatPay
            {
                ClassName = req.OutTradeNo,
                OutTradeNo = req.OutTradeNo
            };
            //本訂單下的支付流水有效,直接使用,沒有生成新的支付流水,從新拉起支付
            var serialNo = "N_" + DateTime.Now.ToString(CommonMessage.DateFormatYYYYMMDDHHMMSSFFFF).Substring(3);
            var paySerial = MyShoppingCartService.GetPaySerial(item.OrderId, item.BuyPrice, "NATIVE", 120, serialNo);
            req.OutTradeNo = paySerial;
            //獲取參數實體
            var model = CreateGetWxPayDataModel(req, item.BuyPrice);
            model.NotifyUrl = weChatModel.NotifyUrl;
            AddOrderLog("NATIVE支付----參數實體)", string.Format("{0}", JsonHelper.toJson(model).Content.ReadAsStringAsync().Result));

            //調用統一下單產生有效期爲10分鐘的二維碼
            var result = CreateRespModel(req, model, respModel, weChatPay);
            //在return_code 和result_code都爲SUCCESS的時候有返回,才能成功
            if (result.GetValue("return_code").ToString() == CommonMessage.WeChatSuccess &&
                result.GetValue("result_code").ToString() == CommonMessage.WeChatSuccess)
            {
                response.Msg = true;
                response.Message = CommonMessage.OrderStatusSuccess;
                response.Info = respModel;
                //更新訂單支付的類型
                MyShoppingCartService.UpdateOrderPayType(req.Subjects, (int)CommonEnum.PayType.WechatPay, (int)CommonEnum.PaySourceType.WebOrApp);
            }

            respModel.JsApiParameters = StringHelper.NullOrEmpty(respModel.JsApiParameters);
            respModel.PrepayId = StringHelper.NullOrEmpty(respModel.PrepayId);
            respModel.QrCodePath = PicHelper.ConcatPicUrl(respModel.QrCodePath);
            AddOrderLog("NATIVE支付----二維碼地址", $"{respModel.QrCodePath}");
            return response;
        }

        /// <summary>
        /// 微信支付 --H5
        /// </summary>
        [HttpPost]
        [Route("api/shopping/WeChatPayForH5")]
        public ResponseMessage WeChatPayForH5(ReqWeChatPay req)
        {
            var response = new ResponseMessage { Msg = false, Message = CommonMessage.GetFailure, Info = 0 };
            if (req == null) return response;
            //將訂單號給商品描述,用於頁面展現
            req.Subjects = req.OutTradeNo;
            //獲取到訂單的相關信息
            var orderInfo = CourseService.GetCourseByOrderNo(req.OutTradeNo);
            var item = orderInfo.FirstOrDefault() ?? new CourseInfo { TrainingInstitutionId = 0, BuyPrice = Convert.ToDecimal(0.01) };
            //獲取機構下的微信支付信息
            var weChatModel = MyShoppingCartService.GetMSiteWeChatPayAccount(item.TrainingInstitutionId);
            //修改微信配置信息
            SetWxPayConfig(weChatModel);
            //構造返回實體
            var respModel = new RespWeChatPay
            {
                ClassName = req.OutTradeNo,
                OutTradeNo = req.OutTradeNo
            };
            var weChatPay = new NativePay();
            //本訂單下的支付流水有效,直接使用,沒有生成新的支付流水,從新拉起支付
            var serialNo = "M_" + DateTime.Now.ToString(CommonMessage.DateFormatYYYYMMDDHHMMSSFFFF).Substring(3);
            //實際應支付金額
            var buyPrice = item.BuyPrice - item.UsedScholarship;
            var paySerial = MyShoppingCartService.GetPaySerial(item.OrderId, buyPrice, "MWEB", 5, serialNo);
            req.OutTradeNo = paySerial;
            //獲取參數實體
            var model = CreateGetWxPayDataModel(req, buyPrice);
            model.NotifyUrl = weChatModel.NotifyUrl;
            AddOrderLog("H5支付----參數實體", $"{JsonHelper.toJson(model).Content.ReadAsStringAsync().Result}");

            //調用統一下單產生支付跳轉url(有效期5分鐘)
            var result = CreateRespModel(req, model, respModel, weChatPay);
            //在return_code 和result_code都爲SUCCESS的時候有返回,才能成功
            if (result.GetValue("return_code").ToString() == CommonMessage.WeChatSuccess &&
                result.GetValue("result_code").ToString() == CommonMessage.WeChatSuccess)
            {
                response.Msg = true;
                response.Message = CommonMessage.OrderStatusSuccess;
                respModel.MWebUrl = StringHelper.NullOrEmpty(respModel.MWebUrl);
                response.Info = respModel;
                AddOrderLog("H5支付----吊起微信的Url", $"{respModel.MWebUrl}");
                MyShoppingCartService.UpdateOrderPayType(req.Subjects, (int)CommonEnum.PayType.WechatPay, (int)CommonEnum.PaySourceType.Mstation); //更新訂單支付的類型
            }
            else
            {
                response.Info = result.IsSet("err_code_des") ? result.GetValue("err_code_des").ToString() : result.GetValue("return_msg").ToString();
                AddOrderLog("H5支付---獲取吊起微信Url失敗", $"錯誤信息:{result.ToJson()}");
            }
            return response;
        }

        /// <summary>
        /// 微信支付 --H5 (微信端) JSAPI
        /// </summary>
        [HttpPost]
        [Route("api/shopping/WeChatPayForWeChatH5")]
        public ResponseMessage WeChatPayForWeChatH5(ReqWeChatPay req)
        {
            var response = new ResponseMessage { Msg = false, Message = CommonMessage.GetFailure, Info = 0 };
            if (req == null) return response;
            //將訂單號給商品描述,用於頁面展現
            req.Subjects = req.OutTradeNo;
            var orderInfo = CourseService.GetCourseByOrderNo(req.OutTradeNo);//獲取到訂單的相關信息
            var item = orderInfo.FirstOrDefault() ?? new CourseInfo { TrainingInstitutionId = 0, BuyPrice = Convert.ToDecimal(0.01) };
            //獲取機構下的微信支付信息
            var weChatModel = MyShoppingCartService.GetMSiteWeChatPayAccount(item.TrainingInstitutionId);
            //修改微信配置信息
            SetWxPayConfig(weChatModel);
            var weChatPay = new NativePay();
            var wxJsSdkApi = new WxJsSdkApi();
            //構造返回實體
            var respModel = new RespWeChatPay
            {
                ClassName = req.OutTradeNo,
                OutTradeNo = req.OutTradeNo
            };
            //本訂單下的支付流水有效,直接使用,沒有生成新的支付流水,從新拉起支付
            var serialNo = "J_" + DateTime.Now.ToString(CommonMessage.DateFormatYYYYMMDDHHMMSSFFFF).Substring(3);
            //實際應支付金額
            var buyPrice = item.BuyPrice - item.UsedScholarship;
            var paySerial = MyShoppingCartService.GetPaySerial(item.OrderId, buyPrice, "JSAPI", 120, serialNo);
            req.OutTradeNo = paySerial;
            //獲取參數實體
            var model = CreateGetWxPayDataModel(req, buyPrice);
            model.NotifyUrl = weChatModel.NotifyUrl;
            model.OpenId = wxJsSdkApi.GetAccessTokenByCode(req.Code).OpenId;
            AddOrderLog("JSAPI支付----參數實體", $"{JsonHelper.toJson(model).Content.ReadAsStringAsync().Result}");

            //調用統一下單產生有效期爲2小時的 JsApiParameters
            var result = CreateRespModel(req, model, respModel, weChatPay);
            //在return_code 和result_code都爲SUCCESS的時候有返回,才能成功
            if (result.GetValue("return_code").ToString() == CommonMessage.WeChatSuccess &&
                result.GetValue("result_code").ToString() == CommonMessage.WeChatSuccess)
            {
                response.Msg = true;
                response.Message = CommonMessage.OrderStatusSuccess;
                response.Info = respModel;
                //更新訂單支付的類型
                MyShoppingCartService.UpdateOrderPayType(req.Subjects, (int)CommonEnum.PayType.WechatPay, (int)CommonEnum.PaySourceType.Mstation);
            }

            respModel.JsApiParameters = StringHelper.NullOrEmpty(respModel.JsApiParameters);
            AddOrderLog("JSAPI支付----JsApiParameters", $"{respModel.JsApiParameters}");
            return response;
        }

  微信配置靜態類字段的賦值html

        /// <summary>
        /// 修改微信配置信息
        /// </summary>
        private void SetWxPayConfig(T_MSiteWeChatPayAccount weChatModel)
        {
            WxPayConfig.APPID = weChatModel.AppId;
            WxPayConfig.MCHID = weChatModel.MchId;
            WxPayConfig.KEY = weChatModel.Key;
            WxPayConfig.APPSECRET = weChatModel.AppSecret;
            WxPayConfig.NOTIFY_URL = weChatModel.NotifyUrl;
        }

  建立統一下單所需實體前端

       /// <summary>
        /// 構造參數實體
        /// </summary>
        /// <param name="req">接口參數</param>
        /// <param name="price">價格,單位:分</param>
        private GetWxPayDataModel CreateGetWxPayDataModel(ReqWeChatPay req, decimal price)
        {
            var ip = GetRealIp();
            var model = new GetWxPayDataModel
            {
                Attach = req.Subjects,
                Body = req.Subjects,
                GoodsTag = req.Subjects,
                OutTradeNo = req.OutTradeNo,
                ProductId = req.OutTradeNo,
                TimeStart = DateTime.Now.ToString(CommonMessage.DateFormatYYYYMMDDHHMMSS),
                TimeExpire = req.TradeType == "NATIVE"
                    ? DateTime.Now.AddMinutes(10).ToString(CommonMessage.DateFormatYYYYMMDDHHMMSS)
                    : req.TradeType == "MWEB"
                        ? DateTime.Now.AddMinutes(5).ToString(CommonMessage.DateFormatYYYYMMDDHHMMSS)
                        : DateTime.Now.AddMinutes(120).ToString(CommonMessage.DateFormatYYYYMMDDHHMMSS),
                TotalFee = (int)(price * 100),
                TradeType = req.TradeType,
                Ip = ip
            };
            return model;
        }

        /// <summary>
        /// 微信支付統一下單後的結果處理
        /// </summary>
        private WxPayData CreateRespModel(ReqWeChatPay req, GetWxPayDataModel model, RespWeChatPay respModel, NativePay weChatPay, bool saveQrCode = true)
        {
            var result = new WxPayData();
            //二維碼掃碼支付
            if (req.TradeType == ReqWeChatPay.NATIVE)
            {
                //統一下單
                result = weChatPay.GetWxPayData(model);
                Log4NetHelp.Info($"下單結果--{result.ToJson()}");
                if (result.GetValue("return_code").ToString() == CommonMessage.WeChatSuccess)
                {
                    var codeUrl = result.GetValue("code_url").ToString();
                    if (!saveQrCode) return result;
                    var uppath = CommonMessage.TPImageUpPath; //獲取圖片上傳路徑
                    var savepath = CommonMessage.TPImageSavePath; //獲取圖片保存數據庫中的路徑
                    var fileName = DateTime.Now.ToString(CommonMessage.DateFormatYYYYMMDDHHMMSSFFFF) + ".png";
                    var newFilePath = string.Format(savepath, "QrCode");
                    newFilePath = $"{newFilePath}{fileName}";
                    var filepath = string.Format(uppath, "QrCode");
                    weChatPay.MakeQrCode(codeUrl, filepath, fileName);
                    respModel.QrCodePath = newFilePath;
                }
            }
            //H5支付
            if (req.TradeType == ReqWeChatPay.MWEB)
            {
                //統一下單
                result = weChatPay.GetWxPayData(model);
                Log4NetHelp.Info($"下單結果--{result.ToJson()}");
                if (result.GetValue("return_code").ToString() == CommonMessage.WeChatSuccess)
                    respModel.MWebUrl = result.GetValue("mweb_url").ToString();
            }
            //JSAPI支付
            if (req.TradeType == ReqWeChatPay.JSAPI)
            {
                //統一下單
                result = weChatPay.GetWxPayData(model);
                Log4NetHelp.Info($"下單結果--{result.ToJson()}");
                if (result.GetValue("return_code").ToString() == CommonMessage.WeChatSuccess)
                    respModel.JsApiParameters = weChatPay.GetJsApiParameters(result);
            }
            return result;
        }

  統一下單以前的數據組合,這裏我是把獲取返回來的數據都存在了redis中了。未過時以前直接獲取就能夠用。或者在實際應用中能夠存在數據庫中或者緩存中。node

       /// <summary>
        /// 生成直接支付url,有效期爲2小時 ----NATIVE
        /// 生成支付跳轉url,有效期爲5分鐘 ----MWEB
        /// 生成prepay_id,有效期2小時  ----JSAPI
        /// 微信支付統一下單後的返回數據
        /// 模式二
        /// </summary>
        /// <returns>數據裏面有prepay_id和二維碼連接code_url</returns>
        public WxPayData GetWxPayData(GetWxPayDataModel model)
        {
            WxPayData data = new WxPayData();
            var validTime = CommonConst.ConstIntNumberZero;
            if (model.TradeType == "NATIVE") validTime = 7200;
            if (model.TradeType == "MWEB") validTime = 300;
            if (model.TradeType == "JSAPI") validTime = 7200;
            //redis中獲取以前存的微信返回數據
            var redisValue = redis.GetRedisValue(model.OutTradeNo);
            Log.Info("get wechat unifiedOrder redisValue:", redisValue.ToJson());
            if (!string.IsNullOrEmpty(redisValue))
            {
                SetWxPayResultData(redisValue, data);
                return data;
            }
            data.SetValue("body", model.Body);//商品描述
            data.SetValue("attach", model.Attach);//附加數據
            data.SetValue("out_trade_no", model.OutTradeNo);//訂單號
            data.SetValue("total_fee", model.TotalFee);//總金額
            data.SetValue("time_start", model.TimeStart);//交易起始時間
            data.SetValue("time_expire", model.TimeExpire);//交易結束時間
            data.SetValue("goods_tag", model.GoodsTag);//商品標記
            data.SetValue("trade_type", model.TradeType);//交易類型
            data.SetValue("product_id", model.ProductId);//商品ID
            data.SetValue("notify_url", model.NotifyUrl);//微信異步回調地址
            data.SetValue("openid", model.OpenId ?? "");//openId
            data.SetValue("spbill_create_ip", model.Ip ?? "");//終端ip
            //調用統一下單接口
            data = WxPayApi.UnifiedOrder(data);
            Log.Info("get wechat unifiedOrder model:", data.ToJson());
            //將微信返回的數據存到redis中
            if (data.GetValue("return_code").ToString() == "SUCCESS" && data.GetValue("result_code").ToString() == "SUCCESS")
                redis.SetRedis(model.OutTradeNo, data.GetValues(), DateTime.Now.AddSeconds(validTime));
            return data;
        }

  下面就到了微信demo中的統一下單接口的調用了web

     /**
        * 
        * 統一下單
        * @param WxPaydata inputObj 提交給統一下單API的參數
        * @param int timeOut 超時時間
        * @throws WxPayException
        * @return 成功時返回,其餘拋異常
        */
        public static WxPayData UnifiedOrder(WxPayData inputObj, int timeOut = 60)
        {
            string url = "https://api.mch.weixin.qq.com/pay/unifiedorder";
            //檢測必填參數
            if (!inputObj.IsSet("out_trade_no"))
            {
                throw new WxPayException("缺乏統一支付接口必填參數out_trade_no!");
            }
            else if (!inputObj.IsSet("body"))
            {
                throw new WxPayException("缺乏統一支付接口必填參數body!");
            }
            else if (!inputObj.IsSet("total_fee"))
            {
                throw new WxPayException("缺乏統一支付接口必填參數total_fee!");
            }
            else if (!inputObj.IsSet("trade_type"))
            {
                throw new WxPayException("缺乏統一支付接口必填參數trade_type!");
            }

            //關聯參數
            if (inputObj.GetValue("trade_type").ToString() == "JSAPI" && !inputObj.IsSet("openid"))
            {
                throw new WxPayException("統一支付接口中,缺乏必填參數openid!trade_type爲JSAPI時,openid爲必填參數!");
            }
            if (inputObj.GetValue("trade_type").ToString() == "NATIVE" && !inputObj.IsSet("product_id"))
            {
                throw new WxPayException("統一支付接口中,缺乏必填參數product_id!trade_type爲NATIVE時,product_id爲必填參數!");
            }

            if (inputObj.GetValue("trade_type").ToString() == "MWEB" && !inputObj.IsSet("spbill_create_ip"))
            {
                throw new WxPayException("統一支付接口中,缺乏必填參數spbill_create_ip!trade_type爲MWEB時,spbill_create_ip爲必填參數!");
            }

            //異步通知url未設置,則使用配置文件中的url
            if (!inputObj.IsSet("notify_url"))
            {
                inputObj.SetValue("notify_url", WxPayConfig.NOTIFY_URL);//異步通知url
            }

            inputObj.SetValue("appid", WxPayConfig.APPID);//公衆帳號ID
            inputObj.SetValue("mch_id", WxPayConfig.MCHID);//商戶號
            inputObj.SetValue("spbill_create_ip", inputObj.IsSet("spbill_create_ip") ? inputObj.GetValue("spbill_create_ip").ToString() : "");//終端ip	  	    
            inputObj.SetValue("nonce_str", GenerateNonceStr());//隨機字符串

            //簽名
            inputObj.SetValue("sign", inputObj.MakeSign());
            string xml = inputObj.ToXml();

            var start = DateTime.Now;

            //Log.Debug("WxPayApi", "UnfiedOrder request : " + xml);
            string response = HttpService.Post(xml, url, false, timeOut); //準備了這麼多就爲了這一句
            //Log.Debug("WxPayApi", "UnfiedOrder response : " + response);

            var end = DateTime.Now;
            int timeCost = (int)((end - start).TotalMilliseconds);

            WxPayData result = new WxPayData();
            result.FromXml(response);

            ReportCostTime(url, timeCost, result);//測速上報

            return result;
        }

  FromXml() 會對簽名進行驗證。回調的時候也能夠直接調用。redis

    /**
        * @將xml轉爲WxPayData對象並返回對象內部的數據
        * @param string 待轉換的xml串
        * @return 經轉換獲得的Dictionary
        * @throws WxPayException
        */
        public SortedDictionary<string, object> FromXml(string xml)
        {
            if (string.IsNullOrEmpty(xml))
            {
                Log.Error(this.GetType().ToString(), "將空的xml串轉換爲WxPayData不合法!");
                throw new WxPayException("將空的xml串轉換爲WxPayData不合法!");
            }

            XmlDocument xmlDoc = new XmlDocument();
            xmlDoc.LoadXml(xml);
            XmlNode xmlNode = xmlDoc.FirstChild;//獲取到根節點<xml>
            XmlNodeList nodes = xmlNode.ChildNodes;
            foreach (XmlNode xn in nodes)
            {
                XmlElement xe = (XmlElement)xn;
                m_values[xe.Name] = xe.InnerText;//獲取xml的鍵值對到WxPayData內部的數據中
            }
			
            try
            {
				// 錯誤是沒有簽名
				if(m_values["return_code"] != "SUCCESS")
				{
					return m_values;
				}
                CheckSign();//驗證簽名,不經過會拋異常
            }
            catch(WxPayException ex)
            {
                throw new WxPayException(ex.Message);
            }

            return m_values;
        }

統一下單以後要對數據進行分類處理。 NATIVE--生成二維碼 用戶掃碼支付, MWEB -- 返回mweb_url 用來吊起微信應用,JSAPI -- 返回 jsApiParam 用來吊起微信支付頁面數據庫

      /// <summary>
        /// 生成二維碼
        /// </summary>
        /// <param name="codeUrl">微信返回的二維碼圖片地址</param>
        /// <param name="filePath">二維碼圖片存放的地址</param>
        public void MakeQrCode(string codeUrl, string filePath, string fileName)
        {
            //初始化二維碼生成工具
            QRCodeEncoder qrCodeEncoder = new QRCodeEncoder
            {
                QRCodeEncodeMode = QRCodeEncoder.ENCODE_MODE.BYTE,
                QRCodeErrorCorrect = QRCodeEncoder.ERROR_CORRECTION.M,
                QRCodeVersion = 0,
                QRCodeScale = 4
            };
            //將字符串生成二維碼圖片
            Bitmap image = qrCodeEncoder.Encode(codeUrl, Encoding.Default);
            if (!Directory.Exists(filePath))
            {
                Directory.CreateDirectory(filePath);
            }
            filePath += fileName;
            image.Save(filePath, ImageFormat.Jpeg);
        }

吊起微信所須要的參數 json

        /// <summary>
        /// 獲取json參數返回給前端使用
        /// </summary>
        /// <param name="data">統一下單以後的返回數據</param>
        /// <returns>json參數串,返回給前端使用</returns>
        public string GetJsApiParameters(WxPayData data)
        {
            Log.Debug(this.GetType().ToString(), "JsApiPay::GetJsApiParam is processing...");

            WxPayData jsApiParam = new WxPayData();
            jsApiParam.SetValue("appId", data.GetValue("appid"));
            jsApiParam.SetValue("timeStamp", WxPayApi.GenerateTimeStamp());
            jsApiParam.SetValue("nonceStr", WxPayApi.GenerateNonceStr());
            jsApiParam.SetValue("package", "prepay_id=" + data.GetValue("prepay_id"));
            jsApiParam.SetValue("signType", "MD5");
            jsApiParam.SetValue("paySign", jsApiParam.MakeSign());

            string parameters = jsApiParam.ToJson();

            Log.Debug(this.GetType().ToString(), "Get jsApiParam : " + parameters);
            return parameters;
        }

JSAPI 須要獲取openId  下面是openId的獲取方法,其中code須要從前端獲取。能夠參考下官網第一步說明: https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140842api

  由於access token的獲取天天有次數限制,這裏須要進行保存下。瀏覽器

  public class WxJsSdkApi
    {
        /// <summary>
        /// 緩存
        /// </summary>
        private RedisInfoHelper redis => new RedisInfoHelper();

        /// <summary>
        /// 微信appid
        /// </summary>
        private string appId = WxPayConfig.APPID;

        /// <summary>
        /// 微信appsecret
        /// </summary>
        private string secret = WxPayConfig.APPSECRET;
        /// <summary>
        /// 獲取基礎支持access_token url  每日調用上限2000次
        /// 詳情參考:https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140183
        /// </summary>
        private string WxGetTokenUrl = "https://api.weixin.qq.com/cgi-bin/token";
        private string WxGetTokenParam = "grant_type=client_credential&appid={0}&secret={1}";

        /// <summary>
        /// 獲取jsapi_ticket url
        /// 詳情參考:https://qydev.weixin.qq.com/wiki/index.php?title=%E5%BE%AE%E4%BF%A1JS-SDK%E6%8E%A5%E5%8F%A3
        /// </summary>
        private string WxGetTicketUrl = "https://api.weixin.qq.com/cgi-bin/ticket/getticket";
        private string WxGetTicketParam = "access_token={0}&type=jsapi";

        /// <summary>
        /// 加密字符串的拼接
        /// </summary>
        private string WxSha1SignStr = "jsapi_ticket={0}&noncestr={1}&timestamp={2}&url={3}";

        /// <summary>
        /// 經過用戶code獲取網頁受權access_token url
        /// 詳情參考:https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140842
        /// </summary>
        private string WxGetAccessTokenByCodeUrl = "https://api.weixin.qq.com/sns/oauth2/access_token";
        private string WxGetAccessTokenByCodeParam = "appid={0}&secret={1}&code={2}&grant_type=authorization_code";

        /// <summary>
        /// 基礎支持Access_Token 有效期7200秒
        /// </summary>
        public string BaseAccessToken => GetBaseAccessToken();

        /// <summary>
        /// Ticket 公衆號用於調用微信JS接口的臨時票據 (有效期7200秒)
        /// </summary>
        public string Ticket
        {
            get { return GetTicket(); }
        }

        /// <summary>
        /// 生成簽名的隨機串
        /// </summary>
        public string NonceStr { get; set; } = Guid.NewGuid().ToString().Replace("-", "");

        /// <summary>
        /// 生成簽名的時間戳
        /// </summary>
        public string TimeStamp
        {
            get
            {
                TimeSpan ts = DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, 0);
                return Convert.ToInt64(ts.TotalSeconds).ToString();
            }
        }

        /// <summary>
        /// 獲取基礎支持access_token
        /// </summary>
        private string GetBaseAccessToken()
        {
            var accesstoken = redis.GetAccessTokenByAppId("wx_" + appId);
            if ((string.Empty).Equals(accesstoken))
            {
                return GetBaseToken();
            }
            return accesstoken != null ? accesstoken.ToString() : GetBaseToken();
        }

        /// <summary>
        /// 獲取基礎支持access_token
        /// </summary>
        private string GetBaseToken()
        {
            try
            {
                string token = string.Empty;

                //向微信發送請求獲取 基礎支持accessToken
                string content = HttpClientHelper.HttpGet(WxGetTokenUrl,
                    string.Format(WxGetTokenParam, appId, secret));
                Log4NetHelp.Debug($"微信JSSDK獲取基礎accessToken參數--appid:{content},secret:{secret}");
                Log4NetHelp.Debug($"微信JSSDK獲取基礎accessToken返回結果--{content}");
                if (!string.IsNullOrEmpty(content))
                {
                    var obj = JsonConvert.DeserializeObject<BaseTokenResult>(content);
                    if (!obj.errcode.HasValue)
                    {
                        token = obj.access_token;
                        //緩存 access_token
                        redis.SetRedis("wx_" + appId, token, DateTime.Now.AddSeconds(7200));
                        //獲取 ticket
                        GetTicket(token);
                    }
                }
                return token;
            }
            catch (Exception ex)
            {
                return ex.Message;
            }
        }

        /// <summary>
        /// 獲取ticket
        /// </summary>
        private string GetTicket(string accessToken = "")
        {
            var ticket = string.Empty;
            accessToken = BaseAccessToken;
            if (string.Empty.Equals(accessToken)) return ticket;
            ticket = redis.GetTicketByAccessToken(accessToken);
            if ((string.Empty).Equals(ticket))
            {
                string content = HttpClientHelper.HttpGet(WxGetTicketUrl, string.Format(WxGetTicketParam, accessToken));
                Log4NetHelp.Debug($"微信JSSDK獲取ticket參數--accessToken:{accessToken}");
                Log4NetHelp.Debug($"微信JSSDK獲取ticket返回結果--{content}");
                JsApiTicket obj = JsonConvert.DeserializeObject<JsApiTicket>(content);
                ticket = obj.ticket;
                redis.SetRedis(accessToken, ticket, DateTime.Now.AddSeconds(7200));
            }
            return ticket;
        }

        /// <summary>
        /// SDK生成簽名 (SHA1加密) 
        /// 注意:須要引用System.Security.dll
        /// </summary>
        /// <param name="url">當前頁面連接</param>
        public string MakeSha1Sign(string nonceStr, string timeStamp, string url)
        {
            string str = string.Format(WxSha1SignStr, Ticket, nonceStr, timeStamp, url);
            Log4NetHelp.Info($"SDK生成簽名加密字符串--{str}");
            byte[] StrRes = Encoding.Default.GetBytes(str);
            HashAlgorithm iSHA = new SHA1CryptoServiceProvider();
            StrRes = iSHA.ComputeHash(StrRes);
            StringBuilder EnText = new StringBuilder();
            foreach (byte iByte in StrRes)
            {
                EnText.AppendFormat("{0:x2}", iByte);
            }
            return EnText.ToString();
        }

        /// <summary>
        /// 經過code換取網頁受權access_token
        /// </summary>
        /// <param name="code"></param>
        /// <returns></returns>
        public TokenResultInfo GetAccessTokenByCode(string code)
        {
            var result = new TokenResultInfo();
            //向微信發送請求獲取 網頁受權accessToken
            string content = HttpClientHelper.HttpGet(WxGetAccessTokenByCodeUrl,
                string.Format(WxGetAccessTokenByCodeParam, appId, secret, code));
            Log4NetHelp.Info($"經過code換取網頁受權access_token參數--appid:{appId}--secret:{secret}--code:{code}");
            Log4NetHelp.Info($"經過code換取網頁受權access_token返回結果--{content}--appid:{appId}--secret:{secret}--code:{code}");
            if (!string.IsNullOrEmpty(content))
            {
                var obj = JsonConvert.DeserializeObject<TokenResult>(content);
                if (!obj.errcode.HasValue)
                {
                    result.AccessToken = obj.access_token;
                    result.OpenId = obj.openid;
                }
            }

            return result;
        }
    }

回調時統一處理

       /// <summary>
        /// 微信異步回調處理
        /// </summary>
        [HttpPost]
        [Route("api/shopping/WeChatNotifyUrl")]
        public HttpResponseMessage WeChatNotifyUrl()
        {
            var response = new HttpResponseMessage();
            var notify = new ResultNotify();
            //簽名驗證 && 數據有效性驗證
            var data = notify.ProcessNotify(HttpContext.Current);
            AddOrderLog("微信回調日誌", $"微信回調 {data.ToJson()}");
            var res = new WxPayData();
            res.SetValue("return_code", CommonMessage.WeChatFail);
            res.SetValue("return_msg", CommonMessage.WeChatFailMsg);

            if (data.GetValue("return_code").ToString() == "SUCCESS" &&
                data.GetValue("result_code").ToString() == "SUCCESS")
            {
                //2. 校驗返回的訂單金額是否與商戶側的訂單金額一致  不一致,返回錯誤信息
                var isSameAccount = MyShoppingCartService.GetMyOrderTotalFeeByTradeNo(data.GetValue("out_trade_no").ToString(), Convert.ToInt32(data.GetValue("total_fee").ToString()));
                if (!isSameAccount)
                {
                    res.SetValue("return_msg", CommonMessage.InconformityWithAmount);
                }
                else
                {
                    //更新訂單以及相關業務處理
                    res.SetValue("return_code", CommonMessage.WeChatSuccess);
                    res.SetValue("return_msg", CommonMessage.WeChatSuccessMsg);

                }
            }
            response.Content = new StringContent(res.ToXml());
            AddOrderLog("微信回調日誌", $"微信回調返回數據 {res.ToXml()}");
            return response;
        }

  回調處理基類:

    /// <summary>
    /// 回調處理基類
    /// 主要負責接收微信支付後臺發送過來的數據,對數據進行簽名驗證
    /// 子類在此類基礎上進行派生並重寫本身的回調處理過程
    /// </summary>
    public class Notify
    {
        /// <summary>
        /// 接收從微信支付後臺發送過來的數據並驗證簽名
        /// </summary>
        /// <returns>微信支付後臺返回的數據</returns>
        public WxPayData GetNotifyData(HttpContext context)
        {
            //接收從微信後臺POST過來的數據
            System.IO.Stream s = context.Request.InputStream;
            int count = 0;
            byte[] buffer = new byte[1024];
            StringBuilder builder = new StringBuilder();
            while ((count = s.Read(buffer, 0, 1024)) > 0)
            {
                builder.Append(Encoding.UTF8.GetString(buffer, 0, count));
            }
            s.Flush();
            s.Close();
            s.Dispose();

            Log.Info(this.GetType().ToString(), "Receive data from WeChat : " + builder.ToString());

            //轉換數據格式並驗證簽名
            WxPayData data = new WxPayData();
            try
            {
                //FormXML會校驗簽名
                data.FromXml(builder.ToString());
            }
            catch (WxPayException ex)
            {
                //若簽名錯誤,則當即返回結果給微信支付後臺
                WxPayData res = new WxPayData();
                res.SetValue("return_code", "FAIL");
                res.SetValue("return_msg", ex.Message);
                Log.Error(this.GetType().ToString(), "Sign check error : " + res.ToXml());
                context.Response.Write(res.ToXml());
                context.Response.End();
            }

            Log.Info(this.GetType().ToString(), "Check sign success");
            return data;
        }

        //派生類須要重寫這個方法,進行不一樣的回調處理
        public virtual WxPayData ProcessNotify(HttpContext context)
        {
            return null;
        }
    }

    /// <summary>
    /// 支付結果通知回調處理類
    /// 負責接收微信支付後臺發送的支付結果並對訂單有效性進行驗證,將驗證結果反饋給微信支付後臺
    /// </summary>
    public class ResultNotify : Notify
    {
        public override WxPayData ProcessNotify(HttpContext context)
        {
            WxPayData notifyData = GetNotifyData(context);
       //這裏能夠進行其餘的驗證
       。。。。。。。 return notifyData; } }

  獲取真實IP的方法:

        #region 獲取web端真實IP地址

        /// <summary>
        /// 獲取web端真實IP地址
        /// </summary>
        /// <returns></returns>
        public string GetRealIp()
        {
            var ip = string.Empty;
            if (HttpContext.Current != null)
            {
                ip = GetWebClientIp();
            }
            if (string.IsNullOrWhiteSpace(ip))
            {
                ip = GetLanIp();
            }

            return ip;
        }

        /// <summary>
        /// 獲取Web客戶端的IP
        /// </summary>
        /// <returns></returns>
        private string GetWebClientIp()
        {
            var ip = GetWebProxyRealIp() ?? GetWebRemoteIp();
            foreach (var hostAddress in Dns.GetHostAddresses(ip))
            {
                if (hostAddress.AddressFamily == AddressFamily.InterNetwork)
                {
                    return hostAddress.ToString();
                }
            }
            return string.Empty;
        }

        /// <summary>
        /// 獲取Web遠程IP
        /// </summary>
        /// <returns></returns>
        private static string GetWebRemoteIp()
        {
            try
            {
                return HttpContext.Current.Request.ServerVariables["HTTP_X_FORWARDED_FOR"] ??
                       HttpContext.Current.Request.ServerVariables["REMOTE_ADDR"] ?? "";
            }
            catch (Exception e)
            {
                return string.Empty;
            }
        }

        /// <summary>
        /// 獲取Web代理真實IP
        /// </summary>
        /// <returns></returns>
        private string GetWebProxyRealIp()
        {
            var request = HttpContext.Current.Request;
            string ip = request.Headers.Get("x-forwarded-for");

            if (string.IsNullOrEmpty(ip) || string.Equals("unknown", ip, StringComparison.OrdinalIgnoreCase))
            {
                ip = request.Headers.Get("Proxy-Client-IP");
            }

            if (string.IsNullOrEmpty(ip) || string.Equals("unknown", ip, StringComparison.OrdinalIgnoreCase))
            {
                ip = request.Headers.Get("WL-Proxy-Client-IP");
            }

            if (string.IsNullOrEmpty(ip) || string.Equals("unknown", ip, StringComparison.OrdinalIgnoreCase))
            {
                ip = request.UserHostAddress;
            }

            if (string.IsNullOrEmpty(ip))
            {
                return string.Empty;
            }
            // 可能存在以下格式:X-Forwarded-For: client, proxy1, proxy2
            if (ip.Contains(", "))
            {
                // 若是存在多個反向代理,得到的IP是一個用逗號分隔的IP集合,取第一個
                // X-Forwarded-For: client  第一個
                string[] ips = ip.Split(new string[1] { ", " }, StringSplitOptions.RemoveEmptyEntries);
                var i = 0;
                for (i = 0; i < ips.Length; i++)
                {
                    if (ips[i] != "")
                    {
                        // 判斷是否爲內網IP
                        if (false == IsInnerIp(ips[i]))
                        {
                            IPAddress realIp;
                            if (IPAddress.TryParse(ips[i], out realIp) && ips[i].Split('.').Length == 4)
                            {
                                //合法IP
                                return ips[i];
                            }

                            return "";
                        }
                    }
                }

                ip = ips[0];// 默認獲取第一個IP地址
            }

            return ip;

        }

        /// <summary>
        /// 判斷IP地址是否爲內網IP地址
        /// </summary>
        /// <param name="ip">IP地址</param>
        /// <returns></returns>
        private bool IsInnerIp(string ip)
        {
            bool isInnerIp = false;
            ulong ipNum = Ip2Ulong(ip);

            /**
             * 私有IP
             * A類:10.0.0.0-10.255.255.255
             * B類:172.16.0.0-172.31.255.255
             * C類:192.168.0.0-192.168.255.255
             * 固然,還有127這個網段是環回地址
             */

            ulong aBegin = Ip2Ulong("10.0.0.0");
            ulong aEnd = Ip2Ulong("10.255.255.255");
            ulong bBegin = Ip2Ulong("172.16.0.0");
            ulong bEnd = Ip2Ulong("10.31.255.255");
            ulong cBegin = Ip2Ulong("192.168.0.0");
            ulong cEnd = Ip2Ulong("192.168.255.255");

            isInnerIp = IsInner(ipNum, aBegin, aEnd) || IsInner(ipNum, bBegin, bEnd) || IsInner(ipNum, cBegin, cEnd) ||
                        ip.Equals("127.0.0.1");
            return isInnerIp;
        }

        /// <summary>
        /// 將IP地址轉換爲Long型數字
        /// </summary>
        /// <param name="ip">IP地址</param>
        /// <returns></returns>
        private ulong Ip2Ulong(string ip)
        {
            byte[] bytes = IPAddress.Parse(ip).GetAddressBytes();
            ulong ret = 0;
            foreach (var b in bytes)
            {
                ret <<= 8;
                ret |= b;
            }

            return ret;
        }

        /// <summary>
        /// 判斷用戶IP地址轉換爲Long型後是否在內網IP地址所在範圍
        /// </summary>
        /// <param name="userIp">用戶IP</param>
        /// <param name="begin">開始範圍</param>
        /// <param name="end">結束範圍</param>
        /// <returns></returns>
        private bool IsInner(ulong userIp, ulong begin, ulong end)
        {
            return (userIp >= begin) && (userIp <= end);
        }

        /// <summary>
        /// 獲取局域網IP
        /// </summary>
        /// <returns></returns>
        private string GetLanIp()
        {
            foreach (var hostAddress in Dns.GetHostAddresses(Dns.GetHostName()))
            {
                if (hostAddress.AddressFamily == AddressFamily.InterNetwork)
                {
                    return hostAddress.ToString();
                }
            }
            return string.Empty;
        }

        #endregion
相關文章
相關標籤/搜索