对于支付宝,我们首先得赞扬下,提供了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}&notify_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#下支付宝新版异步回调数据处理及校验(需支付宝提供的AopSdk)相关推荐

  1. 支付宝支付异步回调成功,页面跳转问题

    问题: 支付宝扫码支付成功异步回调了处理函数,需要返回信息告诉支付宝的.这样导致了我没办法去处理支付成功的页面跳转.想到一种方法就是ajax轮询,但是觉得如果用户一直不扫码,或一直不支付,那轮询就会不 ...

  2. 支付宝支付异步回调 302

    APP从测试环境迁移到生产环境,重新测试发现购买时,支付宝支付成功之后,订单状态未更新,即异步回调接口未执行. 检查发现notify_url是正确的,并且已经支付成功(账户有被扣款),登录蚂蚁金服开放 ...

  3. 支付宝php异步回调,支付宝支付成功之后异步回调处理

    /** * alipay_notify.php. * User: lvfk * Date: 2017/10/26 0026 * Time: 13:48 * Desc: 支付宝支付成功异步通知 */ i ...

  4. 支付宝支付异步回调的一些问题

    做了好多次支付宝支付,还是经常掉坑,现在整理下,作为参考 1,关于设置公钥 一定要注意,是"支付宝公钥",不要填应用公钥,不然就坑了 2.关于回调的域名地址 干净些,不要有参数,如 ...

  5. 支付宝wap异步回调失败问题

    支付宝返回数据中对fund_bill_list字段进行了转义,导致签名验证失败,在进行验签之前需要对改字段进行反转一下(htmlspecialchars_decode)

  6. 项目对接支付宝支付,内网穿透实现监听支付宝的支付成功异步回调通知

    调试的话使用支付宝的沙箱环境来进行调试 调试的时候因为是本地调试,没有上线的域名,无法被支付宝的异步回调请求所打到,这种情况下可以使用内网穿透来进行解决 内网穿透步骤: 内网穿透的几个常用软件 1.n ...

  7. 支付宝支付接口之异步回调

    近期写了一个微信支付的异步回调,现在就把支付宝的也写了吧. @Before(Tx.class)public void asynchronous_notify() throws UnsupportedE ...

  8. 【JUC系列】Future异步回调模式

    何为异步回调 前面只是一个例子,对并发的主要模式进行形象的说明. 下面正式来讲下经常使用的几个和并发相关的概念. 1.2.1. 同步.异步.阻塞.非阻塞 一:同步 所谓同步,就是在发出一个功能调用时, ...

  9. java异步多线程 判断线程状态_java多线程和异步回调

    在实际开发过程中遇到的多线程情况不多,但是在生产环境中多线程是最基本的情况,java面试时也会考到,所以看看多线程的知识还是很有必要的. Thread,Runnable,Callable,Future ...

最新文章

  1. 七牛云内容审核服务被选为「上海首批人工智能创新产品」
  2. cmd oracle sys登录_Oracle 忘记了sys和system密码和用户名的解决方法
  3. Docker selenium自动化 - windows版docker的安装与运行环境检测
  4. Python之tkinter:动态演示调用python库的tkinter带你进入GUI世界(text.insert/link各种事件)
  5. xss_url通关_1-10
  6. 新云网站管理系统最新版注入漏洞
  7. jQuery循环滚动展示代码
  8. 草稿 断开绑dgv1
  9. 数字彩色电视摄像机结构
  10. Xbox One:未来的客厅主角
  11. 【论文笔记_自监督知识蒸馏】Refine Myself by Teaching Myself : Feature Refinement via Self-Knowledge Distillation
  12. UE4天气效果加白天黑夜的平滑过度切换
  13. excel的sumif()函数和sumifs()函数
  14. 荣耀10青春版支持鸿蒙吗,荣耀10青春版详细评测:又一款年轻群体收割机
  15. 2022-03-27 screenX和clientX的区别以及offsetX和pageX的区别
  16. 日常水文章之Linux+arm+阿里IOT sdk+Cmake
  17. Vue3+TS + element-plus 项目 动态生成Icon图标
  18. 解决bug-python中关于Process finished with exit code -1073740791
  19. Android VCard联系人备份恢复(导入/导出)详解
  20. 随笔 数据库敏感数据加密存储

热门文章

  1. Least Crucial Node UVALive - 7456
  2. Arduino ESP8266/ESP32读取和改写MAC
  3. 防火墙、路由器、交换机+模拟软件+流控
  4. 解决虚拟机-虚拟网络配置没有桥接模式,本地没有虚拟网卡
  5. 售票计算机 制票机的使用方法,铁路客运计算机售票具体操作.pdf
  6. Spring Boot WebFlux 入门
  7. 动态规划(练习题目)
  8. 华胜天成收购中天安泰10%股权 出手网络信息安全
  9. 虚拟机+服务器管理器怎么打开,大话西游2特色版虚拟机镜像一键服务端+启动教程+物品ID+GM设置方法+数据库工具等...
  10. 七(10)springtask-RabbitMq-内容审核