上一节分享了微信小程序支付的后台,这一节来分享一下微信APP支付的后台。微信APP支付和微信小程序差别不大,微信APP支付后台不需要微信登录凭证、后台下单时交易类型(trade_type)不再是"JSAPI",而是“APP”、商户后台传递给支付端的下单参数也有所不同。由于微信小程序支付和APP支付使用的APPID不同,索性直接写了两套支付,不再在代码里区分究竟该使用小程序支付的配置参数还是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 }