最近负责的一些项目开发,都用到了微信支付(微信公众号支付、微信H5支付、微信扫码支付)。在开发的过程中,在调试支付的过程中,或多或少都遇到了一些问题,今天总结下,分享,留存。代码在文章结尾处,有需要的同学可以下载看下。
先说注意的第一点,所有支付的第一步都是请求统一下单,统一下单,统一下单,请求URL地址:https://api.mch.weixin.qq.com/pay/unifiedorder。
再说一个微信官方提供的一个很重要的工具,微信支付接口签名校验工具(网址:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=20_1),此工具旨在帮助开发者检测调用【微信支付接口API】时发送的请求参数中生成的签名是否正确,提交相关信息后可获得签名校验结果。特别实用!特别实用!特别实用!签名只要正确了,一切就OK了!
第一部分 微信公众号支付
微信公众号支付需要配置的参数有:APPID(微信公众号开发者ID)、APPSECRET(微信公众号开发者密码)、MCHID(商户ID)、KEY(商户密钥)。
微信公众号支付应用的场景是在微信内部的H5环境中使用的支付方式。因为要通过网页授权获取用户的OpenId,所以必须要配置网页授权域名。同时要配置JS接口安全域名。
JsApiConfig.cs
1 using System.Web; 2 using System.Text; 3 using System.IO; 4 using System.Net; 5 using System; 6 using System.Xml; 7 using System.Collections.Generic; 8 using Gwbnsh.Common; 9 10 namespace Gwbnsh.API.Payment.wxpay 11 { 12 public class JsApiConfig 13 { 14 #region 字段 15 private string partner = string.Empty; 16 private string key = string.Empty; 17 private string appid = string.Empty; 18 private string appsecret = string.Empty; 19 private string redirect_url = string.Empty; 20 private string notify_url = string.Empty; 21 #endregion 22 23 public JsApiConfig(int site_payment_id) 24 { 25 Model.site_payment model = new BLL.site_payment().GetModel(site_payment_id); //站点支付方式 26 if (model != null) 27 { 28 Model.payment payModel = new BLL.payment().GetModel(model.payment_id); //支付平台 29 Model.sites siteModel = new BLL.sites().GetModel(model.site_id); //站点配置 30 Model.sysconfig sysConfig = new BLL.sysconfig().loadConfig(); //系统配置 31 32 partner = model.key1; //商户号(必须配置) 33 key = model.key2; //商户支付密钥,参考开户邮件设置(必须配置) 34 appid = model.key3; //绑定支付的APPID(必须配置) 35 appsecret = model.key4; //公众帐号secert(仅JSAPI支付的时候需要配置) 36 37 //获取用户的OPENID回调地址及登录后的回调地址 38 redirect_url = "http://m.gwbnsh.net.cn/hd/SellPhone" + payModel.return_url; 39 notify_url = "http://m.gwbnsh.net.cn/hd/SellPhone" + payModel.notify_url; 40 } 41 } 42 } 43 44 #region 属性 45 /// <summary> 46 /// 商户号(必须配置) 47 /// </summary> 48 public string Partner 49 { 50 get { return partner; } 51 set { partner = value; } 52 } 53 54 /// <summary> 55 /// 获取或设交易安全校验码 56 /// </summary> 57 public string Key 58 { 59 get { return key; } 60 set { key = value; } 61 } 62 63 /// <summary> 64 /// 绑定支付的APPID(必须配置) 65 /// </summary> 66 public string AppId 67 { 68 get { return appid; } 69 set { appid = value; } 70 } 71 72 /// <summary> 73 /// 公众帐号secert(仅JSAPI支付的时候需要配置) 74 /// </summary> 75 public string AppSecret 76 { 77 get { return appsecret; } 78 set { appsecret = value; } 79 } 80 81 /// <summary> 82 /// 获取用户的OPENID回调地址 83 /// </summary> 84 public string Redirect_url 85 { 86 get { return redirect_url; } 87 } 88 89 /// <summary> 90 /// 获取服务器异步通知页面路径 91 /// </summary> 92 public string Notify_url 93 { 94 get { return notify_url; } 95 } 96 97 #endregion 98 } 99 }
JsApiPay.cs
1 using System; 2 using System.Collections.Generic; 3 using System.Web; 4 using System.Net; 5 using System.IO; 6 using System.Text; 7 using Gwbnsh.Common; 8 9 namespace Gwbnsh.API.Payment.wxpay 10 { 11 public class JsApiPay 12 { 13 /** 14 * 15 * 测速上报 16 * @param string interface_url 接口URL 17 * @param int timeCost 接口耗时 18 * @param WxPayData inputObj参数数组 19 */ 20 public static void ReportCostTime(int paymentId, string interface_url, int timeCost, WxPayData inputObj) 21 { 22 //如果仅失败上报 23 if (inputObj.IsSet("return_code") && inputObj.GetValue("return_code").ToString() == "SUCCESS" && 24 inputObj.IsSet("result_code") && inputObj.GetValue("result_code").ToString() == "SUCCESS") 25 { 26 return; 27 } 28 29 //上报逻辑 30 WxPayData data = new WxPayData(); 31 data.SetValue("interface_url", interface_url); 32 data.SetValue("execute_time_", timeCost); 33 //返回状态码 34 if (inputObj.IsSet("return_code")) 35 { 36 data.SetValue("return_code", inputObj.GetValue("return_code")); 37 } 38 //返回信息 39 if (inputObj.IsSet("return_msg")) 40 { 41 data.SetValue("return_msg", inputObj.GetValue("return_msg")); 42 } 43 //业务结果 44 if (inputObj.IsSet("result_code")) 45 { 46 data.SetValue("result_code", inputObj.GetValue("result_code")); 47 } 48 //错误代码 49 if (inputObj.IsSet("err_code")) 50 { 51 data.SetValue("err_code", inputObj.GetValue("err_code")); 52 } 53 //错误代码描述 54 if (inputObj.IsSet("err_code_des")) 55 { 56 data.SetValue("err_code_des", inputObj.GetValue("err_code_des")); 57 } 58 //商户订单号 59 if (inputObj.IsSet("out_trade_no")) 60 { 61 data.SetValue("out_trade_no", inputObj.GetValue("out_trade_no")); 62 } 63 //设备号 64 if (inputObj.IsSet("device_info")) 65 { 66 data.SetValue("device_info", inputObj.GetValue("device_info")); 67 } 68 69 try 70 { 71 Report(paymentId, data); 72 } 73 catch (WxPayException ex) 74 { 75 //不做任何处理 76 } 77 } 78 79 /** 80 * 81 * 测速上报接口实现 82 * @param WxPayData inputObj 提交给测速上报接口的参数 83 * @param int timeOut 测速上报接口超时时间 84 * @throws WxPayException 85 * @return 成功时返回测速上报接口返回的结果,其他抛异常 86 */ 87 public static WxPayData Report(int paymentId, WxPayData inputObj, int timeOut = 1) 88 { 89 JsApiConfig jsApiConfig = new JsApiConfig(paymentId); 90 string url = "https://api.mch.weixin.qq.com/payitil/report"; 91 //检测必填参数 92 if (!inputObj.IsSet("interface_url")) 93 { 94 throw new WxPayException("接口URL,缺少必填参数interface_url!"); 95 } 96 if (!inputObj.IsSet("return_code")) 97 { 98 throw new WxPayException("返回状态码,缺少必填参数return_code!"); 99 } 100 if (!inputObj.IsSet("result_code")) 101 { 102 throw new WxPayException("业务结果,缺少必填参数result_code!"); 103 } 104 if (!inputObj.IsSet("user_ip")) 105 { 106 throw new WxPayException("访问接口IP,缺少必填参数user_ip!"); 107 } 108 if (!inputObj.IsSet("execute_time_")) 109 { 110 throw new WxPayException("接口耗时,缺少必填参数execute_time_!"); 111 } 112 113 inputObj.SetValue("appid", jsApiConfig.AppId);//公众账号ID 114 inputObj.SetValue("mch_id", jsApiConfig.Partner);//商户号 115 inputObj.SetValue("user_ip", DTRequest.GetIP());//终端ip 116 inputObj.SetValue("time", DateTime.Now.ToString("yyyyMMddHHmmss"));//商户上报时间 117 inputObj.SetValue("nonce_str", GenerateNonceStr());//随机字符串 118 inputObj.SetValue("sign", inputObj.MakeSign(jsApiConfig.Key));//签名 119 string xml = inputObj.ToXml(); 120 121 string response = HttpService.Post(xml, url, false, timeOut); 122 123 WxPayData result = new WxPayData(); 124 result.FromXml(response, jsApiConfig.Key); 125 return result; 126 } 127 128 /** 129 * 生成时间戳,标准北京时间,时区为东八区,自1970年1月1日 0点0分0秒以来的秒数 130 * @return 时间戳 131 */ 132 public static string GenerateTimeStamp() 133 { 134 TimeSpan ts = DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, 0); 135 return Convert.ToInt64(ts.TotalSeconds).ToString(); 136 } 137 138 /** 139 * 生成随机串,随机串包含字母或数字 140 * @return 随机串 141 */ 142 public static string GenerateNonceStr() 143 { 144 return Guid.NewGuid().ToString().Replace("-", ""); 145 } 146 147 /// <summary> 148 /// 接收从微信支付后台发送过来的数据暂不验证签名 149 /// </summary> 150 /// <returns>微信支付后台返回的数据</returns> 151 public static WxPayData GetNotifyData() 152 { 153 //接收从微信后台POST过来的数据 154 System.IO.Stream s = HttpContext.Current.Request.InputStream; 155 int count = 0; 156 byte[] buffer = new byte[1024]; 157 StringBuilder builder = new StringBuilder(); 158 while ((count = s.Read(buffer, 0, 1024)) > 0) 159 { 160 builder.Append(Encoding.UTF8.GetString(buffer, 0, count)); 161 } 162 s.Flush(); 163 s.Close(); 164 s.Dispose(); 165 166 //转换数据格式并验证签名 167 WxPayData data = new WxPayData(); 168 try 169 { 170 data.FromXml(builder.ToString()); 171 } 172 catch (WxPayException ex) 173 { 174 //若有错误,则立即返回结果给微信支付后台 175 WxPayData res = new WxPayData(); 176 res.SetValue("return_code", "FAIL"); 177 res.SetValue("return_msg", ex.Message); 178 HttpContext.Current.Response.Write(res.ToXml()); 179 HttpContext.Current.Response.End(); 180 } 181 182 return data; 183 } 184 185 /** 186 * 187 * 查询订单 188 * @param WxPayData inputObj 提交给查询订单API的参数 189 * @param int timeOut 超时时间 190 * @throws WxPayException 191 * @return 成功时返回订单查询结果,其他抛异常 192 */ 193 public static WxPayData OrderQuery(int paymentId, WxPayData inputObj, int timeOut = 6) 194 { 195 string sendUrl = "https://api.mch.weixin.qq.com/pay/orderquery"; 196 //检测必填参数 197 if (!inputObj.IsSet("out_trade_no") && !inputObj.IsSet("transaction_id")) 198 { 199 throw new WxPayException("订单查询接口中,out_trade_no、transaction_id至少填一个!"); 200 } 201 JsApiConfig jsApiConfig = new JsApiConfig(paymentId); 202 inputObj.SetValue("appid", jsApiConfig.AppId);//公众账号ID 203 inputObj.SetValue("mch_id", jsApiConfig.Partner);//商户号 204 inputObj.SetValue("nonce_str", GenerateNonceStr());//随机字符串 205 inputObj.SetValue("sign", inputObj.MakeSign(jsApiConfig.Key));//签名 206 string xml = inputObj.ToXml(); 207 var startTime = DateTime.Now; //开始时间 208 string response = HttpService.Post(xml, sendUrl, false, timeOut);//调用HTTP通信接口提交数据 209 var endTime = DateTime.Now; //结束时间 210 int timeCost = (int)((endTime - startTime).TotalMilliseconds); //计算所用时间 211 //将xml格式的数据转化为对象以返回 212 WxPayData result = new WxPayData(); 213 result.FromXml(response, jsApiConfig.Key); 214 ReportCostTime(paymentId, sendUrl, timeCost, result);//测速上报 215 return result; 216 } 217 218 } 219 }
第二部分 微信H5支付
微信H5支付是微信官方2017年上半年刚刚对外开放的支付模式,它主要应用于在手机网站在移动浏览器(非微信环境)调用微信支付的场景。
注意:微信H5支付需要在微信支付商户平台单独申请开通,否则无法使用。
微信H5支付的流程比较简单,就是拼接请求的xml数据,进行统一下单,获取到支付的mweb_url,然后请求这个url网址就行。
H5Config.cs
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 6 namespace Gwbnsh.API.Payment.wxpay 7 { 8 /// <summary> 9 /// 移动端非微信浏览器支付 10 /// </summary> 11 public class H5Config 12 { 13 #region 字段 14 private string partner = string.Empty; 15 private string key = string.Empty; 16 private string appid = string.Empty; 17 private string notify_url = string.Empty; 18 #endregion 19 20 public H5Config(int site_payment_id) 21 { 22 Model.site_payment model = new BLL.site_payment().GetModel(site_payment_id); //站点支付方式 23 if (model != null) 24 { 25 Model.payment payModel = new BLL.payment().GetModel(model.payment_id); //支付平台 26 Model.sites siteModel = new BLL.sites().GetModel(model.site_id); //站点配置 27 Model.sysconfig sysConfig = new BLL.sysconfig().loadConfig(); //系统配置 28 29 partner = model.key1; //商户号(必须配置) 30 key = model.key2; //商户支付密钥,参考开户邮件设置(必须配置) 31 appid = model.key3; //绑定支付的APPID(必须配置) 32 notify_url = ""; 33 } 34 } 35 36 #region 属性 37 /// <summary> 38 /// 商户号(必须配置) 39 /// </summary> 40 public string Partner 41 { 42 get { return partner; } 43 set { partner = value; } 44 } 45 46 /// <summary> 47 /// 获取或设交易安全校验码 48 /// </summary> 49 public string Key 50 { 51 get { return key; } 52 set { key = value; } 53 } 54 55 /// <summary> 56 /// 绑定支付的APPID(必须配置) 57 /// </summary> 58 public string AppId 59 { 60 get { return appid; } 61 set { appid = value; } 62 } 63 64 /// <summary> 65 /// 获取服务器异步通知页面路径 66 /// </summary> 67 public string Notify_url 68 { 69 get { return notify_url; } 70 } 71 72 #endregion 73 } 74 }
H5Pay.cs
1 using Gwbnsh.Common; 2 using System; 3 using System.Collections.Generic; 4 using System.Linq; 5 using System.Text; 6 using System.Web; 7 8 namespace Gwbnsh.API.Payment.wxpay 9 { 10 public class H5Pay 11 { 12 /** 13 * 14 * 测速上报 15 * @param string interface_url 接口URL 16 * @param int timeCost 接口耗时 17 * @param WxPayData inputObj参数数组 18 */ 19 public static void ReportCostTime(int paymentId, string interface_url, int timeCost, WxPayData inputObj) 20 { 21 //如果仅失败上报 22 if (inputObj.IsSet("return_code") && inputObj.GetValue("return_code").ToString() == "SUCCESS" && 23 inputObj.IsSet("result_code") && inputObj.GetValue("result_code").ToString() == "SUCCESS") 24 { 25 return; 26 } 27 28 //上报逻辑 29 WxPayData data = new WxPayData(); 30 data.SetValue("interface_url", interface_url); 31 data.SetValue("execute_time_", timeCost); 32 //返回状态码 33 if (inputObj.IsSet("return_code")) 34 { 35 data.SetValue("return_code", inputObj.GetValue("return_code")); 36 } 37 //返回信息 38 if (inputObj.IsSet("return_msg")) 39 { 40 data.SetValue("return_msg", inputObj.GetValue("return_msg")); 41 } 42 //业务结果 43 if (inputObj.IsSet("result_code")) 44 { 45 data.SetValue("result_code", inputObj.GetValue("result_code")); 46 } 47 //错误代码 48 if (inputObj.IsSet("err_code")) 49 { 50 data.SetValue("err_code", inputObj.GetValue("err_code")); 51 } 52 //错误代码描述 53 if (inputObj.IsSet("err_code_des")) 54 { 55 data.SetValue("err_code_des", inputObj.GetValue("err_code_des")); 56 } 57 //商户订单号 58 if (inputObj.IsSet("out_trade_no")) 59 { 60 data.SetValue("out_trade_no", inputObj.GetValue("out_trade_no")); 61 } 62 //设备号 63 if (inputObj.IsSet("device_info")) 64 { 65 data.SetValue("device_info", inputObj.GetValue("device_info")); 66 } 67 68 try 69 { 70 Report(paymentId, data); 71 } 72 catch (WxPayException ex) 73 { 74 //不做任何处理 75 } 76 } 77 78 /** 79 * 80 * 测速上报接口实现 81 * @param WxPayData inputObj 提交给测速上报接口的参数 82 * @param int timeOut 测速上报接口超时时间 83 * @throws WxPayException 84 * @return 成功时返回测速上报接口返回的结果,其他抛异常 85 */ 86 public static WxPayData Report(int paymentId, WxPayData inputObj, int timeOut = 1) 87 { 88 H5Config h5Config = new H5Config(paymentId); 89 string url = "https://api.mch.weixin.qq.com/payitil/report"; 90 //检测必填参数 91 if (!inputObj.IsSet("interface_url")) 92 { 93 throw new WxPayException("接口URL,缺少必填参数interface_url!"); 94 } 95 if (!inputObj.IsSet("return_code")) 96 { 97 throw new WxPayException("返回状态码,缺少必填参数return_code!"); 98 } 99 if (!inputObj.IsSet("result_code")) 100 { 101 throw new WxPayException("业务结果,缺少必填参数result_code!"); 102 } 103 if (!inputObj.IsSet("user_ip")) 104 { 105 throw new WxPayException("访问接口IP,缺少必填参数user_ip!"); 106 } 107 if (!inputObj.IsSet("execute_time_")) 108 { 109 throw new WxPayException("接口耗时,缺少必填参数execute_time_!"); 110 } 111 112 inputObj.SetValue("appid", h5Config.AppId);//公众账号ID 113 inputObj.SetValue("mch_id", h5Config.Partner);//商户号 114 inputObj.SetValue("user_ip", DTRequest.GetIP());//终端ip 115 inputObj.SetValue("time", DateTime.Now.ToString("yyyyMMddHHmmss"));//商户上报时间 116 inputObj.SetValue("nonce_str", GenerateNonceStr());//随机字符串 117 inputObj.SetValue("sign", inputObj.MakeSign(h5Config.Key));//签名 118 string xml = inputObj.ToXml(); 119 120 string response = HttpService.Post(xml, url, false, timeOut); 121 122 WxPayData result = new WxPayData(); 123 result.FromXml(response, h5Config.Key); 124 return result; 125 } 126 127 /** 128 * 生成时间戳,标准北京时间,时区为东八区,自1970年1月1日 0点0分0秒以来的秒数 129 * @return 时间戳 130 */ 131 public static string GenerateTimeStamp() 132 { 133 TimeSpan ts = DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, 0); 134 return Convert.ToInt64(ts.TotalSeconds).ToString(); 135 } 136 137 /** 138 * 生成随机串,随机串包含字母或数字 139 * @return 随机串 140 */ 141 public static string GenerateNonceStr() 142 { 143 return Guid.NewGuid().ToString().Replace("-", ""); 144 } 145 /// <summary> 146 /// 接收从微信支付后台发送过来的数据未验证签名 147 /// </summary> 148 /// <returns>微信支付后台返回的数据</returns> 149 public static WxPayData GetNotifyData() 150 { 151 //接收从微信后台POST过来的数据 152 System.IO.Stream s = HttpContext.Current.Request.InputStream; 153 int count = 0; 154 byte[] buffer = new byte[1024]; 155 StringBuilder builder = new StringBuilder(); 156 while ((count = s.Read(buffer, 0, 1024)) > 0) 157 { 158 builder.Append(Encoding.UTF8.GetString(buffer, 0, count)); 159 } 160 s.Flush(); 161 s.Close(); 162 s.Dispose(); 163 164 //转换数据格式暂不验证签名 165 WxPayData data = new WxPayData(); 166 try 167 { 168 data.FromXml(builder.ToString()); 169 } 170 catch (WxPayException ex) 171 { 172 //若签名错误,则立即返回结果给微信支付后台 173 WxPayData res = new WxPayData(); 174 res.SetValue("return_code", "FAIL"); 175 res.SetValue("return_msg", ex.Message); 176 HttpContext.Current.Response.Write(res.ToXml()); 177 HttpContext.Current.Response.End(); 178 } 179 180 return data; 181 } 182 183 /** 184 * 185 * 查询订单 186 * @param WxPayData inputObj 提交给查询订单API的参数 187 * @param int timeOut 超时时间 188 * @throws WxPayException 189 * @return 成功时返回订单查询结果,其他抛异常 190 */ 191 public static WxPayData OrderQuery(int paymentId, WxPayData inputObj, int timeOut = 6) 192 { 193 string sendUrl = "https://api.mch.weixin.qq.com/pay/orderquery"; 194 //检测必填参数 195 if (!inputObj.IsSet("out_trade_no") && !inputObj.IsSet("transaction_id")) 196 { 197 throw new WxPayException("订单查询接口中,out_trade_no、transaction_id至少填一个!"); 198 } 199 H5Config h5Config = new H5Config(paymentId); 200 inputObj.SetValue("appid", h5Config.AppId);//公众账号ID 201 inputObj.SetValue("mch_id", h5Config.Partner);//商户号 202 inputObj.SetValue("nonce_str", GenerateNonceStr());//随机字符串 203 inputObj.SetValue("sign", inputObj.MakeSign(h5Config.Key));//签名 204 string xml = inputObj.ToXml(); 205 var startTime = DateTime.Now; //开始时间 206 string response = HttpService.Post(xml, sendUrl, false, timeOut);//调用HTTP通信接口提交数据 207 var endTime = DateTime.Now; //结束时间 208 int timeCost = (int)((endTime - startTime).TotalMilliseconds); //计算所用时间 209 //将xml格式的数据转化为对象以返回 210 WxPayData result = new WxPayData(); 211 result.FromXml(response, h5Config.Key); 212 ReportCostTime(paymentId, sendUrl, timeCost, result);//测速上报 213 return result; 214 } 215 216 } 217 }
第三部分 微信扫码支付
微信扫码支付一般应用的场景是PC端电脑支付。微信扫码支付可分为两种模式,根据支付场景选择相应模式。一般情况下的PC端扫码支付选择的是模式二,需要注意的是模式二无回调函数。
【模式一】商户后台系统根据微信支付规则链接生成二维码,链接中带固定参数productid(可定义为产品标识或订单号)。用户扫码后,微信支付系统将productid和用户唯一标识(openid)回调商户后台系统(需要设置支付回调URL),商户后台系统根据productid生成支付交易,最后微信支付系统发起用户支付流程。
【模式二】商户后台系统调用微信支付【统一下单API】生成预付交易,将接口返回的链接生成二维码,用户扫码后输入密码完成支付交易。注意:该模式的预付单有效期为2小时,过期后无法支付。
微信扫码支付最友好的解决方案就是支付完成之后通过JS设置监听函数,通过该函数完成跳转。可参考的代码如下:
NativeConfig.cs
1 using System.Web; 2 using System.Text; 3 using System.IO; 4 using System.Net; 5 using System; 6 using System.Xml; 7 using System.Collections.Generic; 8 using Gwbnsh.Common; 9 10 namespace Gwbnsh.API.Payment.wxpay 11 { 12 public class NativeConfig 13 { 14 #region 字段 15 private string partner = string.Empty; 16 private string key = string.Empty; 17 private string appid = string.Empty; 18 private string notify_url = string.Empty; 19 #endregion 20 21 public NativeConfig(int site_payment_id) 22 { 23 Model.site_payment model = new BLL.site_payment().GetModel(site_payment_id); //站点支付方式 24 if (model != null) 25 { 26 Model.payment payModel = new BLL.payment().GetModel(model.payment_id); //支付平台 27 Model.sites siteModel = new BLL.sites().GetModel(model.site_id); //站点配置 28 Model.sysconfig sysConfig = new BLL.sysconfig().loadConfig(); //系统配置 29 30 partner = model.key1; //商户号(必须配置) 31 key = model.key2; //商户支付密钥,参考开户邮件设置(必须配置) 32 appid = model.key3; //绑定支付的APPID(必须配置) 33 notify_url = ""; 34 } 35 } 36 37 #region 属性 38 /// <summary> 39 /// 商户号(必须配置) 40 /// </summary> 41 public string Partner 42 { 43 get { return partner; } 44 set { partner = value; } 45 } 46 47 /// <summary> 48 /// 获取或设交易安全校验码 49 /// </summary> 50 public string Key 51 { 52 get { return key; } 53 set { key = value; } 54 } 55 56 /// <summary> 57 /// 绑定支付的APPID(必须配置) 58 /// </summary> 59 public string AppId 60 { 61 get { return appid; } 62 set { appid = value; } 63 } 64 65 /// <summary> 66 /// 获取服务器异步通知页面路径 67 /// </summary> 68 public string Notify_url 69 { 70 get { return notify_url; } 71 } 72 73 #endregion 74 } 75 }
NativePay.cs
1 using System; 2 using System.Collections.Generic; 3 using System.Web; 4 using System.Net; 5 using System.IO; 6 using System.Text; 7 using Gwbnsh.Common; 8 9 namespace Gwbnsh.API.Payment.wxpay 10 { 11 public class NativePay 12 { 13 /** 14 * 15 * 测速上报 16 * @param string interface_url 接口URL 17 * @param int timeCost 接口耗时 18 * @param WxPayData inputObj参数数组 19 */ 20 public static void ReportCostTime(int paymentId, string interface_url, int timeCost, WxPayData inputObj) 21 { 22 //如果仅失败上报 23 if (inputObj.IsSet("return_code") && inputObj.GetValue("return_code").ToString() == "SUCCESS" && 24 inputObj.IsSet("result_code") && inputObj.GetValue("result_code").ToString() == "SUCCESS") 25 { 26 return; 27 } 28 29 //上报逻辑 30 WxPayData data = new WxPayData(); 31 data.SetValue("interface_url", interface_url); 32 data.SetValue("execute_time_", timeCost); 33 //返回状态码 34 if (inputObj.IsSet("return_code")) 35 { 36 data.SetValue("return_code", inputObj.GetValue("return_code")); 37 } 38 //返回信息 39 if (inputObj.IsSet("return_msg")) 40 { 41 data.SetValue("return_msg", inputObj.GetValue("return_msg")); 42 } 43 //业务结果 44 if (inputObj.IsSet("result_code")) 45 { 46 data.SetValue("result_code", inputObj.GetValue("result_code")); 47 } 48 //错误代码 49 if (inputObj.IsSet("err_code")) 50 { 51 data.SetValue("err_code", inputObj.GetValue("err_code")); 52 } 53 //错误代码描述 54 if (inputObj.IsSet("err_code_des")) 55 { 56 data.SetValue("err_code_des", inputObj.GetValue("err_code_des")); 57 } 58 //商户订单号 59 if (inputObj.IsSet("out_trade_no")) 60 { 61 data.SetValue("out_trade_no", inputObj.GetValue("out_trade_no")); 62 } 63 //设备号 64 if (inputObj.IsSet("device_info")) 65 { 66 data.SetValue("device_info", inputObj.GetValue("device_info")); 67 } 68 69 try 70 { 71 Report(paymentId, data); 72 } 73 catch (WxPayException ex) 74 { 75 //不做任何处理 76 } 77 } 78 79 /** 80 * 81 * 测速上报接口实现 82 * @param WxPayData inputObj 提交给测速上报接口的参数 83 * @param int timeOut 测速上报接口超时时间 84 * @throws WxPayException 85 * @return 成功时返回测速上报接口返回的结果,其他抛异常 86 */ 87 public static WxPayData Report(int paymentId, WxPayData inputObj, int timeOut = 1) 88 { 89 NativeConfig nativeConfig = new NativeConfig(paymentId); 90 string url = "https://api.mch.weixin.qq.com/payitil/report"; 91 //检测必填参数 92 if (!inputObj.IsSet("interface_url")) 93 { 94 throw new WxPayException("接口URL,缺少必填参数interface_url!"); 95 } 96 if (!inputObj.IsSet("return_code")) 97 { 98 throw new WxPayException("返回状态码,缺少必填参数return_code!"); 99 } 100 if (!inputObj.IsSet("result_code")) 101 { 102 throw new WxPayException("业务结果,缺少必填参数result_code!"); 103 } 104 if (!inputObj.IsSet("user_ip")) 105 { 106 throw new WxPayException("访问接口IP,缺少必填参数user_ip!"); 107 } 108 if (!inputObj.IsSet("execute_time_")) 109 { 110 throw new WxPayException("接口耗时,缺少必填参数execute_time_!"); 111 } 112 113 inputObj.SetValue("appid", nativeConfig.AppId);//公众账号ID 114 inputObj.SetValue("mch_id", nativeConfig.Partner);//商户号 115 inputObj.SetValue("user_ip", DTRequest.GetIP());//终端ip 116 inputObj.SetValue("time", DateTime.Now.ToString("yyyyMMddHHmmss"));//商户上报时间 117 inputObj.SetValue("nonce_str", GenerateNonceStr());//随机字符串 118 inputObj.SetValue("sign", inputObj.MakeSign(nativeConfig.Key));//签名 119 string xml = inputObj.ToXml(); 120 121 string response = HttpService.Post(xml, url, false, timeOut); 122 123 WxPayData result = new WxPayData(); 124 result.FromXml(response, nativeConfig.Key); 125 return result; 126 } 127 128 /** 129 * 生成时间戳,标准北京时间,时区为东八区,自1970年1月1日 0点0分0秒以来的秒数 130 * @return 时间戳 131 */ 132 public static string GenerateTimeStamp() 133 { 134 TimeSpan ts = DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, 0); 135 return Convert.ToInt64(ts.TotalSeconds).ToString(); 136 } 137 138 /** 139 * 生成随机串,随机串包含字母或数字 140 * @return 随机串 141 */ 142 public static string GenerateNonceStr() 143 { 144 return Guid.NewGuid().ToString().Replace("-", ""); 145 } 146 /// <summary> 147 /// 接收从微信支付后台发送过来的数据未验证签名 148 /// </summary> 149 /// <returns>微信支付后台返回的数据</returns> 150 public static WxPayData GetNotifyData() 151 { 152 //接收从微信后台POST过来的数据 153 System.IO.Stream s = HttpContext.Current.Request.InputStream; 154 int count = 0; 155 byte[] buffer = new byte[1024]; 156 StringBuilder builder = new StringBuilder(); 157 while ((count = s.Read(buffer, 0, 1024)) > 0) 158 { 159 builder.Append(Encoding.UTF8.GetString(buffer, 0, count)); 160 } 161 s.Flush(); 162 s.Close(); 163 s.Dispose(); 164 165 //转换数据格式暂不验证签名 166 WxPayData data = new WxPayData(); 167 try 168 { 169 data.FromXml(builder.ToString()); 170 } 171 catch (WxPayException ex) 172 { 173 //若签名错误,则立即返回结果给微信支付后台 174 WxPayData res = new WxPayData(); 175 res.SetValue("return_code", "FAIL"); 176 res.SetValue("return_msg", ex.Message); 177 HttpContext.Current.Response.Write(res.ToXml()); 178 HttpContext.Current.Response.End(); 179 } 180 181 return data; 182 } 183 184 /** 185 * 186 * 查询订单 187 * @param WxPayData inputObj 提交给查询订单API的参数 188 * @param int timeOut 超时时间 189 * @throws WxPayException 190 * @return 成功时返回订单查询结果,其他抛异常 191 */ 192 public static WxPayData OrderQuery(int paymentId, WxPayData inputObj, int timeOut = 6) 193 { 194 string sendUrl = "https://api.mch.weixin.qq.com/pay/orderquery"; 195 //检测必填参数 196 if (!inputObj.IsSet("out_trade_no") && !inputObj.IsSet("transaction_id")) 197 { 198 throw new WxPayException("订单查询接口中,out_trade_no、transaction_id至少填一个!"); 199 } 200 NativeConfig nativeConfig = new NativeConfig(paymentId); 201 inputObj.SetValue("appid", nativeConfig.AppId);//公众账号ID 202 inputObj.SetValue("mch_id", nativeConfig.Partner);//商户号 203 inputObj.SetValue("nonce_str", GenerateNonceStr());//随机字符串 204 inputObj.SetValue("sign", inputObj.MakeSign(nativeConfig.Key));//签名 205 string xml = inputObj.ToXml(); 206 var startTime = DateTime.Now; //开始时间 207 string response = HttpService.Post(xml, sendUrl, false, timeOut);//调用HTTP通信接口提交数据 208 var endTime = DateTime.Now; //结束时间 209 int timeCost = (int)((endTime - startTime).TotalMilliseconds); //计算所用时间 210 //将xml格式的数据转化为对象以返回 211 WxPayData result = new WxPayData(); 212 result.FromXml(response, nativeConfig.Key); 213 ReportCostTime(paymentId, sendUrl, timeCost, result);//测速上报 214 return result; 215 } 216 217 } 218 }
以下为扫码支付、H5支付以及公众号支付需要用到的共同类:
HttpService.cs
1 using System; 2 using System.Collections.Generic; 3 using System.Web; 4 using System.Net; 5 using System.IO; 6 using System.Text; 7 using System.Net.Security; 8 using System.Security.Authentication; 9 using System.Security.Cryptography.X509Certificates; 10 11 namespace Gwbnsh.API.Payment.wxpay 12 { 13 /// <summary> 14 /// http连接基础类,负责底层的http通信 15 /// </summary> 16 public class HttpService 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 + WxPayConfig.SSLCERT_PATH, WxPayConfig.SSLCERT_PASSWORD); 68 request.ClientCertificates.Add(cert); 69 }*/ 70 71 //往服务器写入数据 72 reqStream = request.GetRequestStream(); 73 reqStream.Write(data, 0, data.Length); 74 reqStream.Close(); 75 76 //获取服务端返回 77 response = (HttpWebResponse)request.GetResponse(); 78 79 //获取服务端返回数据 80 StreamReader sr = new StreamReader(response.GetResponseStream(), Encoding.UTF8); 81 result = sr.ReadToEnd().Trim(); 82 sr.Close(); 83 } 84 catch (System.Threading.ThreadAbortException e) 85 { 86 System.Threading.Thread.ResetAbort(); 87 } 88 catch (WebException e) 89 { 90 throw new WxPayException(e.ToString()); 91 } 92 catch (Exception e) 93 { 94 throw new WxPayException(e.ToString()); 95 } 96 finally 97 { 98 //关闭连接和流 99 if (response != null) 100 { 101 response.Close(); 102 } 103 if (request != null) 104 { 105 request.Abort(); 106 } 107 } 108 return result; 109 } 110 111 /// <summary> 112 /// 处理http GET请求,返回数据 113 /// </summary> 114 /// <param name="url">请求的url地址</param> 115 /// <returns>http GET成功后返回的数据,失败抛WebException异常</returns> 116 public static string Get(string url) 117 { 118 System.GC.Collect(); 119 string result = ""; 120 121 HttpWebRequest request = null; 122 HttpWebResponse response = null; 123 124 //请求url以获取数据 125 try 126 { 127 //设置最大连接数 128 ServicePointManager.DefaultConnectionLimit = 200; 129 //设置https验证方式 130 if (url.StartsWith("https", StringComparison.OrdinalIgnoreCase)) 131 { 132 ServicePointManager.ServerCertificateValidationCallback = 133 new RemoteCertificateValidationCallback(CheckValidationResult); 134 } 135 136 /*************************************************************** 137 * 下面设置HttpWebRequest的相关属性 138 * ************************************************************/ 139 request = (HttpWebRequest)WebRequest.Create(url); 140 141 request.Method = "GET"; 142 143 //设置代理 144 /*WebProxy proxy = new WebProxy(); 145 proxy.Address = new Uri(WxPayConfig.PROXY_URL); 146 request.Proxy = proxy;*/ 147 148 //获取服务器返回 149 response = (HttpWebResponse)request.GetResponse(); 150 151 //获取HTTP返回数据 152 StreamReader sr = new StreamReader(response.GetResponseStream(), Encoding.UTF8); 153 result = sr.ReadToEnd().Trim(); 154 sr.Close(); 155 } 156 catch (System.Threading.ThreadAbortException e) 157 { 158 System.Threading.Thread.ResetAbort(); 159 } 160 catch (WebException e) 161 { 162 throw new WxPayException(e.ToString()); 163 } 164 catch (Exception e) 165 { 166 throw new WxPayException(e.ToString()); 167 } 168 finally 169 { 170 //关闭连接和流 171 if (response != null) 172 { 173 response.Close(); 174 } 175 if (request != null) 176 { 177 request.Abort(); 178 } 179 } 180 return result; 181 } 182 } 183 }
WxPayData.cs
1 using System; 2 using System.Collections.Generic; 3 using System.Web; 4 using System.Xml; 5 using System.Security.Cryptography; 6 using System.Text; 7 using Gwbnsh.Common; 8 9 namespace Gwbnsh.API.Payment.wxpay 10 { 11 /// <summary> 12 /// 微信支付协议接口数据类,所有的API接口通信都依赖这个数据结构, 13 /// 在调用接口之前先填充各个字段的值,然后进行接口通信, 14 /// 这样设计的好处是可扩展性强,用户可随意对协议进行更改而不用重新设计数据结构, 15 /// 还可以随意组合出不同的协议数据包,不用为每个协议设计一个数据包结构 16 /// </summary> 17 public class WxPayData 18 { 19 public WxPayData() 20 { 21 22 } 23 24 //采用排序的Dictionary的好处是方便对数据包进行签名,不用再签名之前再做一次排序 25 private SortedDictionary<string, object> m_values = new SortedDictionary<string, object>(); 26 27 /** 28 * 设置某个字段的值 29 * @param key 字段名 30 * @param value 字段值 31 */ 32 public void SetValue(string key, object value) 33 { 34 m_values[key] = value; 35 } 36 37 /** 38 * 根据字段名获取某个字段的值 39 * @param key 字段名 40 * @return key对应的字段值 41 */ 42 public object GetValue(string key) 43 { 44 object o = null; 45 m_values.TryGetValue(key, out o); 46 return o; 47 } 48 49 /** 50 * 判断某个字段是否已设置 51 * @param key 字段名 52 * @return 若字段key已被设置,则返回true,否则返回false 53 */ 54 public bool IsSet(string key) 55 { 56 object o = null; 57 m_values.TryGetValue(key, out o); 58 if (null != o) 59 return true; 60 else 61 return false; 62 } 63 64 /** 65 * @将Dictionary转成xml 66 * @return 经转换得到的xml串 67 * @throws WxPayException 68 **/ 69 public string ToXml() 70 { 71 //数据为空时不能转化为xml格式 72 if (0 == m_values.Count) 73 { 74 throw new WxPayException("WxPayData数据为空!"); 75 } 76 77 string xml = "<xml>"; 78 foreach (KeyValuePair<string, object> pair in m_values) 79 { 80 //字段值不能为null,会影响后续流程 81 if (pair.Value == null) 82 { 83 throw new WxPayException("WxPayData内部含有值为null的字段!"); 84 } 85 86 if (pair.Value.GetType() == typeof(int)) 87 { 88 xml += "<" + pair.Key + ">" + pair.Value + "</" + pair.Key + ">"; 89 } 90 else if (pair.Value.GetType() == typeof(string)) 91 { 92 xml += "<" + pair.Key + ">" + "<![CDATA[" + pair.Value + "]]></" + pair.Key + ">"; 93 } 94 else//除了string和int类型不能含有其他数据类型 95 { 96 throw new WxPayException("WxPayData字段数据类型错误!"); 97 } 98 } 99 xml += "</xml>"; 100 return xml; 101 } 102 103 /** 104 * @接收从微信后台POST过来的数据(未验证签名) 105 * @return 经转换得到的Dictionary 106 * @throws WxPayException 107 */ 108 public SortedDictionary<string, object> GetRequest() 109 { 110 //接收从微信后台POST过来的数据 111 System.IO.Stream s = HttpContext.Current.Request.InputStream; 112 int count = 0; 113 byte[] buffer = new byte[1024]; 114 StringBuilder builder = new StringBuilder(); 115 while ((count = s.Read(buffer, 0, 1024)) > 0) 116 { 117 builder.Append(Encoding.UTF8.GetString(buffer, 0, count)); 118 } 119 s.Flush(); 120 s.Close(); 121 s.Dispose(); 122 123 if (string.IsNullOrEmpty(builder.ToString())) 124 { 125 throw new WxPayException("将空的xml串转换为WxPayData不合法!"); 126 } 127 128 XmlDocument xmlDoc = new XmlDocument(); 129 xmlDoc.LoadXml(builder.ToString()); 130 XmlNode xmlNode = xmlDoc.FirstChild;//获取到根节点<xml> 131 XmlNodeList nodes = xmlNode.ChildNodes; 132 foreach (XmlNode xn in nodes) 133 { 134 XmlElement xe = (XmlElement)xn; 135 m_values[xe.Name] = xe.InnerText;//获取xml的键值对到WxPayData内部的数据中 136 } 137 138 return m_values; 139 } 140 141 /** 142 * @将xml转为WxPayData对象并返回对象内部的数据 143 * @param string 待转换的xml串 144 * @return 经转换得到的Dictionary 145 * @throws WxPayException 146 */ 147 public SortedDictionary<string, object> FromXml(string xml, string key) 148 { 149 if (string.IsNullOrEmpty(xml)) 150 { 151 throw new WxPayException("将空的xml串转换为WxPayData不合法!"); 152 } 153 154 XmlDocument xmlDoc = new XmlDocument(); 155 xmlDoc.LoadXml(xml); 156 XmlNode xmlNode = xmlDoc.FirstChild;//获取到根节点<xml> 157 XmlNodeList nodes = xmlNode.ChildNodes; 158 foreach (XmlNode xn in nodes) 159 { 160 XmlElement xe = (XmlElement)xn; 161 m_values[xe.Name] = xe.InnerText;//获取xml的键值对到WxPayData内部的数据中 162 } 163 164 try 165 { 166 //2015-06-29 错误是没有签名 167 if (m_values["return_code"] != "SUCCESS") 168 { 169 return m_values; 170 } 171 CheckSign(key);//验证签名,不通过会抛异常 172 } 173 catch (Exception ex) 174 { 175 throw new WxPayException(ex.Message); 176 } 177 178 return m_values; 179 } 180 /** 181 * @将xml转为WxPayData对象并返回对象内部的数据(未验证签名) 182 * @param string 待转换的xml串 183 * @return 经转换得到的Dictionary 184 * @throws WxPayException 185 */ 186 public SortedDictionary<string, object> FromXml(string xml) 187 { 188 if (string.IsNullOrEmpty(xml)) 189 { 190 throw new WxPayException("将空的xml串转换为WxPayData不合法!"); 191 } 192 193 XmlDocument xmlDoc = new XmlDocument(); 194 xmlDoc.LoadXml(xml); 195 XmlNode xmlNode = xmlDoc.FirstChild;//获取到根节点<xml> 196 XmlNodeList nodes = xmlNode.ChildNodes; 197 foreach (XmlNode xn in nodes) 198 { 199 XmlElement xe = (XmlElement)xn; 200 m_values[xe.Name] = xe.InnerText;//获取xml的键值对到WxPayData内部的数据中 201 } 202 203 try 204 { 205 //2015-06-29 错误是没有签名 206 if (m_values["return_code"] != "SUCCESS") 207 { 208 return m_values; 209 } 210 } 211 catch (Exception ex) 212 { 213 throw new WxPayException(ex.Message); 214 } 215 216 return m_values; 217 } 218 /** 219 * @Dictionary格式转化成url参数格式 220 * @ return url格式串, 该串不包含sign字段值 221 */ 222 public string ToUrl() 223 { 224 string buff = ""; 225 foreach (KeyValuePair<string, object> pair in m_values) 226 { 227 if (pair.Value == null) 228 { 229 throw new WxPayException("WxPayData内部含有值为null的字段!"); 230 } 231 232 if (pair.Key != "sign" && pair.Value.ToString() != "") 233 { 234 buff += pair.Key + "=" + pair.Value + "&"; 235 } 236 } 237 buff = buff.Trim('&'); 238 return buff; 239 } 240 241 242 /** 243 * @Dictionary格式化成Json 244 * @return json串数据 245 */ 246 public string ToJson() 247 { 248 string jsonStr = JsonHelper.ObjectToJSON(m_values); 249 return jsonStr; 250 } 251 252 /** 253 * @values格式化成能在Web页面上显示的结果(因为web页面上不能直接输出xml格式的字符串) 254 */ 255 public string ToPrintStr() 256 { 257 string str = ""; 258 foreach (KeyValuePair<string, object> pair in m_values) 259 { 260 if (pair.Value == null) 261 { 262 throw new WxPayException("WxPayData内部含有值为null的字段!"); 263 } 264 265 str += string.Format("{0}={1}<br>", pair.Key, pair.Value.ToString()); 266 } 267 return str; 268 } 269 270 /** 271 * @生成签名,详见签名生成算法 272 * @return 签名, sign字段不参加签名 273 */ 274 public string MakeSign(string key) 275 { 276 //转url格式 277 string str = ToUrl(); 278 //在string后加入API KEY 279 str += "&key=" + key; 280 //MD5加密 281 var md5 = MD5.Create(); 282 var bs = md5.ComputeHash(Encoding.UTF8.GetBytes(str)); 283 var sb = new StringBuilder(); 284 foreach (byte b in bs) 285 { 286 sb.Append(b.ToString("x2")); 287 } 288 //所有字符转为大写 289 return sb.ToString().ToUpper(); 290 } 291 292 /** 293 * 294 * 检测签名是否正确 295 * 正确返回true,错误抛异常 296 */ 297 public bool CheckSign(string key) 298 { 299 //如果没有设置签名,则跳过检测 300 if (!IsSet("sign")) 301 { 302 throw new WxPayException("WxPayData签名存在但不合法!"); 303 } 304 //如果设置了签名但是签名为空,则抛异常 305 else if (GetValue("sign") == null || GetValue("sign").ToString() == "") 306 { 307 throw new WxPayException("WxPayData签名存在但不合法!"); 308 } 309 310 //获取接收到的签名 311 string return_sign = GetValue("sign").ToString(); 312 313 //在本地计算新的签名 314 string cal_sign = MakeSign(key); 315 316 if (cal_sign == return_sign) 317 { 318 return true; 319 } 320 321 throw new WxPayException("WxPayData签名验证错误!"); 322 } 323 324 /** 325 * @获取Dictionary 326 */ 327 public SortedDictionary<string, object> GetValues() 328 { 329 return m_values; 330 } 331 } 332 }
WxPayException.cs
1 using System; 2 using System.Collections.Generic; 3 using System.Web; 4 5 namespace Gwbnsh.API.Payment.wxpay 6 { 7 public class WxPayException : Exception 8 { 9 public WxPayException(string msg) : base(msg) 10 { 11 12 } 13 } 14 }
最后,总结一下上述几种支付方式需要注意的点。
1. 所有的支付参数都需要到微信支付商户平台(pay.weixin.qq.com)配置参数。
2. 微信公众号支付、微信扫码支付需要在微信公众号里面申请开通;H5支付需要在微信商户平台申请开通。
3. 仅有公众号支付和扫码支付一模式需配置支付域名,H5无需配置域名,但是使用的网站域名和申请时填写的要一致。
4. 所有使用JS API方式发起支付请求的链接地址,都必须在当前页面所配置的支付授权目录之下。
5. 当公众平台接到扫码支付请求时,会回调当前页面所配置的支付回调链接传递订单信息。
以下为源码,包含aspx页面文件和详细使用说明:下载
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Linux glibc自带哈希表的用例及性能测试
· 深入理解 Mybatis 分库分表执行原理
· 如何打造一个高并发系统?
· .NET Core GC压缩(compact_phase)底层原理浅谈
· 现代计算机视觉入门之:什么是图片特征编码
· 手把手教你在本地部署DeepSeek R1,搭建web-ui ,建议收藏!
· Spring AI + Ollama 实现 deepseek-r1 的API服务和调用
· 数据库服务器 SQL Server 版本升级公告
· 程序员常用高效实用工具推荐,办公效率提升利器!
· C#/.NET/.NET Core技术前沿周刊 | 第 23 期(2025年1.20-1.26)