当前位置: 首页 > news >正文

微信支付接入的总结 —— NATIVE MWEB JSAPI

        这段时间工作中需要对接微信支付,而且要多个端同时进行接入,web端,手机浏览器,微信浏览器,所以研究了下。不同场景选择合适的接入方式是必须的。https://pay.weixin.qq.com/wiki/doc/api/index.html 官网上对这些都有说明。基础配置的流程就不写了。下面直接进入代码环节。

     /// <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;
        }

  微信配置静态类字段的赋值

        /// <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中了。未过期之前直接获取就可以用。或者在实际应用中可以存在数据库中或者缓存中。

       /// <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中的统一下单接口的调用了

     /**
        * 
        * 统一下单
        * @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() 会对签名进行验证。回调的时候也可以直接调用。

    /**
        * @将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);
        }

吊起微信所需要的参数 

        /// <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=mp1421140842

  因为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

 

转载于:https://www.cnblogs.com/teng-0802/p/10611278.html

相关文章:

  • (转)shell中括号的特殊用法 linux if多条件判断
  • 第五周作业
  • http转https后资源加载不显示
  • 微信内嵌浏览器打开手机浏览器下载APP(APK)的方法
  • WordPress 主题开发商将客户当肉鸡,向对手发起 DDoS 攻击
  • 重装系统踩坑之路
  • [20190401]关于semtimedop函数调用.txt
  • gitlab改成中文版(汉化)
  • docker部署[centos/微服务], 安装mysql/mongodb/redis/elasticsearch
  • 手把手教你安装Linux性能监控工具——pydash
  • 宝马将机器架上微软 Azure,国内科技巨头也难抵汽车“诱惑”
  • 微服务落地,我们在考虑什么?\n
  • Vue-cli 3.0基础项目工程模板
  • 北大AI公开课2019 | 微软亚洲研究院周明:NLP的进步将如何改变搜索体验?
  • 多态使用时,父类多态时需要使用子类特有对象。需要判断 就使用instanceof
  • __proto__ 和 prototype的关系
  • 「前端」从UglifyJSPlugin强制开启css压缩探究webpack插件运行机制
  • 【Leetcode】104. 二叉树的最大深度
  • 【node学习】协程
  • 【每日笔记】【Go学习笔记】2019-01-10 codis proxy处理流程
  • Angular 响应式表单 基础例子
  • Fabric架构演变之路
  • Next.js之基础概念(二)
  • October CMS - 快速入门 9 Images And Galleries
  • Ruby 2.x 源代码分析:扩展 概述
  • springMvc学习笔记(2)
  • Vue 2.3、2.4 知识点小结
  • 阿里研究院入选中国企业智库系统影响力榜
  • 给github项目添加CI badge
  • 给初学者:JavaScript 中数组操作注意点
  • 基于游标的分页接口实现
  • 数组大概知多少
  • 线上 python http server profile 实践
  • 原创:新手布局福音!微信小程序使用flex的一些基础样式属性(一)
  • gunicorn工作原理
  • ​ubuntu下安装kvm虚拟机
  • ​如何防止网络攻击?
  • #常见电池型号介绍 常见电池尺寸是多少【详解】
  • $con= MySQL有关填空题_2015年计算机二级考试《MySQL》提高练习题(10)
  • (12)目标检测_SSD基于pytorch搭建代码
  • (2022 CVPR) Unbiased Teacher v2
  • (3)nginx 配置(nginx.conf)
  • (Bean工厂的后处理器入门)学习Spring的第七天
  • (C语言)输入一个序列,判断是否为奇偶交叉数
  • (LeetCode) T14. Longest Common Prefix
  • (zt)最盛行的警世狂言(爆笑)
  • (第9篇)大数据的的超级应用——数据挖掘-推荐系统
  • (附源码)springboot炼糖厂地磅全自动控制系统 毕业设计 341357
  • (附源码)springboot学生选课系统 毕业设计 612555
  • (附源码)计算机毕业设计ssm高校《大学语文》课程作业在线管理系统
  • (求助)用傲游上csdn博客时标签栏和网址栏一直显示袁萌 的头像
  • (十六)串口UART
  • (一一四)第九章编程练习
  • ******IT公司面试题汇总+优秀技术博客汇总
  • .desktop 桌面快捷_Linux桌面环境那么多,这几款优秀的任你选