C#下支付宝新版异步回调数据处理及校验(需支付宝提供的AopSdk)
2018-01-17 16:25
363 查看
对于支付宝,我们首先得赞扬下,提供了NET下的SDK,这个AopSdk当真是大而全,但同时却又不得不吐槽下,都提供了那么多的Request/Response(笔者下载的是alipay-sdk-NET20170615110549,里面光Request就有556个),但回调这一块居然一点都没提供(我确信肯定没提供,因为我按各种关键字进行了全文搜索),开放平台上也只说提供了服务端SDK,包含签名之类的功能,所以所有的接入者不得不自己写回调这一块
支付宝的回调与微信相比,除了签名外,还增加了notify_id用于校验该次请求是否是支付宝发起(注意这个id在你返回success后就失效了),所以更多了一层安全性
好了,啰嗦了这么多,还是直接来代码吧 /// <summary>
/// 支付宝回调辅助类
/// </summary>
public class AlipayNotifyHelper
{
/// <summary>
/// 根据请求数据获取对应的回调实体
/// </summary>
/// <typeparam name="T">回调实体</typeparam>
/// <param name="collection">请求数据</param>
/// <returns></returns>
public static T GetNotify<T>(NameValueCollection collection)
where T : new()
{
T entity = new T();
var props = typeof(T).GetProperties();
foreach (var key in collection.AllKeys)
{
var pName = Regex.Replace(key, @"(?:^|_)([a-zA-Z])", m => m.Groups[1].Value.ToUpper());
var p = props.FirstOrDefault(t => t.Name == pName);
if (p != null)
{
Type pType = null;
if (!p.PropertyType.IsGenericType)
{
pType = p.PropertyType;
}
else if (p.PropertyType.GetGenericTypeDefinition() == typeof(Nullable<>))
{
pType = Nullable.GetUnderlyingType(p.PropertyType);
}
if (pType != null)
{
p.SetValue(entity, ConvertTo(collection[key], pType));
}
}
}
return entity;
}
private static object ConvertTo(string value, Type type)
{
if (type.IsEnum)
{
return Enum.Parse(type, value);
}
else
{
return Convert.ChangeType(value, type);
}
}
/// <summary>
/// 对数据进行正确性校验,校验通过的话返回对应回调实体
/// </summary>
/// <typeparam name="T">回调实体</typeparam>
/// <param name="collection">请求数据</param>
/// <param name="alipayPublicKey">支付宝公钥</param>
/// <param name="keyFromFile">是否从文件读取 true代表从文件读取</param>
/// <param name="verifyFromAlipay">是否校验该次请求是否为支付宝发出</param>
/// <param name="mapiUrl">校验地址</param>
/// <param name="partnerKey">请求数据中合作伙伴号对应的键值</param>
/// <param name="notifyIdKey">请求数据中回调id对应的键值</param>
/// <param name="charsetKey">请求数据中charset对应的键值</param>
/// <param name="signTypeKey">请求数据中signtype对应的键值</param>
/// <returns></returns>
public static async Task<T> VerifyAndGetNotify<T>(NameValueCollection collection, string alipayPublicKey, bool keyFromFile,
bool verifyFromAlipay = true, string mapiUrl = "https://mapi.alipay.com/gateway.do", string partnerKey = "seller_id", string notifyIdKey = "notify_id", string charsetKey = "charset", string signTypeKey = "sign_type")
where T : new()
{
var parameters = collection.Cast<string>().ToDictionary(k => k, v => collection[v]);
if (Aop.Api.Util.AlipaySignature.RSACheckV1(parameters, alipayPublicKey, parameters[charsetKey], parameters[signTypeKey], keyFromFile)
&& await IsNotifiedFromAlipay(verifyFromAlipay, parameters[partnerKey], parameters[notifyIdKey], mapiUrl))
{
return GetNotify<T>(collection);
}
return default(T);
}
private static async Task<bool> IsNotifiedFromAlipay(bool verifyFromAlipay, string partner, string notifyId, string mapiUrl)
{
if (!verifyFromAlipay) return true;
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(string.Format("{0}?service=notify_verify&partner={1}¬ify_id={2}", mapiUrl, partner, notifyId));
var response = await request.GetResponseAsync();
using (var stream = response.GetResponseStream())
{
using (StreamReader sr = new StreamReader(stream))
{
return sr.ReadToEnd().Equals("true", StringComparison.OrdinalIgnoreCase);
}
}
}
/// <summary>
/// 获取实体对应的签名用字典
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="entity"></param>
/// <returns></returns>
public static IDictionary<string, string> GetSignDictionary<T>(T entity)
{
Dictionary<string, string> dic = new Dictionary<string, string>();
var props = typeof(T).GetProperties();
foreach (var p in props)
{
var pValue = p.GetValue(entity);
if (pValue != null)
{
var pKey = Regex.Replace(p.Name, @"[A-Z]", m => string.Format("{0}{1}", m.Index == 0 ? "" : "_", m.Value.ToLower()));
dic.Add(pKey, pValue.ToString());
}
}
return dic;
}
}这个类使用前提你定义的回调实体属性都是以Pascal 命名法命名,而且对于支付宝回传的复杂实体(Json)不能进行反序列化处理(但对于金额部分可以定义为decimal),以wap支付为例,以下是定义的回调实体
上面只是列举了代码,现在说下该怎么使用,因为支付宝的回调都是以application/x-www-form-urlencoded进行回调的,所以这里在方法参数上采用了NameValueCollection,即直接使用POST过来的数据,在校验通过后才返回对应的回调实体,下面分别列举Webform、MVC、WebAPI如何获取这个NameValueCollection
WebForm,这个无需举例,只需在Page_Load中,直接将Request.Form作为参数传递给VerifyAndGetNotify方法
MVC,这个可以同WebForm一样直接使用Request.Form,也可以按以下的方法定义Action,然后直接将FormCollection collection传递给VerifyAndGetNotify方法 public ActionResult Form(FormCollection collection)
{
//Request.Form 也可以通过这样的方式获取
return Redirect("Index");
}WebAPI,这个与上述两者皆不相同,当然这里也不考虑直接获取WebForm下的Request的方式,所以这里将以FormDataCollection来间接的获取,然后将获取到的col传递给VerifyAndGetNotify方法
2018-03-09补充:git下载已不再依赖AopSdk,同时支持Standard2.0
支付宝的回调与微信相比,除了签名外,还增加了notify_id用于校验该次请求是否是支付宝发起(注意这个id在你返回success后就失效了),所以更多了一层安全性
好了,啰嗦了这么多,还是直接来代码吧 /// <summary>
/// 支付宝回调辅助类
/// </summary>
public class AlipayNotifyHelper
{
/// <summary>
/// 根据请求数据获取对应的回调实体
/// </summary>
/// <typeparam name="T">回调实体</typeparam>
/// <param name="collection">请求数据</param>
/// <returns></returns>
public static T GetNotify<T>(NameValueCollection collection)
where T : new()
{
T entity = new T();
var props = typeof(T).GetProperties();
foreach (var key in collection.AllKeys)
{
var pName = Regex.Replace(key, @"(?:^|_)([a-zA-Z])", m => m.Groups[1].Value.ToUpper());
var p = props.FirstOrDefault(t => t.Name == pName);
if (p != null)
{
Type pType = null;
if (!p.PropertyType.IsGenericType)
{
pType = p.PropertyType;
}
else if (p.PropertyType.GetGenericTypeDefinition() == typeof(Nullable<>))
{
pType = Nullable.GetUnderlyingType(p.PropertyType);
}
if (pType != null)
{
p.SetValue(entity, ConvertTo(collection[key], pType));
}
}
}
return entity;
}
private static object ConvertTo(string value, Type type)
{
if (type.IsEnum)
{
return Enum.Parse(type, value);
}
else
{
return Convert.ChangeType(value, type);
}
}
/// <summary>
/// 对数据进行正确性校验,校验通过的话返回对应回调实体
/// </summary>
/// <typeparam name="T">回调实体</typeparam>
/// <param name="collection">请求数据</param>
/// <param name="alipayPublicKey">支付宝公钥</param>
/// <param name="keyFromFile">是否从文件读取 true代表从文件读取</param>
/// <param name="verifyFromAlipay">是否校验该次请求是否为支付宝发出</param>
/// <param name="mapiUrl">校验地址</param>
/// <param name="partnerKey">请求数据中合作伙伴号对应的键值</param>
/// <param name="notifyIdKey">请求数据中回调id对应的键值</param>
/// <param name="charsetKey">请求数据中charset对应的键值</param>
/// <param name="signTypeKey">请求数据中signtype对应的键值</param>
/// <returns></returns>
public static async Task<T> VerifyAndGetNotify<T>(NameValueCollection collection, string alipayPublicKey, bool keyFromFile,
bool verifyFromAlipay = true, string mapiUrl = "https://mapi.alipay.com/gateway.do", string partnerKey = "seller_id", string notifyIdKey = "notify_id", string charsetKey = "charset", string signTypeKey = "sign_type")
where T : new()
{
var parameters = collection.Cast<string>().ToDictionary(k => k, v => collection[v]);
if (Aop.Api.Util.AlipaySignature.RSACheckV1(parameters, alipayPublicKey, parameters[charsetKey], parameters[signTypeKey], keyFromFile)
&& await IsNotifiedFromAlipay(verifyFromAlipay, parameters[partnerKey], parameters[notifyIdKey], mapiUrl))
{
return GetNotify<T>(collection);
}
return default(T);
}
private static async Task<bool> IsNotifiedFromAlipay(bool verifyFromAlipay, string partner, string notifyId, string mapiUrl)
{
if (!verifyFromAlipay) return true;
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(string.Format("{0}?service=notify_verify&partner={1}¬ify_id={2}", mapiUrl, partner, notifyId));
var response = await request.GetResponseAsync();
using (var stream = response.GetResponseStream())
{
using (StreamReader sr = new StreamReader(stream))
{
return sr.ReadToEnd().Equals("true", StringComparison.OrdinalIgnoreCase);
}
}
}
/// <summary>
/// 获取实体对应的签名用字典
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="entity"></param>
/// <returns></returns>
public static IDictionary<string, string> GetSignDictionary<T>(T entity)
{
Dictionary<string, string> dic = new Dictionary<string, string>();
var props = typeof(T).GetProperties();
foreach (var p in props)
{
var pValue = p.GetValue(entity);
if (pValue != null)
{
var pKey = Regex.Replace(p.Name, @"[A-Z]", m => string.Format("{0}{1}", m.Index == 0 ? "" : "_", m.Value.ToLower()));
dic.Add(pKey, pValue.ToString());
}
}
return dic;
}
}这个类使用前提你定义的回调实体属性都是以Pascal 命名法命名,而且对于支付宝回传的复杂实体(Json)不能进行反序列化处理(但对于金额部分可以定义为decimal),以wap支付为例,以下是定义的回调实体
/// <summary> /// 支付宝wap支付回调 https://docs.open.alipay.com/203/105286/ /// </summary> public class AlipayTradeWapPayNotify { /// <summary> /// notify_time 通知时间 格式为yyyy-MM-dd HH:mm:ss /// </summary> public string NotifyTime { get; set; } /// <summary> /// notify_type 通知类型 /// </summary> public string NotifyType { get; set; } /// <summary> /// notify_id 通知校验ID /// </summary> public string NotifyId { get; set; } /// <summary> /// app_id 支付宝分配给开发者的应用Id /// </summary> public string AppId { get; set; } /// <summary> /// charset 编码格式,如utf-8、gbk、gb2312等 /// </summary> public string Charset { get; set; } /// <summary> /// version 调用的接口版本,固定为:1.0 /// </summary> public string Version { get; set; } /// <summary> /// sign_type 签名类型,商户生成签名字符串所使用的签名算法类型,目前支持RSA2和RSA,推荐使用RSA2 /// </summary> public AlipaySignType SignType { get; set; } /// <summary> /// sign 签名 /// </summary> public string Sign { get; set; } /// <summary> /// trade_no 支付宝交易凭证号 /// </summary> public string TradeNo { get; set; } /// <summary> /// out_trade_no 原支付请求的商户订单号 /// </summary> public string OutTradeNo { get; set; } /// <summary> /// out_biz_no 商户业务ID,主要是退款通知中返回退款申请的流水号 /// </summary> public string OutBizNo { get; set; } /// <summary> /// buyer_id 买家支付宝账号对应的支付宝唯一用户号。以2088开头的纯16位数字 /// </summary> public string BuyerId { get; set; } /// <summary> /// buyer_logon_id 买家支付宝账号 /// </summary> public string BuyerLogonId { get; set; } /// <summary> /// seller_id 卖家支付宝用户号 /// </summary> public string SellerId { get; set; } /// <summary> /// seller_email 卖家支付宝账号 /// </summary> public string SellerEmail { get; set; } /// <summary> /// trade_status 交易目前所处的状态 /// </summary> public AlipayTradeStatus? TradeStatus { get; set; } /// <summary> /// total_amount 本次交易支付的订单金额,单位为人民币(元) /// </summary> public decimal? TotalAmount { get; set; } /// <summary> /// receipt_amount 商家在交易中实际收到的款项,单位为元 /// </summary> public decimal? ReceiptAmount { get; set; } /// <summary> /// invoice_amount 用户在交易中支付的可开发票的金额 /// </summary> public decimal? InvoiceAmount { get; set; } /// <summary> /// buyer_pay_amount 用户在交易中支付的金额 /// </summary> public decimal? BuyerPayAmount { get; set; } /// <summary> /// point_amount 使用集分宝支付的金额 /// </summary> public decimal? PointAmount { get; set; } /// <summary> /// refund_fee 退款通知中,返回总退款金额,单位为元,支持两位小数 /// </summary> public decimal? RefundFee { get; set; } /// <summary> /// subject 商品的标题/交易标题/订单标题/订单关键字等,是请求时对应的参数,原样通知回来 /// </summary> public string Subject { get; set; } /// <summary> /// body 该订单的备注、描述、明细等。对应请求时的body参数,原样通知回来 /// </summary> public string Body { get; set; } /// <summary> /// gmt_create 该笔交易创建的时间。格式为yyyy-MM-dd HH:mm:ss /// </summary> public string GmtCreate { get; set; } /// <summary> /// gmt_payment 该笔交易的买家付款时间。格式为yyyy-MM-dd HH:mm:ss /// </summary> public string GmtPayment { get; set; } /// <summary> /// gmt_refund 该笔交易的退款时间。格式为yyyy-MM-dd HH:mm:ss.S /// </summary> public string GmtRefund { get; set; } /// <summary> /// gmt_close 该笔交易结束时间。格式为yyyy-MM-dd HH:mm:ss /// </summary> public string GmtClose { get; set; } /// <summary> /// fund_bill_list 支付成功的各个渠道金额信息 /// 例:[{"amount":"15.00","fundChannel":"ALIPAYACCOUNT"}] /// </summary> public string FundBillList { get; set; } /// <summary> /// passback_params 公共回传参数,如果请求时传递了该参数,则返回给商户时会在异步通知时将该参数原样返回。本参数必须进行UrlEncode之后才可以发送给支付宝 /// </summary> public string PassbackParams { get; set; } /// <summary> /// voucher_detail_list 本交易支付时所使用的所有优惠券信息 /// 例:[{"amount":"0.20","merchantContribute":"0.00","name":"一键创建券模板的券名称","otherContribute":"0.20","type":"ALIPAY_DISCOUNT_VOUCHER","memo":"学生卡8折优惠"] /// </summary> public string VoucherDetailList { get; set; } }注意凡是支付宝开放平台上注明不是必返回的值,这里都设置为了可空类型,然后是对应的几个枚举
/// <summary> /// alipay签名方式 /// </summary> public enum AlipaySignType { RSA2 = 0, RSA = 1 }
/// <summary> /// 订单支付状态 /// </summary> public enum AlipayTradeStatus { /// <summary> /// 交易创建,等待买家付款 /// </summary> WAIT_BUYER_PAY = 0, /// <summary> /// 商户签约的产品支持退款功能的前提下,买家付款成功 /// </summary> TRADE_SUCCESS = 1, /// <summary> /// 交易结束,不可退款 /// </summary> TRADE_FINISHED = 2, /// <summary> /// 未付款交易超时关闭,或支付完成后全额退款 /// </summary> TRADE_CLOSED = 3, }
上面只是列举了代码,现在说下该怎么使用,因为支付宝的回调都是以application/x-www-form-urlencoded进行回调的,所以这里在方法参数上采用了NameValueCollection,即直接使用POST过来的数据,在校验通过后才返回对应的回调实体,下面分别列举Webform、MVC、WebAPI如何获取这个NameValueCollection
WebForm,这个无需举例,只需在Page_Load中,直接将Request.Form作为参数传递给VerifyAndGetNotify方法
MVC,这个可以同WebForm一样直接使用Request.Form,也可以按以下的方法定义Action,然后直接将FormCollection collection传递给VerifyAndGetNotify方法 public ActionResult Form(FormCollection collection)
{
//Request.Form 也可以通过这样的方式获取
return Redirect("Index");
}WebAPI,这个与上述两者皆不相同,当然这里也不考虑直接获取WebForm下的Request的方式,所以这里将以FormDataCollection来间接的获取,然后将获取到的col传递给VerifyAndGetNotify方法
public void Post(FormDataCollection collection) { var col = collection.ReadAsNameValueCollection(); }说完了如何获取NameValueCollection,下面最后再列下使用的例子
string appId = "你的appid"; //这里除必输项外,所有可选参数都采用的默认值,注意默认是会向支付宝请求校验该次请求是否为支付宝发出的请求 var wapNotify = await AlipayNotifyHelper.VerifyAndGetNotify<AlipayTradeWapPayNotify>(collection, AlipayInfo.AlipayPublicKey, false); if (wapNotify != null) { if ((wapNotify.TradeStatus == AlipayTradeStatus.TRADE_SUCCESS || wapNotify.TradeStatus == AlipayTradeStatus.TRADE_FINISHED) && wapNotify.AppId == appId)//校验appid是否一致 { //校验wapNotify.OutTradeNo是否正确 //校验wapNotify.TotalAmount.Value是否一致 //校验wapNotify.SellerId是否正确(其实就是合作伙伴号,这一步实际我没有进行校验) //以上校验都通过后,根据wapNotify.TradeStatus来进行相应的业务处理 } }千万注意该辅助类依赖于支付宝提供的sdk,该sdk的下载地址为:https://docs.open.alipay.com/54/103419/完整的代码及示例可在 此处下载
2018-03-09补充:git下载已不再依赖AopSdk,同时支持Standard2.0
相关文章推荐
- c# 支付宝新版接口异步通知notify_url、Return_url处理
- C#应用消息队列异步处理数据存储
- C#网络Socket的数据发送与接收处理(利用异步)的模板(模式)
- C#网络Socket的数据发送与接收处理(利用异步)的模板(模式)
- jersey处理支付宝异步回调通知的问题:java.lang.IllegalArgumentException: Error parsing media type 'application/x-www-form-urlencoded; text/html; charset=UTF-8'
- Unity3d-c# Socket异步通讯与Unity组件数据更新的处理
- .net C# 异步socket ,监听和接收数据时可能会引起的 无法访问已释放的对像异常.捕捉处理.
- 支付宝即时到账接口,异步回调post请求的接受,Jersey 处理post参数的方法
- 【手机网络游戏 编程】C#异步socketAPI调用 处理数据的流程
- 支付宝通过https异步回调处理
- 串口通信中接收数据时延迟处理与缓存处理的解决方案(C#)
- MyGeneration学习笔记(8) :dOOdad提供的数据绑定、特殊函数和事务处理
- C# 多线程并发处理数据库数据,发送信号等待处理完统一插入.(转)
- 在C#中使用WIA获取扫描仪数据(利用Filter处理图片)
- C#Socket 网络通信异步处理
- c#中,确保数据接收完整的 串口处理程序
- C# 异步读取数据库里面的数据与绑定UI的解决办法
- 事件的机制 内存泄漏 事件的处理 异步装载回调 异步编程的实现 (知识点)
- 运用C#处理lob数据类型 (Oracle)
- ASP.NET 客户端回调实现 (C#) 示例 (VS2005)MSDN 错误处理