最近负责的一些项目开发,都用到了微信支付(微信公众号支付、微信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页面文件和详细使用说明:下载
Copyright © 广州京杭网络科技有限公司 2005-2024 版权所有 粤ICP备16019765号
广州京杭网络科技有限公司 版权所有