上一節分享了微信小程序支付的后台,這一節來分享一下微信APP支付的后台。微信APP支付和微信小程序差別不大,微信APP支付后台不需要微信登錄憑證、后台下單時交易類型(trade_type)不再是"JSAPI",而是“APP”、商戶后台傳遞給支付端的下單參數也有所不同。由於微信小程序支付和APP支付使用的APPID不同,索性直接寫了兩套支付,不再在代碼里區分究竟該使用小程序支付的配置參數還是APP支付的參數。
官方是這樣介紹的

具體實現:
在WePay文件夾下新建AppPay文件夾(微信支付的公共類在上一節),用於存放微信APP支付用到的類,新建AppPayConfig類
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 }
新建AppPayHttpService類
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.AppPay 13 { 14 public class AppPayHttpService 15 { 16 private static Log Log = new Log(AppPayConfig.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 + AppPayConfig.SSLCERT_PATH, AppPayConfig.SSLCERT_PASSWORD); 68 request.ClientCertificates.Add(cert); 69 //Log.Debug("WxPayApi", "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("HttpService", "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("HttpService", e.ToString()); 94 if (e.Status == WebExceptionStatus.ProtocolError) 95 { 96 Log.Error("HttpService", "StatusCode : " + ((HttpWebResponse)e.Response).StatusCode); 97 Log.Error("HttpService", "StatusDescription : " + ((HttpWebResponse)e.Response).StatusDescription); 98 } 99 throw new WePayException(e.ToString()); 100 } 101 catch (Exception e) 102 { 103 Log.Error("HttpService", 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("HttpService", "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("HttpService", e.ToString()); 175 if (e.Status == WebExceptionStatus.ProtocolError) 176 { 177 Log.Error("HttpService", "StatusCode : " + ((HttpWebResponse)e.Response).StatusCode); 178 Log.Error("HttpService", "StatusDescription : " + ((HttpWebResponse)e.Response).StatusDescription); 179 } 180 throw new WePayException(e.ToString()); 181 } 182 catch (Exception e) 183 { 184 Log.Error("HttpService", 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 }
新建AppPayData類
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.AppPay 11 { 12 /// <summary> 13 /// 微信支付協議接口數據類,所有的API接口通信都依賴這個數據結構, 14 /// 在調用接口之前先填充各個字段的值,然后進行接口通信, 15 /// 這樣設計的好處是可擴展性強,用戶可隨意對協議進行更改而不用重新設計數據結構, 16 /// 還可以隨意組合出不同的協議數據包,不用為每個協議設計一個數據包結構 17 /// </summary> 18 public class AppPayData 19 { 20 private Log Log = new Log(AppPayConfig.LogPath); 21 22 public AppPayData() 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.Debug(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=" + AppPayConfig.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 }
新建AppPayNotify類
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.AppPay 9 { 10 /// <summary> 11 /// 回調處理基類 12 /// 主要負責接收微信支付后台發送過來的數據,對數據進行簽名驗證 13 /// 子類在此類基礎上進行派生並重寫自己的回調處理過程 14 /// </summary> 15 public class AppPayNotify 16 { 17 public HttpContext context { get; set; } 18 public Log Log = new Log(AppPayConfig.LogPath); 19 20 public AppPayNotify(HttpContext context) 21 { 22 this.context = context; 23 } 24 25 /// <summary> 26 /// 接收從微信支付后台發送過來的數據並驗證簽名 27 /// </summary> 28 /// <returns>微信支付后台返回的數據</returns> 29 public AppPayData GetNotifyData() 30 { 31 //接收從微信后台POST過來的數據 32 System.IO.Stream s = context.Request.InputStream; 33 int count = 0; 34 byte[] buffer = new byte[1024]; 35 StringBuilder builder = new StringBuilder(); 36 while ((count = s.Read(buffer, 0, 1024)) > 0) 37 { 38 builder.Append(Encoding.UTF8.GetString(buffer, 0, count)); 39 } 40 s.Flush(); 41 s.Close(); 42 s.Dispose(); 43 44 //轉換數據格式並驗證簽名 45 AppPayData data = new AppPayData(); 46 try 47 { 48 data.FromXml(builder.ToString()); 49 } 50 catch (WePayException ex) 51 { 52 //若簽名錯誤,則立即返回結果給微信支付后台 53 AppPayData res = new AppPayData(); 54 res.SetValue("return_code", "FAIL"); 55 res.SetValue("return_msg", ex.Message); 56 Log.Error(this.GetType().ToString(), "Sign check error : " + res.ToXml()); 57 context.Response.Write(res.ToXml()); 58 context.Response.End(); 59 } 60 61 Log.Info(this.GetType().ToString(), "Check sign success"); 62 return data; 63 } 64 65 //派生類需要重寫這個方法,進行不同的回調處理 66 public virtual void ProcessNotify() 67 { 68 69 } 70 } 71 }
新建AppPayApi類
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.AppPay 8 { 9 public class AppPayApi 10 { 11 public static Log Log = new Log(AppPayConfig.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 AppPayData Micropay(AppPayData 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", AppPayConfig.APPID);//公眾賬號ID 45 inputObj.SetValue("mch_id", AppPayConfig.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 = AppPayHttpService.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 AppPayData result = new AppPayData(); 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 AppPayData OrderQuery(AppPayData 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", AppPayConfig.APPID);//公眾賬號ID 87 inputObj.SetValue("mch_id", AppPayConfig.MCHID);//商戶號 88 inputObj.SetValue("nonce_str", 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 = AppPayHttpService.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 AppPayData result = new AppPayData(); 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 AppPayData Reverse(AppPayData 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", AppPayConfig.APPID);//公眾賬號ID 130 inputObj.SetValue("mch_id", AppPayConfig.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 = AppPayHttpService.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 AppPayData result = new AppPayData(); 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 AppPayData Refund(AppPayData 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", AppPayConfig.APPID);//公眾賬號ID 189 inputObj.SetValue("mch_id", AppPayConfig.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 = AppPayHttpService.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 AppPayData result = new AppPayData(); 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 AppPayData RefundQuery(AppPayData 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", AppPayConfig.APPID);//公眾賬號ID 235 inputObj.SetValue("mch_id", AppPayConfig.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 = AppPayHttpService.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 AppPayData result = new AppPayData(); 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 AppPayData DownloadBill(AppPayData 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", AppPayConfig.APPID);//公眾賬號ID 277 inputObj.SetValue("mch_id", AppPayConfig.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 = AppPayHttpService.Post(xml, url, false, timeOut);//調用HTTP通信接口以提交數據到API 285 Log.Info("XcxPayApi", "DownloadBill result : " + response); 286 287 AppPayData result = new AppPayData(); 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 AppPayData ShortUrl(AppPayData 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", AppPayConfig.APPID);//公眾賬號ID 321 inputObj.SetValue("mch_id", AppPayConfig.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 = AppPayHttpService.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 AppPayData result = new AppPayData(); 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 AppPayData UnifiedOrder(AppPayData 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", AppPayConfig.NOTIFY_URL);//異步通知url 387 } 388 389 inputObj.SetValue("appid", AppPayConfig.APPID);//公眾賬號ID 390 inputObj.SetValue("mch_id", AppPayConfig.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 = AppPayHttpService.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 AppPayData result = new AppPayData(); 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 AppPayData UnifiedOrderApp(AppPayData 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", AppPayConfig.NOTIFY_URL);//異步通知url 458 } 459 460 inputObj.SetValue("appid", AppPayConfig.APPID);//公眾賬號ID 461 inputObj.SetValue("mch_id", AppPayConfig.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 = AppPayHttpService.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 AppPayData result = new AppPayData(); 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 AppPayData CloseOrder(AppPayData 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", AppPayConfig.APPID);//公眾賬號ID 505 inputObj.SetValue("mch_id", AppPayConfig.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 = AppPayHttpService.Post(xml, url, false, timeOut); 513 514 var end = DateTime.Now; 515 int timeCost = (int)((end - start).TotalMilliseconds); 516 517 AppPayData result = new AppPayData(); 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, AppPayData 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 AppPayData data = new AppPayData(); 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 AppPayData Report(AppPayData 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", AppPayConfig.APPID);//公眾賬號ID 633 inputObj.SetValue("mch_id", AppPayConfig.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 = AppPayHttpService.Post(xml, url, false, timeOut); 643 644 Log.Info("XcxPayApi", "Report response : " + response); 645 646 AppPayData result = new AppPayData(); 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}", AppPayConfig.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 }
接下來是業務類,注意業務類中不必再傳微信登錄憑證。
1 using App.Common.Extension; 2 using App.Pay.WePay.AppPay; 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 public class WeAppPayController : BaseController 13 { 14 /// <summary> 15 /// 微信app支付 16 /// </summary> 17 /// <param name="oidStr">訂單編號</param> 18 /// <returns></returns> 19 public ActionResult WxPayApp(string oidStr) 20 { 21 // App調用只能傳參 22 int[] oIds = Serialize.JsonTo<List<int>>(oidStr).ToArray(); 23 24 #region 驗證訂單是否有效 25 26 decimal payPrice = 0; 27 string detail = ""; 28 29 /// 驗證訂單是否有效,並統計訂單總金額 30 /// ... 31 32 #endregion 33 34 #region 統一下單 35 try 36 { 37 //string userId = LoginUser.Id.ToString(); 38 var address = WebConfigurationManager.AppSettings["WxAppNotifyUrl"].ToString(); 39 AppPayData data = new AppPayData(); 40 data.SetValue("body", "民政社工培訓-課程購買"); 41 //data.SetValue("attach", userId + "|" + String.Join(",", oIds).ToString()); 42 data.SetValue("attach", String.Join(",", oIds).ToString()); 43 Random rd = new Random(); 44 var payNum = DateTime.Now.ToString("yyyyMMddHHmmss") + rd.Next(0, 1000).ToString().PadLeft(3, '0'); 45 data.SetValue("out_trade_no", payNum); 46 data.SetValue("detail", detail.Substring(0, detail.Length - 1)); 47 data.SetValue("total_fee", Convert.ToInt32(payPrice * 100)); 48 data.SetValue("time_start", DateTime.Now.ToString("yyyyMMddHHmmss")); 49 data.SetValue("time_expire", DateTime.Now.AddMinutes(10).ToString("yyyyMMddHHmmss")); 50 data.SetValue("notify_url", address); 51 52 //注意,這里交易方式是APP 53 data.SetValue("trade_type", "APP"); 54 55 AppPayData result = AppPayApi.UnifiedOrder(data); 56 var appid = ""; 57 var partnerid = ""; 58 var prepayid = ""; 59 var package = ""; 60 var nonceStr = ""; 61 var timestamp = ""; 62 var sign = ""; 63 if (!result.IsSet("appid") || !result.IsSet("prepay_id") || result.GetValue("prepay_id").ToString() == "") 64 { 65 return Json(false, "下單失敗!"); 66 } 67 else 68 { 69 //統一下單 70 /// 修改訂單狀態 71 //OrderBll.Value.UpdateOrderApp(oIds, payNum); 72 73 appid = result.GetValue("appid").ToString(); 74 nonceStr = result.GetValue("nonce_str").ToString(); 75 partnerid = result.GetValue("mch_id").ToString(); 76 prepayid = result.GetValue("prepay_id").ToString(); 77 package = "Sign=WXPay";// "prepay_id=" + result.GetValue("prepay_id").ToString(); 78 } 79 var signType = "MD5"; 80 timestamp = ((DateTime.Now.Ticks - TimeZone.CurrentTimeZone.ToLocalTime(new DateTime(1970, 1, 1)).Ticks) / 10000).ToString(); 81 AppPayData applet = new AppPayData(); 82 applet.SetValue("appid", appid); 83 applet.SetValue("noncestr", nonceStr); 84 applet.SetValue("package", package); 85 applet.SetValue("partnerid", partnerid); 86 applet.SetValue("prepayid", prepayid); 87 //applet.SetValue("signType", signType); 88 applet.SetValue("timestamp", timestamp); 89 sign = applet.MakeSign(); 90 return Json(new { appid, partnerid, prepayid, package, nonceStr, timestamp, sign }); 91 } 92 catch (Exception ex) 93 { 94 return Json(false, "缺少參數"); 95 } 96 97 #endregion 98 99 } 100 101 /// <summary> 102 /// 微信支付回調 103 /// </summary> 104 /// <returns></returns> 105 public string WxAppNotifyUrl() 106 { 107 Pay.Log Log = new Pay.Log(AppPayConfig.LogPath); 108 Log.Info("WxAppNotifyUrl", "支付回調"); 109 AppPayNotify notify = new AppPayNotify(System.Web.HttpContext.Current); 110 AppPayData notifyData = notify.GetNotifyData(); 111 112 //檢查支付結果中transaction_id是否存在 113 if (!notifyData.IsSet("transaction_id")) 114 { 115 //若transaction_id不存在,則立即返回結果給微信支付后台 116 AppPayData res = new AppPayData(); 117 res.SetValue("return_code", "FAIL"); 118 res.SetValue("return_msg", "支付結果中微信訂單號不存在"); 119 Log.Error(this.GetType().ToString(), "The Pay result is error : " + res.ToXml()); 120 Response.Write(res.ToXml()); 121 Response.End(); 122 } 123 124 string transaction_id = notifyData.GetValue("transaction_id").ToString(); 125 126 //查詢訂單,判斷訂單真實性 127 if (!AppQueryOrder(transaction_id)) 128 { 129 //若訂單查詢失敗,則立即返回結果給微信支付后台 130 AppPayData res = new AppPayData(); 131 res.SetValue("return_code", "FAIL"); 132 res.SetValue("return_msg", "訂單查詢失敗"); 133 Log.Error(this.GetType().ToString(), "Order query failure : " + res.ToXml()); 134 135 Response.Write(res.ToXml()); 136 Response.End(); 137 } 138 //查詢訂單成功 139 else 140 { 141 AppPayData res = new AppPayData(); 142 res.SetValue("return_code", "SUCCESS"); 143 res.SetValue("return_msg", "OK"); 144 Log.Info(this.GetType().ToString(), "Order query success : " + res.ToXml()); 145 Log.Info(this.GetType().ToString(), "Order query success,notifyData : " + notifyData.ToXml()); 146 var returnCode = notifyData.GetValue("return_code").ToString(); 147 var transactionNo = transaction_id;//微信訂單號 148 var outTradeNo = notifyData.GetValue("out_trade_no").ToString();//自定義訂單號 149 var attach = notifyData.GetValue("attach").ToString();//身份證 150 var endTime = notifyData.GetValue("time_end").ToString();//交易結束時間 151 //var body = notifyData.GetValue("body").ToString();//projectIdlist 152 var totalFee = notifyData.GetValue("total_fee").ToString(); ;//支付金額 153 154 int userId = Convert.ToInt32(attach.Split('|')[0]); 155 string msg; 156 try 157 { 158 ///修改數據庫訂單狀態 159 //var result = OrderBll.Value.CompleteWePay(userId, totalFee, transactionNo, returnCode, outTradeNo, attach, endTime, out msg); 160 var result = true; 161 162 Log.Info(this.GetType().ToString(), "CompleteWePay:" + result); 163 } 164 catch (Exception e) 165 { 166 Log.Error(this.GetType().ToString(), "CompleteWePay:" + e.ToString()); 167 } 168 169 Response.Write(res.ToXml()); 170 Response.End(); 171 } 172 173 return ""; 174 } 175 176 /// <summary> 177 /// 查詢訂單 178 /// </summary> 179 /// <param name="transaction_id"></param> 180 /// <returns></returns> 181 private bool AppQueryOrder(string transaction_id) 182 { 183 AppPayData req = new AppPayData(); 184 req.SetValue("transaction_id", transaction_id); 185 AppPayData res = AppPayApi.OrderQuery(req); 186 if (res.GetValue("return_code").ToString() == "SUCCESS" && res.GetValue("result_code").ToString() == "SUCCESS") 187 { 188 return true; 189 } 190 else 191 { 192 return false; 193 } 194 } 195 } 196 }
