.Net後臺實現微信小程序支付

最近一直再研究微信支付和支付寶支付,官方支付文檔中一直在講與第三方支付打交道的原理,卻沒有介紹咱們本身項目中的APP與後臺該怎麼交互(哈哈,人家也不必介紹這一塊)。拜讀了官方文檔和前輩們的佳做,本身在這裏作一些總結。php

不論是微信支付仍是支付寶支付,使用的是小程序、APP或網頁均可以用如下示例圖來講明。node

支付流程:git


  ① 支付端將訂單號(小程序中還須要傳遞登陸憑證)傳遞至後臺商戶。github

  ②後臺驗證訂單、統計訂單總價,請求第三方獲取下單參數。web

  ③第三方返回下單參數。算法

  ④後臺將從第三方返回的參數按須要返回至支付端。json

  ⑤支付端拿着後臺返回的參數下單。小程序

  ⑥第三方返回支付結果。微信小程序

  ⑦支付成功後,第三方發起支付回調通知商戶後臺,在這一步,商戶可在回調中修改訂單以及用戶的相關支付狀態。
api

微信小程序支付:


 先放一張官方的圖

具體實現:

新建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

相關文章
相關標籤/搜索