您的位置:首页 > 编程语言 > C#

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支付为例,以下是定义的回调实体
/// <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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐