最近一直再研究微信支付和支付寶支付,官方支付文檔中一直在講與第三方支付打交道的原理,卻沒有介紹咱們本身項目中的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 }
新建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 }
新建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 }
新建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 }
新建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 }
以上四個類是微信支付通用的,所以統一放在了微信支付文件夾下。
新建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 }
<!--小程序支付--> <add key="XcxAppID" value="" /> <add key="XcxAppSecret" value="" /> <add key="XcxMchID" value="" /> <add key="XcxKey" value="" /> <!--回調通知--> <add key="XcxNotifyUrl" value="" />
新建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 }
新建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 }
新建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 }
新建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 }
至此,小程序支付的架子咱們已經搭建好了,接下來,就是在咱們的業務中去使用這個架子。
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 }
注意:擴展一個對象反序列化的方法(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 }
支付完成後,微信會把相關支付信息通知支付回調接口發送給商戶,商戶在回調接口中接收處理,並返回應答。注意,支付回調接口必需要在外網能夠訪問到、不能有身份驗證(容許匿名訪問)、接口無異常,此外若是微信收到商戶的應答不是成功或超時,微信會認爲通知失敗,微信會經過必定的策略按期從新發起通知,儘量提升通知的成功率(通知頻率15/15/30/180/1800/1800/1800/1800/3600,單位:秒)。