微信支付接入的总结 —— 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}×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
微信支付接入的总结 —— NATIVE & MWEB & JSAPI的更多相关文章
- C#开发微信门户及应用(32)--微信支付接入和API封装使用
在微信的应用上,微信支付是一个比较有用的部分,但也是比较复杂的技术要点,在微商大行其道的年代,自己的商店没有增加微信支付好像也说不过去,微信支付旨在为广大微信用户及商户提供更优质的支付服务,微信的支付 ...
- 亲历H5移动端游戏微信支付接入及那些坑(二)——获取Openid和授权
第一篇中将一些坑说明,那么这篇开始正式进入接入步骤.具体的参数说明,我不会列出,毕竟微信官方文档都有,我想大家都看的懂,而且这接口也有可能微信会变动,所以不列出来,也是不想引起大家的误解,接入步骤只起 ...
- 亲历H5移动端游戏微信支付接入及那些坑(四)——参考文档
写完三篇后,我觉得微信支付的文档确实比较乱,所以在此做一个整理汇总 支付流程相关文档 一下文档已经按照接入顺序排列,请依次参考阅读 微信公众号网页授权两种access_token区别,获取用户open ...
- 亲历H5移动端游戏微信支付接入及那些坑(一)——支付方式与坑
最近项目进入中后期,开始接入支付.要求是使用微信支付,呵呵,好笑的是不知老板从哪里听来的,居然和我说只要是熟手,接个微信支付两小时搞定,我只能再次呵呵.先不说支付处理逻辑,而且公司本来也没现成的接入模 ...
- 亲历H5移动端游戏微信支付接入及那些坑(三)——支付接入
终于到接入支付了,小小的一个微信支付,居然也写了3篇,好长,好累. 接入环境 对接入环境,前端的话,应该是以js为主吧,也有可能是,PHP,Java,C++,或者C#都可以.为什么在此特意提一下接入环 ...
- 微信支付v2开发(11) Native支付
关键字:微信公众平台 微信支付 Native原生支付 作者:方倍工作室 原文:http://www.cnblogs.com/txw1958/p/wxpay-native.html 在这篇微信公众平台开 ...
- Android app 第三方微信支付接入详解
微信支付做了好几遍了,都没有出现什么棘手的问题,下面一一为大家分享一下,欢迎吐槽. 还是老样子,接入微信的支付要第一步添加微信支付官方的包libammsdk.jar 首先就处理略坑的一个问题,app应 ...
- C#开发微信门户及应用(40)--使用微信JSAPI实现微信支付功能
在我前面的几篇博客,有介绍了微信支付.微信红包.企业付款等各种和支付相关的操作,不过上面都是基于微信普通API的封装,本篇随笔继续微信支付这一主题,继续介绍基于微信网页JSAPI的方式发起的微信支付功 ...
- 关于IOS调用微信支付jsapi不起作用的解决方法
微信支付时,安卓机调用 jsapi可以支付,IOS就不行,点击立即支付,直接返回原立即支付页面,跟刷新页面差不多,解决方案很简单:两句话而已. 不得不说,微信支付坑太多了,我擦..... <sc ...
随机推荐
- 【转】Jmeter JDBC请求的问题
如何添加一个JDBC请求?本次以Orale为例. 1 From网上下载一个名为Class12.jar 的驱动包,然后放到该目录下:[否则会提示no suitable driver] 2 查看链接O ...
- Linux面试题大全(带答案)
一.填空题:1. 在Linux系统中,以 文件 方式访问设备 .2. Linux内核引导时,从文件 /etc/fstab 中读取要加载的文件系统.3. Linux文件系统中每个文件用 索引节点来标识. ...
- oracle里的查询转换
oracle里的查询转换的作用 Oracle里的查询转换,有称为查询改写,指oracle在执行目标sql时可能会做等价改写,目的是为了更高效的执行目标sql 在10g及其以后的版本中,oracle会对 ...
- NSString 转换
NSString *tempA = @"123"; NSString *tempB = @"456"; 1,字符串拼接 NSString *newString ...
- iOS平台下闪退原因汇总(一):"Ran out of trampolines of type 0/1/2" 运行时间错误
"Ran out of trampolines of type 0/1/2" 运行时间错误通常出现在使用大量递归泛型时.要看到这个错误需要连接着设备直接将项目build到设备里运行 ...
- 16c550芯片编写的优化
参考了 <Altera FPGA/CPLD 设计>高级篇, 关于状态机的推荐写法实现的功能是一样的但是编译使用的逻辑门如下图: 下图是我自己编的状态机需要的逻辑: 下图是使用推荐的有限状态 ...
- 用一两句话说一下你对“盒模型”这个概念的理解,和它都涉及到哪些css属性
网页设计中常听的属性名:内容(content).填充(padding).边框(border).边界(margin), CSS盒子模式都具备这些属性. 这些属性我们可以用日常生活中的常见事物——盒子作一 ...
- Python Twisted系列教程10:增强defer功能的客户端
作者:dave@http://krondo.com/an-introduction-to-asynchronous-programming-and-twisted/ 译者:杨晓伟(采用意译) 可以从这 ...
- [019] Android平台调用WebService详解
http://blog.csdn.net/lyq8479/article/details/6428288/ http://www.cnblogs.com/gzggyy/archive/2011/06/ ...
- C语言运算符优先级和口诀 (转)
一共有十五个优先级: 1 () [] . -> 2 ! ~ -(负号) ++ -- &(取变量地址)* (type)(强制类型) sizeof 3 ...