這段時間工做中須要對接微信支付,並且要多個端同時進行接入,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}×tamp={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