.Net后台实现微信小程序支付

最近一直再研究微信支付和支付宝支付,官方支付文档中一直在讲与第三方支付打交道的原理,却没有介绍我们自己项目中的APP与后台该怎么交互(哈哈,人家也没必要介绍这一块)。拜读了官方文档和前辈们的佳作,自己在这里做一些总结。

不管是微信支付还是支付宝支付,使用的是小程序、APP或网页都可以用以下示例图来说明。

支付流程:


  ① 支付端将订单号(小程序中还需要传递登录凭证)传递至后台商户。

  ②后台验证订单、统计订单总价,请求第三方获取下单参数。

  ③第三方返回下单参数。

  ④后台将从第三方返回的参数按需要返回至支付端。

  ⑤支付端拿着后台返回的参数下单。

  ⑥第三方返回支付结果。

  ⑦支付成功后,第三方发起支付回调通知商户后台,在这一步,商户可在回调中修改订单以及用户的相关支付状态。

微信小程序支付:


 先放一张官方的图

具体实现:

新建App.Pay项目,在新项目中新建Log类,记录操作过程中的日志。

 1 using System;
 2 using System.Collections.Generic;
 3 using System.IO;
 4 using System.Linq;
 5 using System.Text;
 6 using System.Threading.Tasks;
 7 using System.Web;
 8 
 9 namespace App.Pay
10 {
11     public class Log
12     {
13         //在网站根目录下创建日志目录
14         public string path;
15 
16         public Log(string path)
17         {
18             this.path = HttpContext.Current.Request.PhysicalApplicationPath + path;
19         }
20         /**
21          * 向日志文件写入调试信息
22          * @param className 类名
23          * @param content 写入内容
24          */
25         public void Debug(string className, string content)
26         {
27             WriteLog("DEBUG", className, content);
28         }
29 
30         /**
31         * 向日志文件写入运行时信息
32         * @param className 类名
33         * @param content 写入内容
34         */
35         public void Info(string className, string content)
36         {
37             WriteLog("INFO", className, content);
38         }
39 
40         /**
41         * 向日志文件写入出错信息
42         * @param className 类名
43         * @param content 写入内容
44         */
45         public void Error(string className, string content)
46         {
47             WriteLog("ERROR", className, content);
48         }
49 
50         /**
51         * 实际的写日志操作
52         * @param type 日志记录类型
53         * @param className 类名
54         * @param content 写入内容
55         */
56         protected void WriteLog(string type, string className, string content)
57         {
58             if (!Directory.Exists(path))//如果日志目录不存在就创建
59             {
60                 Directory.CreateDirectory(path);
61             }
62 
63             string time = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff");//获取当前系统时间
64             string filename = path + "/" + DateTime.Now.ToString("yyyy-MM-dd") + ".log";//用日期对日志文件命名
65 
66             //创建或打开日志文件,向日志文件末尾追加记录
67             StreamWriter mySw = File.AppendText(filename);
68 
69             //向日志文件写入内容
70             string write_content = time + " " + type + " " + className + ": " + content;
71             mySw.WriteLine(write_content);
72 
73             //关闭日志文件
74             mySw.Close();
75         }
76     }
77 }
View Code

新建WePay文件夹,新建Config基类,存放微信支付的公共配置参数。

 1 using System;
 2 using System.Collections.Generic;
 3 using System.Linq;
 4 using System.Text;
 5 using System.Threading.Tasks;
 6 
 7 namespace App.Pay.WePay
 8 {
 9     /**
10     *     配置账号信息
11     */
12     public class WePayConfig
13     {
14         //=======【商户系统后台机器IP】===================================== 
15         /* 此参数可手动配置也可在程序中自动获取
16         */
17         public const string IP = "8.8.8.8";
18 
19 
20         //=======【代理服务器设置】===================================
21         /* 默认IP和端口号分别为0.0.0.0和0,此时不开启代理(如有需要才设置)
22         */
23         public const string PROXY_URL = "";
24 
25         //=======【上报信息配置】===================================
26         /* 测速上报等级,0.关闭上报; 1.仅错误时上报; 2.全量上报
27         */
28         public const int REPORT_LEVENL = 1;
29 
30         //=======【日志级别】===================================
31         /* 日志等级,0.不输出日志;1.只输出错误信息; 2.输出错误和正常信息; 3.输出错误信息、正常信息和调试信息
32         */
33         public const int LOG_LEVENL = 3;
34     }
35 }
View Code

新建Exception类,捕获微信支付过程中的异常。

 1 using System;
 2 using System.Collections.Generic;
 3 using System.Linq;
 4 using System.Text;
 5 using System.Threading.Tasks;
 6 
 7 namespace App.Pay.WePay
 8 {
 9     public class WePayException : Exception
10     {
11         public WePayException(string msg) : base(msg)
12         {
13 
14         }
15     }
16 }
View Code

新建SafeXMLDocument类

 1 using System;
 2 using System.Collections.Generic;
 3 using System.Linq;
 4 using System.Text;
 5 using System.Threading.Tasks;
 6 using System.Xml;
 7 
 8 namespace App.Pay.WePay
 9 {
10     public class SafeXmlDocument : XmlDocument
11     {
12         public SafeXmlDocument()
13         {
14             this.XmlResolver = null;
15         }
16     }
17 }
View Code

新建WeHelper类,目前只有一个方法,微信小程序支付中将登录凭证转换为openId。

 1 using App.Common.Extension;
 2 using Newtonsoft.Json.Linq;
 3 using System;
 4 using System.Collections.Generic;
 5 using System.Configuration;
 6 using System.Linq;
 7 using System.Net;
 8 using System.Security.Cryptography;
 9 using System.Text;
10 using System.Threading.Tasks;
11 
12 namespace App.Pay.WePay
13 {
14     public class WeHelper
15     {
16         // 小程序
17         private static string _appid = ConfigurationManager.AppSettings["wxAPPID"];
18         // 小程序
19         private static string _appSecret = ConfigurationManager.AppSettings["wxAppSecret"];
20 
21         public static WxSession Code2Session(string code)
22         {
23             var url = $"https://api.weixin.qq.com/sns/jscode2session?appid={_appid}&secret={_appSecret}&js_code={code}&grant_type=authorization_code";
24             try
25             {
26                 var request = WebRequest.Create(url);
27                 using (var response = request.GetResponse())
28                 {
29                     using (var rs = response.GetResponseStream())
30                     {
31                         using (var s = new System.IO.StreamReader(rs))
32                         {
33                             return s.ReadToEnd().JsonTo<WxSession>();
34                         }
35                     }
36                 }
37             }
38             catch (Exception)
39             {
40                 return null;
41             }
42         }
43     }
44 
45     public class WxSession
46     {
47         public string openid { get; set; }
48         public string session_key { get; set; }
49         public string errcode { get; set; }
50         public string errMsg { get; set; }
51         public string unionid { get; set; }
52     }
53 
54 }
View Code

以上四个类是微信支付通用的,因此统一放在了微信支付文件夹下。

新建XcxPay文件夹,用于存放微信小程序支付的文件,新建XcxPayConfig类,存放关于小程序支付参数,小程序APPID、账号Secert、商户号、商户支付密钥、支付回调地址。我把这些参数值都放在了解决方案的config配置文件中。

 1 using System;
 2 using System.Collections.Generic;
 3 using System.Linq;
 4 using System.Text;
 5 using System.Threading.Tasks;
 6 using System.Web.Configuration;
 7 
 8 namespace App.Pay.WePay.XcxPay
 9 {
10     public class XcxPayConfig : WePayConfig
11     {
12         //=======【基本信息设置】=====================================
13         /* 微信公众号信息配置
14         * APPID:绑定支付的APPID(必须配置)
15         * MCHID:商户号(必须配置)
16         * KEY:商户支付密钥,参考开户邮件设置(必须配置)
17         * APPSECRET:公众帐号secert(仅JSAPI支付的时候需要配置)
18         */
19         /// 小程序支付
20         public static string APPID = WebConfigurationManager.AppSettings["XcxAppID"].ToString();
21         public static string MCHID = WebConfigurationManager.AppSettings["XcxMchID"].ToString();
22         public static string KEY = WebConfigurationManager.AppSettings["XcxKey"].ToString();
23         public static string APPSECRET = WebConfigurationManager.AppSettings["XcxAppSecret"].ToString();
24 
25         //=======【证书路径设置】===================================== 
26         /* 证书路径,注意应该填写绝对路径(仅退款、撤销订单时需要)
27         */
28         public const string SSLCERT_PATH = "cert/apiclient_cert.p12";
29         public const string SSLCERT_PASSWORD = "1233410002";
30 
31         //=======【支付结果通知url】===================================== 
32         /* 支付结果通知回调url,用于商户接收支付结果
33         */
34         public static string NOTIFY_URL = WebConfigurationManager.AppSettings["XcxNotifyUrl"].ToString();
35 
36         // log记录
37         public static string LogPath = WebConfigurationManager.AppSettings["XcxLog"].ToString();
38     }
39 }
View Code
<!--小程序支付-->
    <add key="XcxAppID" value="" />
    <add key="XcxAppSecret" value="" />
    <add key="XcxMchID" value="" />
    <add key="XcxKey" value="" />
<!--回调通知-->
    <add key="XcxNotifyUrl" value="" />
View Code

新建WeXcxPayApi类

  1 using System;
  2 using System.Collections.Generic;
  3 using System.Linq;
  4 using System.Text;
  5 using System.Threading.Tasks;
  6 
  7 namespace App.Pay.WePay.XcxPay
  8 {
  9     public class XcxPayApi
 10     {
 11         public static Log Log = new Log(XcxPayConfig.LogPath);
 12 
 13         /**
 14         * 提交被扫支付API
 15         * 收银员使用扫码设备读取微信用户刷卡授权码以后,二维码或条码信息传送至商户收银台,
 16         * 由商户收银台或者商户后台调用该接口发起支付。
 17         * @param WxPayData inputObj 提交给被扫支付API的参数
 18         * @param int timeOut 超时时间
 19         * @throws WePayException
 20         * @return 成功时返回调用结果,其他抛异常
 21         */
 22         public static XcxPayData Micropay(XcxPayData inputObj, int timeOut = 10)
 23         {
 24             string url = "https://api.mch.weixin.qq.com/pay/micropay";
 25             //检测必填参数
 26             if (!inputObj.IsSet("body"))
 27             {
 28                 throw new WePayException("提交被扫支付API接口中,缺少必填参数body!");
 29             }
 30             else if (!inputObj.IsSet("out_trade_no"))
 31             {
 32                 throw new WePayException("提交被扫支付API接口中,缺少必填参数out_trade_no!");
 33             }
 34             else if (!inputObj.IsSet("total_fee"))
 35             {
 36                 throw new WePayException("提交被扫支付API接口中,缺少必填参数total_fee!");
 37             }
 38             else if (!inputObj.IsSet("auth_code"))
 39             {
 40                 throw new WePayException("提交被扫支付API接口中,缺少必填参数auth_code!");
 41             }
 42 
 43             inputObj.SetValue("spbill_create_ip", WePayConfig.IP);//终端ip
 44             inputObj.SetValue("appid", XcxPayConfig.APPID);//公众账号ID
 45             inputObj.SetValue("mch_id", XcxPayConfig.MCHID);//商户号
 46             inputObj.SetValue("nonce_str", Guid.NewGuid().ToString().Replace("-", ""));//随机字符串
 47             inputObj.SetValue("sign", inputObj.MakeSign());//签名
 48             string xml = inputObj.ToXml();
 49 
 50             var start = DateTime.Now;//请求开始时间
 51 
 52             Log.Info("XcxPayApi", "MicroPay request : " + xml);
 53             string response = XcxPayHttpService.Post(xml, url, false, timeOut);//调用HTTP通信接口以提交数据到API
 54             Log.Info("XcxPayApi", "MicroPay response : " + response);
 55 
 56             var end = DateTime.Now;
 57             int timeCost = (int)((end - start).TotalMilliseconds);//获得接口耗时
 58 
 59             //将xml格式的结果转换为对象以返回
 60             XcxPayData result = new XcxPayData();
 61             result.FromXml(response);
 62 
 63             ReportCostTime(url, timeCost, result);//测速上报
 64 
 65             return result;
 66         }
 67 
 68 
 69         /**
 70         *    
 71         * 查询订单
 72         * @param WxPayData inputObj 提交给查询订单API的参数
 73         * @param int timeOut 超时时间
 74         * @throws WePayException
 75         * @return 成功时返回订单查询结果,其他抛异常
 76         */
 77         public static XcxPayData OrderQuery(XcxPayData inputObj, int timeOut = 6)
 78         {
 79             string url = "https://api.mch.weixin.qq.com/pay/orderquery";
 80             //检测必填参数
 81             if (!inputObj.IsSet("out_trade_no") && !inputObj.IsSet("transaction_id"))
 82             {
 83                 throw new WePayException("订单查询接口中,out_trade_no、transaction_id至少填一个!");
 84             }
 85 
 86             inputObj.SetValue("appid", XcxPayConfig.APPID);//公众账号ID
 87             inputObj.SetValue("mch_id", XcxPayConfig.MCHID);//商户号
 88             inputObj.SetValue("nonce_str", XcxPayApi.GenerateNonceStr());//随机字符串
 89             inputObj.SetValue("sign", inputObj.MakeSign());//签名
 90 
 91             string xml = inputObj.ToXml();
 92 
 93             var start = DateTime.Now;
 94 
 95             Log.Info("XcxPayApi", "OrderQuery request : " + xml);
 96             string response = XcxPayHttpService.Post(xml, url, false, timeOut);//调用HTTP通信接口提交数据
 97             Log.Info("XcxPayApi", "OrderQuery response : " + response);
 98 
 99             var end = DateTime.Now;
100             int timeCost = (int)((end - start).TotalMilliseconds);//获得接口耗时
101 
102             //将xml格式的数据转化为对象以返回
103             XcxPayData result = new XcxPayData();
104             result.FromXml(response);
105 
106             ReportCostTime(url, timeCost, result);//测速上报
107 
108             return result;
109         }
110 
111 
112         /**
113         * 
114         * 撤销订单API接口
115         * @param WxPayData inputObj 提交给撤销订单API接口的参数,out_trade_no和transaction_id必填一个
116         * @param int timeOut 接口超时时间
117         * @throws WePayException
118         * @return 成功时返回API调用结果,其他抛异常
119         */
120         public static XcxPayData Reverse(XcxPayData inputObj, int timeOut = 6)
121         {
122             string url = "https://api.mch.weixin.qq.com/secapi/pay/reverse";
123             //检测必填参数
124             if (!inputObj.IsSet("out_trade_no") && !inputObj.IsSet("transaction_id"))
125             {
126                 throw new WePayException("撤销订单API接口中,参数out_trade_no和transaction_id必须填写一个!");
127             }
128 
129             inputObj.SetValue("appid", XcxPayConfig.APPID);//公众账号ID
130             inputObj.SetValue("mch_id", XcxPayConfig.MCHID);//商户号
131             inputObj.SetValue("nonce_str", GenerateNonceStr());//随机字符串
132             inputObj.SetValue("sign", inputObj.MakeSign());//签名
133             string xml = inputObj.ToXml();
134 
135             var start = DateTime.Now;//请求开始时间
136 
137             Log.Info("XcxPayApi", "Reverse request : " + xml);
138 
139             string response = XcxPayHttpService.Post(xml, url, true, timeOut);
140 
141             Log.Info("XcxPayApi", "Reverse response : " + response);
142 
143             var end = DateTime.Now;
144             int timeCost = (int)((end - start).TotalMilliseconds);
145 
146             XcxPayData result = new XcxPayData();
147             result.FromXml(response);
148 
149             ReportCostTime(url, timeCost, result);//测速上报
150 
151             return result;
152         }
153 
154 
155         /**
156         * 
157         * 申请退款
158         * @param WxPayData inputObj 提交给申请退款API的参数
159         * @param int timeOut 超时时间
160         * @throws WePayException
161         * @return 成功时返回接口调用结果,其他抛异常
162         */
163         public static XcxPayData Refund(XcxPayData inputObj, int timeOut = 6)
164         {
165             string url = "https://api.mch.weixin.qq.com/secapi/pay/refund";
166             //检测必填参数
167             if (!inputObj.IsSet("out_trade_no") && !inputObj.IsSet("transaction_id"))
168             {
169                 throw new WePayException("退款申请接口中,out_trade_no、transaction_id至少填一个!");
170             }
171             else if (!inputObj.IsSet("out_refund_no"))
172             {
173                 throw new WePayException("退款申请接口中,缺少必填参数out_refund_no!");
174             }
175             else if (!inputObj.IsSet("total_fee"))
176             {
177                 throw new WePayException("退款申请接口中,缺少必填参数total_fee!");
178             }
179             else if (!inputObj.IsSet("refund_fee"))
180             {
181                 throw new WePayException("退款申请接口中,缺少必填参数refund_fee!");
182             }
183             else if (!inputObj.IsSet("op_user_id"))
184             {
185                 throw new WePayException("退款申请接口中,缺少必填参数op_user_id!");
186             }
187 
188             inputObj.SetValue("appid", XcxPayConfig.APPID);//公众账号ID
189             inputObj.SetValue("mch_id", XcxPayConfig.MCHID);//商户号
190             inputObj.SetValue("nonce_str", Guid.NewGuid().ToString().Replace("-", ""));//随机字符串
191             inputObj.SetValue("sign", inputObj.MakeSign());//签名
192 
193             string xml = inputObj.ToXml();
194             var start = DateTime.Now;
195 
196             Log.Info("XcxPayApi", "Refund request : " + xml);
197             string response = XcxPayHttpService.Post(xml, url, true, timeOut);//调用HTTP通信接口提交数据到API
198             Log.Info("XcxPayApi", "Refund response : " + response);
199 
200             var end = DateTime.Now;
201             int timeCost = (int)((end - start).TotalMilliseconds);//获得接口耗时
202 
203             //将xml格式的结果转换为对象以返回
204             XcxPayData result = new XcxPayData();
205             result.FromXml(response);
206 
207             ReportCostTime(url, timeCost, result);//测速上报
208 
209             return result;
210         }
211 
212 
213         /**
214         * 
215         * 查询退款
216         * 提交退款申请后,通过该接口查询退款状态。退款有一定延时,
217         * 用零钱支付的退款20分钟内到账,银行卡支付的退款3个工作日后重新查询退款状态。
218         * out_refund_no、out_trade_no、transaction_id、refund_id四个参数必填一个
219         * @param WxPayData inputObj 提交给查询退款API的参数
220         * @param int timeOut 接口超时时间
221         * @throws WePayException
222         * @return 成功时返回,其他抛异常
223         */
224         public static XcxPayData RefundQuery(XcxPayData inputObj, int timeOut = 6)
225         {
226             string url = "https://api.mch.weixin.qq.com/pay/refundquery";
227             //检测必填参数
228             if (!inputObj.IsSet("out_refund_no") && !inputObj.IsSet("out_trade_no") &&
229                 !inputObj.IsSet("transaction_id") && !inputObj.IsSet("refund_id"))
230             {
231                 throw new WePayException("退款查询接口中,out_refund_no、out_trade_no、transaction_id、refund_id四个参数必填一个!");
232             }
233 
234             inputObj.SetValue("appid", XcxPayConfig.APPID);//公众账号ID
235             inputObj.SetValue("mch_id", XcxPayConfig.MCHID);//商户号
236             inputObj.SetValue("nonce_str", GenerateNonceStr());//随机字符串
237             inputObj.SetValue("sign", inputObj.MakeSign());//签名
238 
239             string xml = inputObj.ToXml();
240 
241             var start = DateTime.Now;//请求开始时间
242 
243             Log.Info("XcxPayApi", "RefundQuery request : " + xml);
244             string response = XcxPayHttpService.Post(xml, url, false, timeOut);//调用HTTP通信接口以提交数据到API
245             Log.Info("XcxPayApi", "RefundQuery response : " + response);
246 
247             var end = DateTime.Now;
248             int timeCost = (int)((end - start).TotalMilliseconds);//获得接口耗时
249 
250             //将xml格式的结果转换为对象以返回
251             XcxPayData result = new XcxPayData();
252             result.FromXml(response);
253 
254             ReportCostTime(url, timeCost, result);//测速上报
255 
256             return result;
257         }
258 
259 
260         /**
261         * 下载对账单
262         * @param WxPayData inputObj 提交给下载对账单API的参数
263         * @param int timeOut 接口超时时间
264         * @throws WePayException
265         * @return 成功时返回,其他抛异常
266         */
267         public static XcxPayData DownloadBill(XcxPayData inputObj, int timeOut = 6)
268         {
269             string url = "https://api.mch.weixin.qq.com/pay/downloadbill";
270             //检测必填参数
271             if (!inputObj.IsSet("bill_date"))
272             {
273                 throw new WePayException("对账单接口中,缺少必填参数bill_date!");
274             }
275 
276             inputObj.SetValue("appid", XcxPayConfig.APPID);//公众账号ID
277             inputObj.SetValue("mch_id", XcxPayConfig.MCHID);//商户号
278             inputObj.SetValue("nonce_str", GenerateNonceStr());//随机字符串
279             inputObj.SetValue("sign", inputObj.MakeSign());//签名
280 
281             string xml = inputObj.ToXml();
282 
283             Log.Info("XcxPayApi", "DownloadBill request : " + xml);
284             string response = XcxPayHttpService.Post(xml, url, false, timeOut);//调用HTTP通信接口以提交数据到API
285             Log.Info("XcxPayApi", "DownloadBill result : " + response);
286 
287             XcxPayData result = new XcxPayData();
288             //若接口调用失败会返回xml格式的结果
289             if (response.Substring(0, 5) == "<xml>")
290             {
291                 result.FromXml(response);
292             }
293             //接口调用成功则返回非xml格式的数据
294             else
295                 result.SetValue("result", response);
296 
297             return result;
298         }
299 
300 
301         /**
302         * 
303         * 转换短链接
304         * 该接口主要用于扫码原生支付模式一中的二维码链接转成短链接(weixin://wxpay/s/XXXXXX),
305         * 减小二维码数据量,提升扫描速度和精确度。
306         * @param WxPayData inputObj 提交给转换短连接API的参数
307         * @param int timeOut 接口超时时间
308         * @throws WePayException
309         * @return 成功时返回,其他抛异常
310         */
311         public static XcxPayData ShortUrl(XcxPayData inputObj, int timeOut = 6)
312         {
313             string url = "https://api.mch.weixin.qq.com/tools/shorturl";
314             //检测必填参数
315             if (!inputObj.IsSet("long_url"))
316             {
317                 throw new WePayException("需要转换的URL,签名用原串,传输需URL encode!");
318             }
319 
320             inputObj.SetValue("appid", XcxPayConfig.APPID);//公众账号ID
321             inputObj.SetValue("mch_id", XcxPayConfig.MCHID);//商户号
322             inputObj.SetValue("nonce_str", GenerateNonceStr());//随机字符串    
323             inputObj.SetValue("sign", inputObj.MakeSign());//签名
324             inputObj.SetValue("device_info", "wxAPP");//设备名称
325             string xml = inputObj.ToXml();
326 
327             var start = DateTime.Now;//请求开始时间
328 
329             Log.Info("XcxPayApi", "ShortUrl request : " + xml);
330             string response = XcxPayHttpService.Post(xml, url, false, timeOut);
331             Log.Info("XcxPayApi", "ShortUrl response : " + response);
332 
333             var end = DateTime.Now;
334             int timeCost = (int)((end - start).TotalMilliseconds);
335 
336             XcxPayData result = new XcxPayData();
337             result.FromXml(response);
338             ReportCostTime(url, timeCost, result);//测速上报
339 
340             return result;
341         }
342 
343 
344         /**
345         * 
346         * 统一下单
347         * @param WxPaydata inputObj 提交给统一下单API的参数
348         * @param int timeOut 超时时间
349         * @throws WePayException
350         * @return 成功时返回,其他抛异常
351         */
352         public static XcxPayData UnifiedOrder(XcxPayData inputObj, int timeOut = 6)
353         {
354             string url = "https://api.mch.weixin.qq.com/pay/unifiedorder";
355             //检测必填参数
356             if (!inputObj.IsSet("out_trade_no"))
357             {
358                 throw new WePayException("缺少统一支付接口必填参数out_trade_no!");
359             }
360             else if (!inputObj.IsSet("body"))
361             {
362                 throw new WePayException("缺少统一支付接口必填参数body!");
363             }
364             else if (!inputObj.IsSet("total_fee"))
365             {
366                 throw new WePayException("缺少统一支付接口必填参数total_fee!");
367             }
368             else if (!inputObj.IsSet("trade_type"))
369             {
370                 throw new WePayException("缺少统一支付接口必填参数trade_type!");
371             }
372 
373             //关联参数
374             if (inputObj.GetValue("trade_type").ToString() == "JSAPI" && !inputObj.IsSet("openid"))
375             {
376                 throw new WePayException("统一支付接口中,缺少必填参数openid!trade_type为JSAPI时,openid为必填参数!");
377             }
378             if (inputObj.GetValue("trade_type").ToString() == "NATIVE" && !inputObj.IsSet("product_id"))
379             {
380                 throw new WePayException("统一支付接口中,缺少必填参数product_id!trade_type为JSAPI时,product_id为必填参数!");
381             }
382 
383             //异步通知url未设置,则使用配置文件中的url
384             if (!inputObj.IsSet("notify_url"))
385             {
386                 inputObj.SetValue("notify_url", XcxPayConfig.NOTIFY_URL);//异步通知url
387             }
388 
389             inputObj.SetValue("appid", XcxPayConfig.APPID);//公众账号ID
390             inputObj.SetValue("mch_id", XcxPayConfig.MCHID);//商户号
391             inputObj.SetValue("spbill_create_ip", WePayConfig.IP);//终端ip              
392             inputObj.SetValue("nonce_str", GenerateNonceStr());//随机字符串
393 
394             //签名
395             inputObj.SetValue("sign", inputObj.MakeSign());
396             string xml = inputObj.ToXml();
397 
398             var start = DateTime.Now;
399 
400             Log.Info("XcxPayApi", "UnfiedOrder request : " + xml);
401             string response = XcxPayHttpService.Post(xml, url, false, timeOut);
402             Log.Info("XcxPayApi", "UnfiedOrder response : " + response);
403 
404             var end = DateTime.Now;
405             int timeCost = (int)((end - start).TotalMilliseconds);
406 
407             XcxPayData result = new XcxPayData();
408             result.FromXml(response);
409 
410             ReportCostTime(url, timeCost, result);//测速上报
411 
412             return result;
413         }
414 
415         /**
416         * 
417         * 统一下单
418         * @param WxPaydata inputObj 提交给统一下单API的参数
419         * @param int timeOut 超时时间
420         * @throws WePayException
421         * @return 成功时返回,其他抛异常
422         */
423         public static XcxPayData UnifiedOrderApp(XcxPayData inputObj, int timeOut = 6)
424         {
425             string url = "https://api.mch.weixin.qq.com/pay/unifiedorder";
426             //检测必填参数
427             if (!inputObj.IsSet("out_trade_no"))
428             {
429                 throw new WePayException("缺少统一支付接口必填参数out_trade_no!");
430             }
431             else if (!inputObj.IsSet("body"))
432             {
433                 throw new WePayException("缺少统一支付接口必填参数body!");
434             }
435             else if (!inputObj.IsSet("total_fee"))
436             {
437                 throw new WePayException("缺少统一支付接口必填参数total_fee!");
438             }
439             else if (!inputObj.IsSet("trade_type"))
440             {
441                 throw new WePayException("缺少统一支付接口必填参数trade_type!");
442             }
443 
444             //关联参数
445             if (inputObj.GetValue("trade_type").ToString() == "JSAPI" && !inputObj.IsSet("openid"))
446             {
447                 throw new WePayException("统一支付接口中,缺少必填参数openid!trade_type为JSAPI时,openid为必填参数!");
448             }
449             if (inputObj.GetValue("trade_type").ToString() == "NATIVE" && !inputObj.IsSet("product_id"))
450             {
451                 throw new WePayException("统一支付接口中,缺少必填参数product_id!trade_type为JSAPI时,product_id为必填参数!");
452             }
453 
454             //异步通知url未设置,则使用配置文件中的url
455             if (!inputObj.IsSet("notify_url"))
456             {
457                 inputObj.SetValue("notify_url", XcxPayConfig.NOTIFY_URL);//异步通知url
458             }
459 
460             inputObj.SetValue("appid", XcxPayConfig.APPID);//公众账号ID
461             inputObj.SetValue("mch_id", XcxPayConfig.MCHID);//商户号
462             inputObj.SetValue("spbill_create_ip", WePayConfig.IP);//终端ip              
463             inputObj.SetValue("nonce_str", GenerateNonceStr());//随机字符串
464 
465             //签名
466             inputObj.SetValue("sign", inputObj.MakeSign());
467             string xml = inputObj.ToXml();
468 
469             var start = DateTime.Now;
470 
471             Log.Info("XcxPayApi", "UnfiedOrder request : " + xml);
472             string response = XcxPayHttpService.Post(xml, url, false, timeOut);
473             Log.Info("XcxPayApi", "UnfiedOrder response : " + response);
474 
475             var end = DateTime.Now;
476             int timeCost = (int)((end - start).TotalMilliseconds);
477 
478             XcxPayData result = new XcxPayData();
479             result.FromXml(response);
480 
481             ReportCostTime(url, timeCost, result);//测速上报
482 
483             return result;
484         }
485 
486 
487         /**
488         * 
489         * 关闭订单
490         * @param WxPayData inputObj 提交给关闭订单API的参数
491         * @param int timeOut 接口超时时间
492         * @throws WePayException
493         * @return 成功时返回,其他抛异常
494         */
495         public static XcxPayData CloseOrder(XcxPayData inputObj, int timeOut = 6)
496         {
497             string url = "https://api.mch.weixin.qq.com/pay/closeorder";
498             //检测必填参数
499             if (!inputObj.IsSet("out_trade_no"))
500             {
501                 throw new WePayException("关闭订单接口中,out_trade_no必填!");
502             }
503 
504             inputObj.SetValue("appid", XcxPayConfig.APPID);//公众账号ID
505             inputObj.SetValue("mch_id", XcxPayConfig.MCHID);//商户号
506             inputObj.SetValue("nonce_str", GenerateNonceStr());//随机字符串        
507             inputObj.SetValue("sign", inputObj.MakeSign());//签名
508             string xml = inputObj.ToXml();
509 
510             var start = DateTime.Now;//请求开始时间
511 
512             string response = XcxPayHttpService.Post(xml, url, false, timeOut);
513 
514             var end = DateTime.Now;
515             int timeCost = (int)((end - start).TotalMilliseconds);
516 
517             XcxPayData result = new XcxPayData();
518             result.FromXml(response);
519 
520             ReportCostTime(url, timeCost, result);//测速上报
521 
522             return result;
523         }
524 
525 
526         /**
527         * 
528         * 测速上报
529         * @param string interface_url 接口URL
530         * @param int timeCost 接口耗时
531         * @param WxPayData inputObj参数数组
532         */
533         private static void ReportCostTime(string interface_url, int timeCost, XcxPayData inputObj)
534         {
535             //如果不需要进行上报
536             if (WePayConfig.REPORT_LEVENL == 0)
537             {
538                 return;
539             }
540 
541             //如果仅失败上报
542             if (WePayConfig.REPORT_LEVENL == 1 && inputObj.IsSet("return_code") && inputObj.GetValue("return_code").ToString() == "SUCCESS" &&
543              inputObj.IsSet("result_code") && inputObj.GetValue("result_code").ToString() == "SUCCESS")
544             {
545                 return;
546             }
547 
548             //上报逻辑
549             XcxPayData data = new XcxPayData();
550             data.SetValue("interface_url", interface_url);
551             data.SetValue("execute_time_", timeCost);
552             //返回状态码
553             if (inputObj.IsSet("return_code"))
554             {
555                 data.SetValue("return_code", inputObj.GetValue("return_code"));
556             }
557             //返回信息
558             if (inputObj.IsSet("return_msg"))
559             {
560                 data.SetValue("return_msg", inputObj.GetValue("return_msg"));
561             }
562             //业务结果
563             if (inputObj.IsSet("result_code"))
564             {
565                 data.SetValue("result_code", inputObj.GetValue("result_code"));
566             }
567             //错误代码
568             if (inputObj.IsSet("err_code"))
569             {
570                 data.SetValue("err_code", inputObj.GetValue("err_code"));
571             }
572             //错误代码描述
573             if (inputObj.IsSet("err_code_des"))
574             {
575                 data.SetValue("err_code_des", inputObj.GetValue("err_code_des"));
576             }
577             //商户订单号
578             if (inputObj.IsSet("out_trade_no"))
579             {
580                 data.SetValue("out_trade_no", inputObj.GetValue("out_trade_no"));
581             }
582             //设备号
583             if (inputObj.IsSet("device_info"))
584             {
585                 data.SetValue("device_info", inputObj.GetValue("device_info"));
586             }
587 
588             try
589             {
590                 Report(data);
591             }
592             catch (WePayException ex)
593             {
594                 //不做任何处理
595             }
596         }
597 
598 
599         /**
600         * 
601         * 测速上报接口实现
602         * @param WxPayData inputObj 提交给测速上报接口的参数
603         * @param int timeOut 测速上报接口超时时间
604         * @throws WePayException
605         * @return 成功时返回测速上报接口返回的结果,其他抛异常
606         */
607         public static XcxPayData Report(XcxPayData inputObj, int timeOut = 1)
608         {
609             string url = "https://api.mch.weixin.qq.com/payitil/report";
610             //检测必填参数
611             if (!inputObj.IsSet("interface_url"))
612             {
613                 throw new WePayException("接口URL,缺少必填参数interface_url!");
614             }
615             if (!inputObj.IsSet("return_code"))
616             {
617                 throw new WePayException("返回状态码,缺少必填参数return_code!");
618             }
619             if (!inputObj.IsSet("result_code"))
620             {
621                 throw new WePayException("业务结果,缺少必填参数result_code!");
622             }
623             if (!inputObj.IsSet("user_ip"))
624             {
625                 throw new WePayException("访问接口IP,缺少必填参数user_ip!");
626             }
627             if (!inputObj.IsSet("execute_time_"))
628             {
629                 throw new WePayException("接口耗时,缺少必填参数execute_time_!");
630             }
631 
632             inputObj.SetValue("appid", XcxPayConfig.APPID);//公众账号ID
633             inputObj.SetValue("mch_id", XcxPayConfig.MCHID);//商户号
634             inputObj.SetValue("user_ip", WePayConfig.IP);//终端ip
635             inputObj.SetValue("time", DateTime.Now.ToString("yyyyMMddHHmmss"));//商户上报时间     
636             inputObj.SetValue("nonce_str", GenerateNonceStr());//随机字符串
637             inputObj.SetValue("sign", inputObj.MakeSign());//签名
638             string xml = inputObj.ToXml();
639 
640             Log.Info("XcxPayApi", "Report request : " + xml);
641 
642             string response = XcxPayHttpService.Post(xml, url, false, timeOut);
643 
644             Log.Info("XcxPayApi", "Report response : " + response);
645 
646             XcxPayData result = new XcxPayData();
647             result.FromXml(response);
648             return result;
649         }
650 
651         /**
652         * 根据当前系统时间加随机序列来生成订单号
653          * @return 订单号
654         */
655         public static string GenerateOutTradeNo()
656         {
657             var ran = new Random();
658             return string.Format("{0}{1}{2}", XcxPayConfig.MCHID, DateTime.Now.ToString("yyyyMMddHHmmss"), ran.Next(999));
659         }
660 
661         /**
662         * 生成时间戳,标准北京时间,时区为东八区,自1970年1月1日 0点0分0秒以来的秒数
663          * @return 时间戳
664         */
665         public static string GenerateTimeStamp()
666         {
667             TimeSpan ts = DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, 0);
668             return Convert.ToInt64(ts.TotalSeconds).ToString();
669         }
670 
671         /**
672         * 生成随机串,随机串包含字母或数字
673         * @return 随机串
674         */
675         public static string GenerateNonceStr()
676         {
677             return Guid.NewGuid().ToString().Replace("-", "");
678         }
679     }
680 }
View Code

新建XcxPayData类

  1 using LitJson;
  2 using System;
  3 using System.Collections.Generic;
  4 using System.Linq;
  5 using System.Security.Cryptography;
  6 using System.Text;
  7 using System.Threading.Tasks;
  8 using System.Xml;
  9 
 10 namespace App.Pay.WePay.XcxPay
 11 {
 12     /// <summary>
 13     /// 微信支付协议接口数据类,所有的API接口通信都依赖这个数据结构,
 14     /// 在调用接口之前先填充各个字段的值,然后进行接口通信,
 15     /// 这样设计的好处是可扩展性强,用户可随意对协议进行更改而不用重新设计数据结构,
 16     /// 还可以随意组合出不同的协议数据包,不用为每个协议设计一个数据包结构
 17     /// </summary>
 18     public class XcxPayData
 19     {
 20         private Log Log = new Log(XcxPayConfig.LogPath);
 21 
 22         public XcxPayData()
 23         {
 24         }
 25 
 26         //采用排序的Dictionary的好处是方便对数据包进行签名,不用再签名之前再做一次排序
 27         private SortedDictionary<string, object> m_values = new SortedDictionary<string, object>();
 28 
 29         /**
 30         * 设置某个字段的值
 31         * @param key 字段名
 32          * @param value 字段值
 33         */
 34         public void SetValue(string key, object value)
 35         {
 36             m_values[key] = value;
 37         }
 38 
 39         /**
 40         * 根据字段名获取某个字段的值
 41         * @param key 字段名
 42          * @return key对应的字段值
 43         */
 44         public object GetValue(string key)
 45         {
 46             object o = null;
 47             m_values.TryGetValue(key, out o);
 48             return o;
 49         }
 50 
 51         /**
 52          * 判断某个字段是否已设置
 53          * @param key 字段名
 54          * @return 若字段key已被设置,则返回true,否则返回false
 55          */
 56         public bool IsSet(string key)
 57         {
 58             object o = null;
 59             m_values.TryGetValue(key, out o);
 60             if (null != o)
 61                 return true;
 62             else
 63                 return false;
 64         }
 65 
 66         /**
 67         * @将Dictionary转成xml
 68         * @return 经转换得到的xml串
 69         * @throws WePayException
 70         **/
 71         public string ToXml()
 72         {
 73             //数据为空时不能转化为xml格式
 74             if (0 == m_values.Count)
 75             {
 76                 Log.Error(this.GetType().ToString(), "WxPayData数据为空!");
 77                 throw new WePayException("WxPayData数据为空!");
 78             }
 79 
 80             string xml = "<xml>";
 81             foreach (KeyValuePair<string, object> pair in m_values)
 82             {
 83                 //字段值不能为null,会影响后续流程
 84                 if (pair.Value == null)
 85                 {
 86                     Log.Error(this.GetType().ToString(), "WxPayData内部含有值为null的字段!");
 87                     throw new WePayException("WxPayData内部含有值为null的字段!");
 88                 }
 89 
 90                 if (pair.Value.GetType() == typeof(int))
 91                 {
 92                     xml += "<" + pair.Key + ">" + pair.Value + "</" + pair.Key + ">";
 93                 }
 94                 else if (pair.Value.GetType() == typeof(string))
 95                 {
 96                     xml += "<" + pair.Key + ">" + "<![CDATA[" + pair.Value + "]]></" + pair.Key + ">";
 97                 }
 98                 else//除了string和int类型不能含有其他数据类型
 99                 {
100                     Log.Error(this.GetType().ToString(), "WxPayData字段数据类型错误!");
101                     throw new WePayException("WxPayData字段数据类型错误!");
102                 }
103             }
104             xml += "</xml>";
105             return xml;
106         }
107 
108         /**
109         * @将xml转为WxPayData对象并返回对象内部的数据
110         * @param string 待转换的xml串
111         * @return 经转换得到的Dictionary
112         * @throws WePayException
113         */
114         public SortedDictionary<string, object> FromXml(string xml)
115         {
116             if (string.IsNullOrEmpty(xml))
117             {
118                 Log.Error(this.GetType().ToString(), "将空的xml串转换为WxPayData不合法!");
119                 throw new WePayException("将空的xml串转换为WxPayData不合法!");
120             }
121 
122             SafeXmlDocument xmlDoc = new SafeXmlDocument();
123             xmlDoc.LoadXml(xml);
124             XmlNode xmlNode = xmlDoc.FirstChild;//获取到根节点<xml>
125             XmlNodeList nodes = xmlNode.ChildNodes;
126             foreach (XmlNode xn in nodes)
127             {
128                 XmlElement xe = (XmlElement)xn;
129                 m_values[xe.Name] = xe.InnerText;//获取xml的键值对到WxPayData内部的数据中
130             }
131 
132             try
133             {
134                 //2015-06-29 错误是没有签名
135                 if (m_values["return_code"] != "SUCCESS")
136                 {
137                     return m_values;
138                 }
139                 CheckSign();//验证签名,不通过会抛异常
140             }
141             catch (WePayException ex)
142             {
143                 throw new WePayException(ex.Message);
144             }
145 
146             return m_values;
147         }
148 
149         /**
150         * @Dictionary格式转化成url参数格式
151         * @ return url格式串, 该串不包含sign字段值
152         */
153         public string ToUrl()
154         {
155             string buff = "";
156             foreach (KeyValuePair<string, object> pair in m_values)
157             {
158                 if (pair.Value == null)
159                 {
160                     Log.Error(this.GetType().ToString(), "WxPayData内部含有值为null的字段!");
161                     throw new WePayException("WxPayData内部含有值为null的字段!");
162                 }
163 
164                 if (pair.Key != "sign" && pair.Value.ToString() != "")
165                 {
166                     buff += pair.Key + "=" + pair.Value + "&";
167                 }
168             }
169             buff = buff.Trim('&');
170             return buff;
171         }
172 
173 
174         /**
175         * @Dictionary格式化成Json
176          * @return json串数据
177         */
178         public string ToJson()
179         {
180             string jsonStr = JsonMapper.ToJson(m_values);
181             return jsonStr;
182         }
183 
184         /**
185         * @values格式化成能在Web页面上显示的结果(因为web页面上不能直接输出xml格式的字符串)
186         */
187         public string ToPrintStr()
188         {
189             string str = "";
190             foreach (KeyValuePair<string, object> pair in m_values)
191             {
192                 if (pair.Value == null)
193                 {
194                     Log.Error(this.GetType().ToString(), "WxPayData内部含有值为null的字段!");
195                     throw new WePayException("WxPayData内部含有值为null的字段!");
196                 }
197 
198                 str += string.Format("{0}={1}<br>", pair.Key, pair.Value.ToString());
199             }
200             Log.Info(this.GetType().ToString(), "Print in Web Page : " + str);
201             return str;
202         }
203 
204         /**
205         * @生成签名,详见签名生成算法
206         * @return 签名, sign字段不参加签名
207         */
208         public string MakeSign()
209         {
210             //转url格式
211             string str = ToUrl();
212             //在string后加入API KEY
213             str += "&key=" + XcxPayConfig.KEY;
214             //MD5加密
215             var md5 = MD5.Create();
216             var bs = md5.ComputeHash(Encoding.UTF8.GetBytes(str));
217             var sb = new StringBuilder();
218             foreach (byte b in bs)
219             {
220                 sb.Append(b.ToString("x2"));
221             }
222             //所有字符转为大写
223             return sb.ToString().ToUpper();
224         }
225 
226         /**
227         * 
228         * 检测签名是否正确
229         * 正确返回true,错误抛异常
230         */
231         public bool CheckSign()
232         {
233             //如果没有设置签名,则跳过检测
234             if (!IsSet("sign"))
235             {
236                 Log.Error(this.GetType().ToString(), "WxPayData签名存在但不合法!");
237                 throw new WePayException("WxPayData签名存在但不合法!");
238             }
239             //如果设置了签名但是签名为空,则抛异常
240             else if (GetValue("sign") == null || GetValue("sign").ToString() == "")
241             {
242                 Log.Error(this.GetType().ToString(), "WxPayData签名存在但不合法!");
243                 throw new WePayException("WxPayData签名存在但不合法!");
244             }
245 
246             //获取接收到的签名
247             string return_sign = GetValue("sign").ToString();
248 
249             //在本地计算新的签名
250             string cal_sign = MakeSign();
251 
252             if (cal_sign == return_sign)
253             {
254                 return true;
255             }
256 
257             Log.Error(this.GetType().ToString(), "WxPayData签名验证错误!");
258             throw new WePayException("WxPayData签名验证错误!");
259         }
260 
261         /**
262         * @获取Dictionary
263         */
264         public SortedDictionary<string, object> GetValues()
265         {
266             return m_values;
267         }
268     }
269 }
View Code

新建XcxPayHttpService类,封装了POST请求和Get请求,在这里,我们只使用了POST请求。

  1 using System;
  2 using System.Collections.Generic;
  3 using System.IO;
  4 using System.Linq;
  5 using System.Net;
  6 using System.Net.Security;
  7 using System.Security.Cryptography.X509Certificates;
  8 using System.Text;
  9 using System.Threading.Tasks;
 10 using System.Web;
 11 
 12 namespace App.Pay.WePay.XcxPay
 13 {
 14     public class XcxPayHttpService
 15     {
 16         private static Log Log = new Log(XcxPayConfig.LogPath);
 17 
 18         public static bool CheckValidationResult(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors errors)
 19         {
 20             //直接确认,否则打不开    
 21             return true;
 22         }
 23 
 24         public static string Post(string xml, string url, bool isUseCert, int timeout)
 25         {
 26             System.GC.Collect();//垃圾回收,回收没有正常关闭的http连接
 27 
 28             string result = "";//返回结果
 29 
 30             HttpWebRequest request = null;
 31             HttpWebResponse response = null;
 32             Stream reqStream = null;
 33 
 34             try
 35             {
 36                 //设置最大连接数
 37                 ServicePointManager.DefaultConnectionLimit = 200;
 38                 //设置https验证方式
 39                 if (url.StartsWith("https", StringComparison.OrdinalIgnoreCase))
 40                 {
 41                     ServicePointManager.ServerCertificateValidationCallback =
 42                             new RemoteCertificateValidationCallback(CheckValidationResult);
 43                 }
 44 
 45                 /***************************************************************
 46                 * 下面设置HttpWebRequest的相关属性
 47                 * ************************************************************/
 48                 request = (HttpWebRequest)WebRequest.Create(url);
 49 
 50                 request.Method = "POST";
 51                 request.Timeout = timeout * 1000;
 52 
 53                 //设置代理服务器
 54                 //WebProxy proxy = new WebProxy();                          //定义一个网关对象
 55                 //proxy.Address = new Uri(WxPayConfig.PROXY_URL);              //网关服务器端口:端口
 56                 //request.Proxy = proxy;
 57 
 58                 //设置POST的数据类型和长度
 59                 request.ContentType = "text/xml";
 60                 byte[] data = System.Text.Encoding.UTF8.GetBytes(xml);
 61                 request.ContentLength = data.Length;
 62 
 63                 //是否使用证书
 64                 if (isUseCert)
 65                 {
 66                     string path = HttpContext.Current.Request.PhysicalApplicationPath;
 67                     X509Certificate2 cert = new X509Certificate2(path + XcxPayConfig.SSLCERT_PATH, XcxPayConfig.SSLCERT_PASSWORD);
 68                     request.ClientCertificates.Add(cert);
 69                     Log.Info("XcxPayHttpService", "PostXml used cert");
 70                 }
 71 
 72                 //往服务器写入数据
 73                 reqStream = request.GetRequestStream();
 74                 reqStream.Write(data, 0, data.Length);
 75                 reqStream.Close();
 76 
 77                 //获取服务端返回
 78                 response = (HttpWebResponse)request.GetResponse();
 79 
 80                 //获取服务端返回数据
 81                 StreamReader sr = new StreamReader(response.GetResponseStream(), Encoding.UTF8);
 82                 result = sr.ReadToEnd().Trim();
 83                 sr.Close();
 84             }
 85             catch (System.Threading.ThreadAbortException e)
 86             {
 87                 Log.Error("XcxPayHttpService", "Thread - caught ThreadAbortException - resetting.");
 88                 Log.Error("Exception message: {0}", e.Message);
 89                 System.Threading.Thread.ResetAbort();
 90             }
 91             catch (WebException e)
 92             {
 93                 Log.Error("XcxPayHttpService", e.ToString());
 94                 if (e.Status == WebExceptionStatus.ProtocolError)
 95                 {
 96                     Log.Error("XcxPayHttpService", "StatusCode : " + ((HttpWebResponse)e.Response).StatusCode);
 97                     Log.Error("XcxPayHttpService", "StatusDescription : " + ((HttpWebResponse)e.Response).StatusDescription);
 98                 }
 99                 throw new WePayException(e.ToString());
100             }
101             catch (Exception e)
102             {
103                 Log.Error("XcxPayHttpService", e.ToString());
104                 throw new WePayException(e.ToString());
105             }
106             finally
107             {
108                 //关闭连接和流
109                 if (response != null)
110                 {
111                     response.Close();
112                 }
113                 if (request != null)
114                 {
115                     request.Abort();
116                 }
117             }
118             return result;
119         }
120 
121         /// <summary>
122         /// 处理http GET请求,返回数据
123         /// </summary>
124         /// <param name="url">请求的url地址</param>
125         /// <returns>http GET成功后返回的数据,失败抛WebException异常</returns>
126         public static string Get(string url)
127         {
128             System.GC.Collect();
129             string result = "";
130 
131             HttpWebRequest request = null;
132             HttpWebResponse response = null;
133 
134             //请求url以获取数据
135             try
136             {
137                 //设置最大连接数
138                 ServicePointManager.DefaultConnectionLimit = 200;
139                 //设置https验证方式
140                 if (url.StartsWith("https", StringComparison.OrdinalIgnoreCase))
141                 {
142                     ServicePointManager.ServerCertificateValidationCallback =
143                             new RemoteCertificateValidationCallback(CheckValidationResult);
144                 }
145 
146                 /***************************************************************
147                 * 下面设置HttpWebRequest的相关属性
148                 * ************************************************************/
149                 request = (HttpWebRequest)WebRequest.Create(url);
150 
151                 request.Method = "GET";
152 
153                 //设置代理
154                 //WebProxy proxy = new WebProxy();
155                 //proxy.Address = new Uri(WxPayConfig.PROXY_URL);
156                 //request.Proxy = proxy;
157 
158                 //获取服务器返回
159                 response = (HttpWebResponse)request.GetResponse();
160 
161                 //获取HTTP返回数据
162                 StreamReader sr = new StreamReader(response.GetResponseStream(), Encoding.UTF8);
163                 result = sr.ReadToEnd().Trim();
164                 sr.Close();
165             }
166             catch (System.Threading.ThreadAbortException e)
167             {
168                 Log.Error("XcxPayHttpService", "Thread - caught ThreadAbortException - resetting.");
169                 Log.Error("Exception message: {0}", e.Message);
170                 System.Threading.Thread.ResetAbort();
171             }
172             catch (WebException e)
173             {
174                 Log.Error("XcxPayHttpService", e.ToString());
175                 if (e.Status == WebExceptionStatus.ProtocolError)
176                 {
177                     Log.Error("XcxPayHttpService", "StatusCode : " + ((HttpWebResponse)e.Response).StatusCode);
178                     Log.Error("XcxPayHttpService", "StatusDescription : " + ((HttpWebResponse)e.Response).StatusDescription);
179                 }
180                 throw new WePayException(e.ToString());
181             }
182             catch (Exception e)
183             {
184                 Log.Error("XcxPayHttpService", e.ToString());
185                 throw new WePayException(e.ToString());
186             }
187             finally
188             {
189                 //关闭连接和流
190                 if (response != null)
191                 {
192                     response.Close();
193                 }
194                 if (request != null)
195                 {
196                     request.Abort();
197                 }
198             }
199             return result;
200         }
201     }
202 }
View Code

新建XcxPayNotify类,回调处理基类,负责接收微信支付后台发送过来的数据,并对数据进行签名验证。

 1 using System;
 2 using System.Collections.Generic;
 3 using System.Linq;
 4 using System.Text;
 5 using System.Threading.Tasks;
 6 using System.Web;
 7 
 8 namespace App.Pay.WePay.XcxPay
 9 {
10     /// <summary>
11     /// 回调处理基类
12     /// 主要负责接收微信支付后台发送过来的数据,对数据进行签名验证
13     /// 子类在此类基础上进行派生并重写自己的回调处理过程
14     /// </summary>
15     public class XcxPayNotify
16     {
17         public HttpContext context { get; set; }
18 
19         public Log Log = new Log(XcxPayConfig.LogPath);
20 
21         public XcxPayNotify(HttpContext context)
22         {
23             this.context = context;
24         }
25 
26         /// <summary>
27         /// 接收从微信支付后台发送过来的数据并验证签名
28         /// </summary>
29         /// <returns>微信支付后台返回的数据</returns>
30         public XcxPayData GetNotifyData()
31         {
32             //接收从微信后台POST过来的数据
33             System.IO.Stream s = context.Request.InputStream;
34             int count = 0;
35             byte[] buffer = new byte[1024];
36             StringBuilder builder = new StringBuilder();
37             while ((count = s.Read(buffer, 0, 1024)) > 0)
38             {
39                 builder.Append(Encoding.UTF8.GetString(buffer, 0, count));
40             }
41             s.Flush();
42             s.Close();
43             s.Dispose();
44 
45             //转换数据格式并验证签名
46             XcxPayData data = new XcxPayData();
47             try
48             {
49                 data.FromXml(builder.ToString());
50             }
51             catch (WePayException ex)
52             {
53                 //若签名错误,则立即返回结果给微信支付后台
54                 XcxPayData res = new XcxPayData();
55                 res.SetValue("return_code", "FAIL");
56                 res.SetValue("return_msg", ex.Message);
57                 Log.Error(this.GetType().ToString(), "Sign check error : " + res.ToXml());
58                 context.Response.Write(res.ToXml());
59                 context.Response.End();
60             }
61 
62             Log.Info(this.GetType().ToString(), "Check sign success");
63             return data;
64         }
65 
66         //派生类需要重写这个方法,进行不同的回调处理
67         public virtual void ProcessNotify()
68         {
69 
70         }
71     }
72 }
View Code

 至此,小程序支付的架子我们已经搭建好了,接下来,就是在我们的业务中去使用这个架子。

  1 using App.Pay.WePay;
  2 using App.Pay.WePay.XcxPay;
  3 using System;
  4 using System.Collections.Generic;
  5 using System.Linq;
  6 using System.Web;
  7 using System.Web.Configuration;
  8 using System.Web.Mvc;
  9 
 10 namespace App.WebTest.Controllers
 11 {
 12     /// <summary>
 13     /// 微信小程序支付
 14     /// </summary>
 15     public class WeXcxPayController : BaseController
 16     {
 17         /// <summary>
 18         /// 小程序下单
 19         /// </summary>
 20         /// <param name="oIds">订单Id</param>
 21         /// <param name="code">临时登录凭证</param>
 22         /// <returns></returns>
 23         public ActionResult WeXcxPay(int[] oIds, string code)
 24         {
 25             #region 验证订单是否有效,并合计价格
 26 
 27             //订单价格
 28             decimal payPrice = 0;
 29 
 30             //订单描述
 31             string detail = "";
 32 
 33             //验证订单.....
 34 
 35 
 36             #endregion
 37 
 38             #region 统一下单
 39 
 40             try
 41             {
 42                 //支付回调通知地址
 43                 var address = WebConfigurationManager.AppSettings["WxXcxNotifyUrl"].ToString();
 44                 XcxPayData data = new XcxPayData();
 45                 data.SetValue("body", "商品购买");
 46 
 47                 //可以将用户Id和订单Id同时封装在attach中
 48                 data.SetValue("attach", String.Join(",", oIds).ToString());
 49                 Random rd = new Random();
 50 
 51                 //外部商户订单号
 52                 var payNum = DateTime.Now.ToString("yyyyMMddHHmmss") + rd.Next(0, 1000).ToString().PadLeft(3, '0');
 53                 data.SetValue("out_trade_no", payNum);
 54                 data.SetValue("detail", detail.Substring(0, detail.Length - 1));
 55                 data.SetValue("total_fee", Convert.ToInt32(payPrice * 100));
 56                 data.SetValue("time_start", DateTime.Now.ToString("yyyyMMddHHmmss"));
 57                 data.SetValue("time_expire", DateTime.Now.AddMinutes(10).ToString("yyyyMMddHHmmss"));
 58                 data.SetValue("notify_url", address);
 59                 //data.SetValue("goods_tag", "test");
 60                 data.SetValue("trade_type", "JSAPI");
 61                 data.SetValue("openid", WeHelper.Code2Session(code).openid);
 62 
 63                 XcxPayData result = XcxPayApi.UnifiedOrder(data);
 64                 var flag = true;
 65                 var msg = "";
 66                 var nonceStr = "";
 67                 var appId = "";
 68                 var package = "";
 69                 var mch_id = "";
 70                 if (!result.IsSet("appid") || !result.IsSet("prepay_id") || result.GetValue("prepay_id").ToString() == "")
 71                 {
 72                     flag = false;
 73                     msg = "下单失败";
 74                     return Json(new { Result = false, Msg = "下单失败!" });
 75                 }
 76                 else
 77                 {
 78                     //统一下单
 79 
 80                     ///TO Do......
 81                     /// 修改订单状态
 82 
 83                     nonceStr = result.GetValue("nonce_str").ToString();
 84                     appId = result.GetValue("appid").ToString();
 85                     mch_id = result.GetValue("mch_id").ToString();
 86                     package = "prepay_id=" + result.GetValue("prepay_id").ToString();
 87                 }
 88                 var signType = "MD5";
 89                 var timeStamp = ((DateTime.Now.Ticks - TimeZone.CurrentTimeZone.ToLocalTime(new DateTime(1970, 1, 1)).Ticks) / 10000).ToString();
 90                 XcxPayData applet = new XcxPayData();
 91                 applet.SetValue("appId", appId);
 92                 applet.SetValue("nonceStr", nonceStr);
 93                 applet.SetValue("package", package);
 94                 applet.SetValue("signType", signType);
 95                 applet.SetValue("timeStamp", timeStamp);
 96                 var appletSign = applet.MakeSign();
 97                 return Json(new { timeStamp, nonceStr, package, signType, paySign = appletSign, Result = flag, msg });
 98             }
 99             catch (Exception ex)
100             {
101                 return Json(new { Result = false, msg = "缺少参数" });
102             }
103             #endregion
104         }
105 
106         /// <summary>
107         /// 微信小程序支付回调通知
108         /// </summary>
109         /// <returns></returns>
110         public void WeXcxNotifyUrl()
111         {
112             Pay.Log Log = new Pay.Log(XcxPayConfig.LogPath);
113             Log.Info("WxXcxNotifyUrl", "支付回调");
114             XcxPayNotify notify = new XcxPayNotify(System.Web.HttpContext.Current);
115             XcxPayData notifyData = notify.GetNotifyData();
116 
117             //检查支付结果中transaction_id是否存在
118             if (!notifyData.IsSet("transaction_id"))
119             {
120                 //若transaction_id不存在,则立即返回结果给微信支付后台
121                 XcxPayData res = new XcxPayData();
122                 res.SetValue("return_code", "FAIL");
123                 res.SetValue("return_msg", "支付结果中微信订单号不存在");
124                 Log.Error(this.GetType().ToString(), "The Pay result is error : " + res.ToXml());
125                 Response.Write(res.ToXml());
126                 Response.End();
127             }
128 
129             string transaction_id = notifyData.GetValue("transaction_id").ToString();
130 
131             //查询订单,判断订单真实性
132             if (!XcxQueryOrder(transaction_id))
133             {
134                 //若订单查询失败,则立即返回结果给微信支付后台
135                 XcxPayData res = new XcxPayData();
136                 res.SetValue("return_code", "FAIL");
137                 res.SetValue("return_msg", "订单查询失败");
138                 Log.Error(this.GetType().ToString(), "Order query failure : " + res.ToXml());
139 
140                 Response.Write(res.ToXml());
141                 Response.End();
142             }
143             //查询订单成功
144             else
145             {
146                 XcxPayData res = new XcxPayData();
147                 res.SetValue("return_code", "SUCCESS");
148                 res.SetValue("return_msg", "OK");
149                 Log.Info(this.GetType().ToString(), "Order query success : " + res.ToXml());
150                 Log.Info(this.GetType().ToString(), "Order query success,notifyData : " + notifyData.ToXml());
151                 var returnCode = notifyData.GetValue("return_code").ToString();
152                 var transactionNo = transaction_id;//微信订单号
153                 var outTradeNo = notifyData.GetValue("out_trade_no").ToString();//自定义订单号
154                 var attach = notifyData.GetValue("attach").ToString();//身份证
155                 var endTime = notifyData.GetValue("time_end").ToString();//交易结束时间
156                 //var body = notifyData.GetValue("body").ToString();//projectIdlist
157                 var totalFee = notifyData.GetValue("total_fee").ToString(); ;//支付金额
158 
159                 int userId = Convert.ToInt32(attach.Split('|')[0]);
160                 string msg;
161                 try
162                 {
163                     //var result = OrderBll.Value.CompleteWePay(userId, totalFee, transactionNo, returnCode, outTradeNo, attach, endTime, out msg);
164 
165                     var result = true;
166 
167                     Log.Info(this.GetType().ToString(), "CompleteWePay:" + result);
168                 }
169                 catch (Exception e)
170                 {
171                     Log.Error(this.GetType().ToString(), "CompleteWePay:" + e.ToString());
172                 }
173 
174                 Response.Write(res.ToXml());
175                 Response.End();
176             }
177         }
178 
179         /// <summary>
180         /// 查询订单
181         /// </summary>
182         /// <param name="transaction_id">微信交易订单号</param>
183         /// <returns></returns>
184         private bool XcxQueryOrder(string transaction_id)
185         {
186             XcxPayData req = new XcxPayData();
187             req.SetValue("transaction_id", transaction_id);
188             XcxPayData res = XcxPayApi.OrderQuery(req);
189             if (res.GetValue("return_code").ToString() == "SUCCESS" && res.GetValue("result_code").ToString() == "SUCCESS")
190             {
191                 return true;
192             }
193             else
194             {
195                 return false;
196             }
197         }
198     }
199 }
View Code

注意:扩展一个对象反序列化的方法(WeHelper类中将code转化为Session用到),如果不想添加扩展,也可以直接引用JsonConvert包的DeserializeObject反序列化方法即可。

 1 public static class Serialize
 2     {
 3         public static string ToJson(this object obj)
 4         {
 5             return JsonConvert.SerializeObject(obj);
 6         }
 7 
 8         public static T JsonTo<T>(this string obj)
 9         {
10             return (T)JsonConvert.DeserializeObject(obj, typeof(T));
11         }
12     }
View Code

支付完成后,微信会把相关支付信息通知支付回调接口发送给商户,商户在回调接口中接收处理,并返回应答。注意,支付回调接口必须要在外网可以访问到、不能有身份验证(允许匿名访问)、接口无异常,此外如果微信收到商户的应答不是成功或超时,微信会认为通知失败,微信会通过一定的策略定期重新发起通知,尽可能提高通知的成功率(通知频率15/15/30/180/1800/1800/1800/1800/3600,单位:秒)。

源码:https://github.com/wenha/Utility

posted on 2019-01-13 12:37  那么远这么近  阅读(6605)  评论(17编辑  收藏  举报

导航