1、概要
公众号是以微信用户的一个联系人形式存在的,支付是微信服务号的核心一环。
本篇主要介绍微信支付这一功能,避免大家再跳微信支付的坑。
1.1 关于Magicodes.WeChat.SDK
MAGICODES.WECHAT.SDK为心莱团队封装的轻量级微信SDK,现已全部开源,开源库地址为:https://github.com/xin-lai/Magicodes.WeChat.SDK
更多介绍,请关注后续博客。
2、微信公众号支付
用户已有商城网址,用户通过微信消息、微信扫描二维码、微信自定义菜单等操作在微信内打开网页时,可以调用微信支付完成下单购买流程。
2.1 支付流程
2.1.1 微信网页支付流程
备注:实际流程其实非常简单
① 用户支付之前,程序生成订单并调用统一下单API()
② 微信系统返回预付单信息
③ 根据信息生成JSAPI页面调用的支付参数并签名,jssdk调用
④ 用户支付,jsapi向微信系统发送请求
⑤ 微信系统返回支付结果给用户,同时异步发送结果给程序后台(程序没有收到通知,可以调用查询接口)
⑥ 支付完成,用户界面根据结果做相关页面跳转或提示处理,程序后台根据通知做订单状态变更等逻辑处理。
2.1.2 刷卡支付
后续更新
2.1.3 扫码支付
后续更新
2.1.4 app支付
后续更新
2.2 注意事项
2.3 开发实践
2.3.1 开发配置
1、设置测试目录
在微信公众平台设置。支付测试状态下,设置测试目录,测试人的微信号添加到白名单,发起支付的页面目录必须与设置的精确匹配。并将支付链接发到对应的公众号会话窗口中才能正常发起支付测试。注意正式目录一定不能与测试目录设置成一样,否则支付会出错。
友情提示:如果是使用测试目录的地址,一定要记得把个人测试微信号添加到白名单。
2、设置正式支付目录
根据图中栏目顺序进入修改栏目,勾选JSAPI网页支付开通该权限,并配置好支付授权目录,该目录必须是发起支付的页面的精确目录,子目录下无法正常调用支付。具体界面如图:
友情提示:注意红色框框里面的说明,一不小心会很容易进坑的。
2.3.2 开发程序
直接看代码吧
微信支付业务类
1 /// <summary>
2 /// 微信支付接口,官方API:https://mp.weixin.qq.com/paymch/readtemplate?t=mp/business/course2_tmpl&lang=zh_CN&token=25857919#4
3
4 /// </summary>
5 public class TenPayV3 : PayBase
6
7 {
8 public UnifiedorderResult Unifiedorder(UnifiedorderRequest model)
9
10 {
11
12 var url = "https://api.mch.weixin.qq.com/pay/unifiedorder";
13
14
15
16
17
18 UnifiedorderResult result = null;
19
20
21
22 model.AppId = WeiChatConfig.AppId;
23
24 model.MchId = PayConfig.MchId;
25
26 if (model.NotifyUrl == null)
27
28 model.NotifyUrl = PayConfig.Notify;
29
30 Dictionary<string, string> dictionary = PayUtil.GetAuthors<UnifiedorderRequest>(model);
31
32 model.Sign = PayUtil.CreateMd5Sign(dictionary, PayConfig.TenPayKey);//生成Sign
33
34 Dictionary<string, string> dict = PayUtil.GetAuthors<UnifiedorderRequest>(model);
35
36 result = PostXML<UnifiedorderResult>(url, model);
37
38 return result;
39
40 }
41
42
43
44 /// <summary>
45
46 /// 订单查询接口
47
48 /// </summary>
49
50 /// <param name="data"></param>
51
52 /// <returns></returns>
53
54 public static string OrderQuery(string data)
55
56 {
57
58 var urlFormat = "https://api.mch.weixin.qq.com/pay/orderquery";
59
60
61
62 var formDataBytes = data == null ? new byte[0] : Encoding.UTF8.GetBytes(data);
63
64 using (MemoryStream ms = new MemoryStream())
65
66 {
67
68 ms.Write(formDataBytes, 0, formDataBytes.Length);
69
70 ms.Seek(0, SeekOrigin.Begin);//设置指针读取位置
71
72 return RequestUtility.HttpPost(urlFormat, null, ms);
73
74 }
75
76 }
77
78
79
80 /// <summary>
81
82 /// 关闭订单接口
83
84 /// </summary>
85
86 /// <param name="data">关闭订单需要post的xml数据</param>
87
88 /// <returns></returns>
89
90 public static string CloseOrder(string data)
91
92 {
93
94 var urlFormat = "https://api.mch.weixin.qq.com/pay/closeorder";
95
96
97
98 var formDataBytes = data == null ? new byte[0] : Encoding.UTF8.GetBytes(data);
99
100 using (MemoryStream ms = new MemoryStream())
101
102 {
103
104 ms.Write(formDataBytes, 0, formDataBytes.Length);
105
106 ms.Seek(0, SeekOrigin.Begin);//设置指针读取位置
107
108 return RequestUtility.HttpPost(urlFormat, null, ms);
109
110 }
111
112 }
113
114
115
116
117
118
119
120 /// <summary>
121
122 /// 退款查询接口
123
124 /// </summary>
125
126 /// <param name="data"></param>
127
128 /// <returns></returns>
129
130 public static string RefundQuery(string data)
131
132 {
133
134 var urlFormat = "https://api.mch.weixin.qq.com/pay/refundquery";
135
136
137
138 var formDataBytes = data == null ? new byte[0] : Encoding.UTF8.GetBytes(data);
139
140 using (MemoryStream ms = new MemoryStream())
141
142 {
143
144 ms.Write(formDataBytes, 0, formDataBytes.Length);
145
146 ms.Seek(0, SeekOrigin.Begin);//设置指针读取位置
147
148 return RequestUtility.HttpPost(urlFormat, null, ms);
149
150 }
151
152 }
153
154
155
156 /// <summary>
157
158 /// 对账单接口
159
160 /// </summary>
161
162 /// <param name="data"></param>
163
164 /// <returns></returns>
165
166 public static string DownloadBill(string data)
167
168 {
169
170 var urlFormat = "https://api.mch.weixin.qq.com/pay/downloadbill";
171
172
173
174 var formDataBytes = data == null ? new byte[0] : Encoding.UTF8.GetBytes(data);
175
176 using (MemoryStream ms = new MemoryStream())
177
178 {
179
180 ms.Write(formDataBytes, 0, formDataBytes.Length);
181
182 ms.Seek(0, SeekOrigin.Begin);//设置指针读取位置
183
184 return RequestUtility.HttpPost(urlFormat, null, ms);
185
186 }
187
188 }
189
190
191
192 /// <summary>
193
194 /// 短链接转换接口
195
196 /// </summary>
197
198 /// <param name="data"></param>
199
200 /// <returns></returns>
201
202 public static string ShortUrl(string data)
203
204 {
205
206 var urlFormat = "https://api.mch.weixin.qq.com/tools/shorturl";
207
208
209
210 var formDataBytes = data == null ? new byte[0] : Encoding.UTF8.GetBytes(data);
211
212 using (MemoryStream ms = new MemoryStream())
213
214 {
215
216 ms.Write(formDataBytes, 0, formDataBytes.Length);
217
218 ms.Seek(0, SeekOrigin.Begin);//设置指针读取位置
219
220 return RequestUtility.HttpPost(urlFormat, null, ms);
221
222 }
223
224 }
225
226 /// <summary>
227
228 ///
229
230 /// </summary>
231
232 /// <param name="page"></param>
233
234 /// <returns></returns>
235
236 public NotifyResult Notify(Stream inputStream)
237
238 {
239
240 NotifyResult result = null;
241
242 string data = PayUtil.PostInput(inputStream);
243
244 result = XmlHelper.DeserializeObject<NotifyResult>(data);
245
246 return result;
247
248 }
249
250 /// <summary>
251
252 /// 通知并返回处理XML
253
254 /// </summary>
255
256 /// <param name="inputStream">输入流</param>
257
258 /// <param name="successAction">成功处理逻辑回调函数</param>
259
260 /// <param name="failAction">失败处理逻辑回调函数</param>
261
262 /// <param name="successMsg">成功返回消息</param>
263
264 /// <param name="errorMsg">失败返回消息</param>
265
266 /// <param name="isSync">是否异步执行相关处理逻辑</param>
267
268 /// <returns></returns>
269
270 public string NotifyAndReurnResult(Stream inputStream, Action<NotifyResult> successAction, Action<NotifyResult> failAction, string successMsg = "OK", string errorMsg = "FAIL", bool isSync = true)
271
272 {
273
274 var result = Notify(inputStream);
275
276 var request = new NotifyRequest();
277
278 request.ReturnCode = "FAIL";
279
280 if (result.IsSuccess())
281
282 {
283
284 if (isSync)
285
286 Task.Run(() => successAction(result));
287
288 else
289
290 successAction.Invoke(result);
291
292 //交易成功
293
294 request.ReturnCode = "SUCCESS";
295
296 request.ReturnMsg = successMsg;
297
298 return XmlHelper.SerializeObject(request);
299
300 }
301
302 else
303
304 {
305
306 if (isSync)
307
308 Task.Run(() => failAction(result));
309
310 else
311
312 failAction.Invoke(result);
313
314 request.ReturnMsg = errorMsg;
315
316 return XmlHelper.SerializeObject(request);
317
318 }
319
320 }
321
322 }
323
324 }
把返回参数和请求参数,序列化成对象,方便我们在编写我们本身逻辑的时候调用
1 [XmlRoot("xml")]
2 [Serializable()]
3 public class Result : PayResult
4 {
5 /// <summary>
6 /// 微信分配的公众账号ID
7 /// </summary>
8 [XmlElement("appid")]
9 public string AppId { get; set; }
10 /// <summary>
11 /// 微信支付分配的商户号
12 /// </summary>
13 [XmlElement("mch_id")]
14 public string Mch_Id { get; set; }
15 /// <summary>
16 /// 微信支付分配的终端设备号
17 /// </summary>
18 [XmlElement("device_info")]
19 public string Device_Info { get; set; }
20 /// <summary>
21 /// 随机字符串,不长于32 位
22 /// </summary>
23 [XmlElement("nonce_str")]
24 public string NonceStr { get; set; }
25 /// <summary>
26 /// 签名
27 /// </summary>
28 [XmlElement("sign")]
29 public string Sign { get; set; }
30 /// <summary>
31 /// SUCCESS/FAIL
32 /// </summary>
33 [XmlElement("result_code")]
34 public string ResultCode { get; set; }
35 [XmlElement("err_code")]
36 public string ErrCode { get; set; }
37 [XmlElement("err_code_des")]
38 public string ErrCodeDes { get; set; }
39 }
40
41 [XmlRoot("xml")]
42 [Serializable()]
43 public class UnifiedorderResult : Result
44 {
45 /// <summary>
46 /// 交易类型:JSAPI、NATIVE、APP
47 /// </summary>
48 [XmlElement("trade_type")]
49 public string TradeType { get; set; }
50 /// <summary>
51 /// 微信生成的预支付ID,用于后续接口调用中使用
52 /// </summary>
53 [XmlElement("prepay_id")]
54 public string PrepayId { get; set; }
55 /// <summary>
56 /// trade_type为NATIVE时有返回,此参数可直接生成二维码展示出来进行扫码支付
57 /// </summary>
58 [XmlElement("code_url")]
59 public string CodeUrl { get; set; }
60 }
61
62
63 [XmlRoot("xml")]
64 [Serializable()]
65 public class UnifiedorderRequest
66 {
67 /// <summary>
68 /// OpenId
69 /// </summary>
70 [XmlElement("openid")]
71 public string OpenId { get; set; }
72 /// <summary>
73 /// 【不用填写】微信开放平台审核通过的应用APPID
74 /// </summary>
75 [XmlElement("appid")]
76 public string AppId { get; set; }
77 /// <summary>
78 /// 【不用填写】微信支付分配的商户号
79 /// </summary>
80 [XmlElement("mch_id")]
81 public string MchId { get; set; }
82
83 /// <summary>
84 /// 终端设备号(门店号或收银设备ID),默认请传"WEB"
85 /// </summary>
86 [XmlElement("device_info")]
87 public string DeviceInfo { get; set; }
88 /// <summary>
89 /// 随机字符串,不长于32位
90 /// </summary>
91 [XmlElement("nonce_str")]
92 public string NonceStr { get; set; }
93 /// <summary>
94 /// 【不用填写】签名
95 /// </summary>
96 [XmlElement("sign")]
97 public string Sign { get; set; }
98 /// <summary>
99 /// 商品描述交易字段格式根据不同的应用场景按照以下格式: APP——需传入应用市场上的APP名字-实际商品名称,天天爱消除-游戏充值。
100 /// </summary>
101 [XmlElement("body")]
102 public string Body { get; set; }
103 /// <summary>
104 /// 商品名称明细列表
105 /// </summary>
106 [XmlElement("detail")]
107 public string Detail { get; set; }
108 /// <summary>
109 /// 附加数据,在查询API和支付通知中原样返回,该字段主要用于商户携带订单的自定义数据
110 /// </summary>
111 [XmlElement("attach")]
112 public string Attach { get; set; }
113 /// <summary>
114 /// 商户系统内部的订单号,32个字符内、可包含字母, 其他说明见商户订单号
115 /// </summary>
116 [XmlElement("out_trade_no")]
117 public string OutTradeNo { get; set; }
118 /// <summary>
119 /// 符合ISO 4217标准的三位字母代码,默认人民币:CNY
120 /// </summary>
121 [XmlElement("fee_type")]
122 public string FeeType { get; set; }
123 /// <summary>
124 /// 订单总金额,单位为分
125 /// </summary>
126 [XmlElement("total_fee")]
127 public string TotalFee { get; set; }
128 /// <summary>
129 /// 用户端实际ip
130 /// </summary>
131 [XmlElement("spbill_create_ip")]
132 public string SpbillCreateIp { get; set; }
133 /// <summary>
134 /// 订单生成时间,格式为yyyyMMddHHmmss,如2009年12月25日9点10分10秒表示为20091225091010。
135 /// </summary>
136 [XmlElement("time_start")]
137 public string TimeStart { get; set; }
138 /// <summary>
139 /// 订单失效时间,格式为yyyyMMddHHmmss,如2009年12月27日9点10分10秒表示为20091227091010
140 /// </summary>
141 [XmlElement("time_expire")]
142 public string TimeExpire { get; set; }
143 /// <summary>
144 /// 商品标记,代金券或立减优惠功能的参数,说明详见代金券或立减优惠
145 /// </summary>
146 [XmlElement("goods_tag")]
147 public string GoodsTag { get; set; }
148 /// <summary>
149 /// 【不用填写】接收微信支付异步通知回调地址,通知url必须为直接可访问的url,不能携带参数
150 /// </summary>
151 [XmlElement("notify_url")]
152 public string NotifyUrl { get; set; }
153 /// <summary>
154 /// 支付类型(JSAPI,NATIVE,APP)公众号内支付填JSAPI
155 /// </summary>
156 [XmlElement("trade_type")]
157 public string TradeType { get; set; }
158 /// <summary>
159 /// no_credit--指定不能使用信用卡支付
160 /// </summary>
161 [XmlElement("limit_pay")]
162 public string LimitPay { get; set; }
163 }
164 [XmlRoot("xml")]
165 [Serializable()]
166 public class NotifyResult : PayResult
167 {
168 /// <summary>
169 /// 微信分配的公众账号ID(企业号corpid即为此appId)
170 /// </summary>
171 [XmlElement("appid")]
172 public string AppId { get; set; }
173 /// <summary>
174 /// 微信支付分配的商户号
175 /// </summary>
176 [XmlElement("mch_id")]
177 public string MchId { get; set; }
178 /// <summary>
179 /// 微信支付分配的终端设备号
180 /// </summary>
181 [XmlElement("device_info")]
182 public string DeviceInfo { get; set; }
183 /// <summary>
184 /// 随机字符串,不长于32位
185 /// </summary>
186 [XmlElement("nonce_str")]
187 public string NonceStr { get; set; }
188 /// <summary>
189 /// 签名
190 /// </summary>
191 [XmlElement("sign")]
192 public string Sign { get; set; }
193 /// <summary>
194 /// 业务结果,SUCCESS/FAIL
195 /// </summary>
196 [XmlElement("result_code")]
197 public string ResultCode { get; set; }
198 /// <summary>
199 /// 错误返回的信息描述
200 /// </summary>
201 [XmlElement("err_code")]
202 public string ErrCode { get; set; }
203 /// <summary>
204 /// 错误返回的信息描述
205 /// </summary>
206 [XmlElement("err_code_des")]
207 public string ErrCodeDes { get; set; }
208 /// <summary>
209 /// 用户在商户appid下的唯一标识
210 /// </summary>
211 [XmlElement("openid")]
212 public string OpenId { get; set; }
213 /// <summary>
214 /// 用户是否关注公众账号,Y-关注,N-未关注,仅在公众账号类型支付有效
215 /// </summary>
216 [XmlElement("is_subscribe")]
217 public string IsSubscribe { get; set; }
218 /// <summary>
219 /// 交易类型,JSAPI、NATIVE、APP
220 /// </summary>
221 [XmlElement("trade_type")]
222 public string TradeType { get; set; }
223 /// <summary>
224 /// 银行类型,采用字符串类型的银行标识,银行类型见银行列表
225 /// </summary>
226 [XmlElement("bank_type")]
227 public string BankType { get; set; }
228 /// <summary>
229 /// 订单总金额,单位为分
230 /// </summary>
231 [XmlElement("total_fee")]
232 public string TotalFee { get; set; }
233 /// <summary>
234 /// 应结订单金额=订单金额-非充值代金券金额,应结订单金额<=订单金额
235 /// </summary>
236 [XmlElement("settlement_total_fee")]
237 public string SettlementTotalFee { get; set; }
238 /// <summary>
239 /// 货币类型,符合ISO4217标准的三位字母代码,默认人民币:CNY,其他值列表详见货币类型
240 /// </summary>
241 [XmlElement("fee_type")]
242 public string FeeType { get; set; }
243 /// <summary>
244 /// 货币类型现金支付金额订单现金支付金额,详见支付金额
245 /// </summary>
246 [XmlElement("cash_fee")]
247 public string CashFee { get; set; }
248 /// <summary>
249 /// 货币类型,符合ISO4217标准的三位字母代码,默认人民币:CNY,其他值列表详见货币类型
250 /// </summary>
251 [XmlElement("cash_fee_type")]
252 public string CashFeeType { get; set; }
253 /// <summary>
254 /// 代金券金额<=订单金额,订单金额-代金券金额=现金支付金额,详见支付金额]
255 /// </summary>
256 [XmlElement("coupon_fee")]
257 public string CouponFee { get; set; }
258 /// <summary>
259 /// 代金券使用数量
260 /// </summary>
261 [XmlElement("coupon_count")]
262 public string CouponCount { get; set; }
263 /// <summary>
264 /// CASH--充值代金券 NO_CASH---非充值代金券 订单使用代金券时有返回(取值:CASH、NO_CASH)。$n为下标,从0开始编号,举例:coupon_type_$0
265 /// </summary>
266 [XmlElement("coupon_type_$n")]
267 public string CouponTypeN { get; set; }
268 /// <summary>
269 /// 代金券ID,$n为下标,从0开始编号
270 /// </summary>
271 [XmlElement("coupon_id_$n")]
272 public string CouponIdN { get; set; }
273 /// <summary>
274 /// 单个代金券支付金额,$n为下标,从0开始编号
275 /// </summary>
276 [XmlElement("coupon_fee_$n")]
277 public string CouponFeeN { get; set; }
278 /// <summary>
279 /// 微信支付订单号
280 /// </summary>
281 [XmlElement("transaction_id")]
282 public string TransactionId { get; set; }
283 /// <summary>
284 /// 商户系统的订单号,与请求一致
285 /// </summary>
286 [XmlElement("out_trade_no")]
287 public string OutTradeNo { get; set; }
288 /// <summary>
289 /// 商家数据包,原样返回
290 /// </summary>
291 [XmlElement("attach")]
292 public string Attach { get; set; }
293 /// <summary>
294 /// 支付完成时间,格式为yyyyMMddHHmmss,如2009年12月25日9点10分10秒表示为20091225091010。其他详见时间规则
295 /// </summary>
296 [XmlElement("time_end")]
297 public string TimeEnd { get; set; }
298 }
299 [XmlRoot("xml")]
300 [Serializable()]
301 public class NotifyRequest
302 {
303 /// <summary>
304 /// SUCCESS/FAIL SUCCESS表示商户接收通知成功并校验成功
305 /// </summary>
306 [XmlElement("return_code")]
307 public string ReturnCode { get; set; }
308 /// <summary>
309 /// 返回信息,如非空,为错误原因:签名失败 参数格式校验错误
310 /// </summary>
311 [XmlElement("return_msg")]
312 public string ReturnMsg { get; set; }
313 }
这些地方是获取微信支付的配置信息,appid,machid,密钥等信息。由于我们项目本身涉及到了多租户功能,所以Key是租户Id,这里通过Key获取不能租户的配置信息,如果不要实现多租户,默认不填写就OK,然后我们也可以通过把配置信息封装成一个对象写死在这里。具体源码可以去http:githup.com/xin-lai下载。全部功能已开源。
1 /// <summary>
2
3 /// POST提交请求,返回ApiResult对象
4
5 /// </summary>
6
7 /// <typeparam name="T">ApiResult对象</typeparam>
8
9 /// <param name="url">请求地址</param>
10
11 /// <param name="obj">提交的数据对象</param>
12
13 /// <returns>ApiResult对象</returns>
14
15 protected T PostXML<T>(string url, object obj, Func<string, string> serializeStrFunc = null) where T : PayResult
16
17 {
18
19 var wr = new WeChatApiWebRequestHelper();
20
21 string resultStr = null;
22
23 var result = wr.HttpPost<T>(url, obj, out resultStr, serializeStrFunc, inputDataType: WebRequestDataTypes.XML, outDataType: WebRequestDataTypes.XML);
24
25 if (result != null)
26
27 {
28
29 result.DetailResult = resultStr;
30
31 }
32
33 return result;
34
35 }
36
37 /// <summary>
38
39 /// POST提交请求,带证书,返回ApiResult对象
40
41 /// </summary>
42
43 /// <typeparam name="T">ApiResult对象</typeparam>
44
45 /// <param name="url">请求地址</param>
46
47 /// <param name="obj">提交的数据对象</param>
48
49 /// <returns>ApiResult对象</returns>
50
51 protected T PostXML<T>(string url, object obj, X509Certificate2 cer, Func<string, string> serializeStrFunc = null) where T : PayResult
52
53 {
54
55 var wr = new WeChatApiWebRequestHelper();
56
57 string resultStr = null;
58
59 var result = wr.HttpPost<T>(url, obj,cer, out resultStr, serializeStrFunc, inputDataType: WebRequestDataTypes.XML, outDataType: WebRequestDataTypes.XML);
60
61 if (result != null)
62
63 {
64
65 result.DetailResult = resultStr;
66
67 }
68
69 return result;
70
71 }
72
73 }
PayUtiy类,封装了一些公共方法
1 public static class PayUtil
2
3 {
4
5 /// <summary>
6
7 /// 随机生成Noncestr
8
9 /// </summary>
10
11 /// <returns></returns>
12
13 public static string GetNoncestr()
14
15 {
16
17 Random random = new Random();
18
19 return MD5UtilHelper.GetMD5(random.Next(1000).ToString(), "GBK");
20
21 }
22
23 /// <summary>
24
25 /// 根据当前系统时间加随机序列来生成订单号
26
27 /// </summary>
28
29 /// <returns>订单号</returns>
30
31 public static string GenerateOutTradeNo()
32
33 {
34
35 var ran = new Random();
36
37 return string.Format("{0}{1}", UnixStamp(), ran.Next(999));
38
39 }
40
41 /// <summary>
42
43 /// 获取时间戳
44
45 /// </summary>
46
47 /// <returns></returns>
48
49 public static string GetTimestamp()
50
51 {
52
53 TimeSpan ts = DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, 0);
54
55 return Convert.ToInt64(ts.TotalSeconds).ToString();
56
57 }
58
59
60
61 /// <summary>
62
63 /// 对字符串进行URL编码
64
65 /// </summary>
66
67 /// <param name="instr"></param>
68
69 /// <param name="charset"></param>
70
71 /// <returns></returns>
72
73 public static string UrlEncode(string instr, string charset)
74
75 {
76
77 //return instr;
78
79 if (instr == null || instr.Trim() == "")
80
81 return "";
82
83 else
84
85 {
86
87 string res;
88
89
90
91 try
92
93 {
94
95 res = System.Web.HttpUtility.UrlEncode(instr, Encoding.GetEncoding(charset));
96
97
98
99 }
100
101 catch (Exception ex)
102
103 {
104
105 res = System.Web.HttpUtility.UrlEncode(instr, Encoding.GetEncoding("GB2312"));
106
107 }
108
109
110
111
112
113 return res;
114
115 }
116
117 }
118
119
120
121 /// <summary>
122
123 /// 对字符串进行URL解码
124
125 /// </summary>
126
127 /// <param name="instr"></param>
128
129 /// <param name="charset"></param>
130
131 /// <returns></returns>
132
133 public static string UrlDecode(string instr, string charset)
134
135 {
136
137 if (instr == null || instr.Trim() == "")
138
139 return "";
140
141 else
142
143 {
144
145 string res;
146
147
148
149 try
150
151 {
152
153 res = System.Web.HttpUtility.UrlDecode(instr, Encoding.GetEncoding(charset));
154
155
156
157 }
158
159 catch (Exception ex)
160
161 {
162
163 res = System.Web.HttpUtility.UrlDecode(instr, Encoding.GetEncoding("GB2312"));
164
165 }
166
167
168
169
170
171 return res;
172
173
174
175 }
176
177 }
178
179
180
181
182
183 /// <summary>
184
185 /// 取时间戳生成随即数,替换交易单号中的后10位流水号
186
187 /// </summary>
188
189 /// <returns></returns>
190
191 public static UInt32 UnixStamp()
192
193 {
194
195 TimeSpan ts = DateTime.Now - TimeZone.CurrentTimeZone.ToLocalTime(new DateTime(1970, 1, 1));
196
197 return Convert.ToUInt32(ts.TotalSeconds);
198
199 }
200
201 /// <summary>
202
203 /// 取随机数
204
205 /// </summary>
206
207 /// <param name="length"></param>
208
209 /// <returns></returns>
210
211 public static string BuildRandomStr(int length)
212
213 {
214
215 Random rand = new Random();
216
217
218
219 int num = rand.Next();
220
221
222
223 string str = num.ToString();
224
225
226
227 if (str.Length > length)
228
229 {
230
231 str = str.Substring(0, length);
232
233 }
234
235 else if (str.Length < length)
236
237 {
238
239 int n = length - str.Length;
240
241 while (n > 0)
242
243 {
244
245 str.Insert(0, "0");
246
247 n--;
248
249 }
250
251 }
252
253
254
255 return str;
256
257 }
258
259 /// <summary>
260
261 /// 循环获取一个实体类每个字段的XmlAttribute属性的值
262
263 /// </summary>
264
265 /// <typeparam name="T"></typeparam>
266
267 /// <returns></returns>
268
269 public static Dictionary<string, string> GetAuthors<T>(T model)
270
271 {
272
273 Dictionary<string, string> _dict = new Dictionary<string, string>();
274
275
276
277 Type type = model.GetType(); //获取类型
278
279
280
281 PropertyInfo[] props = typeof(T).GetProperties();
282
283 foreach (PropertyInfo prop in props)
284
285 {
286
287 object[] attrs = prop.GetCustomAttributes(true);
288
289 foreach (object attr in attrs)
290
291 {
292
293 XmlElementAttribute authAttr = attr as XmlElementAttribute;
294
295 if (authAttr != null)
296
297 {
298
299 string auth = authAttr.ElementName;
300
301
302
303 PropertyInfo property = type.GetProperty(prop.Name);
304
305 string value = (string)property.GetValue(model, null); //获取属性值
306
307
308
309 _dict.Add(auth, value);
310
311 }
312
313 }
314
315 }
316
317 return _dict;
318
319 }
320
321
322
323 /// <summary>
324
325 /// 创建md5摘要,规则是:按参数名称a-z排序,遇到空值的参数不参加签名
326
327 /// </summary>
328
329 /// <param name="key">参数名</param>
330
331 /// <param name="value">参数值</param>
332
333 /// key和value通常用于填充最后一组参数
334
335 /// <returns></returns>
336
337 public static string CreateMd5Sign(Dictionary<string, string> dict, string value)
338
339 {
340
341 ArrayList akeys = new ArrayList();
342
343 foreach (var x in dict)
344
345 {
346
347 if ("sign".CompareTo(x.Key) == 0)
348
349 continue;
350
351 akeys.Add(x.Key);
352
353 }
354
355 StringBuilder sb = new StringBuilder();
356
357 akeys.Sort();
358
359
360
361 foreach (string k in akeys)
362
363 {
364
365 string v = (string)dict[k];
366
367 if (null != v && "".CompareTo(v) != 0
368
369 && "sign".CompareTo(k) != 0 && "key".CompareTo(k) != 0)
370
371 {
372
373 sb.Append(k + "=" + v + "&");
374
375 }
376
377 }
378
379 sb.Append("key=" + value);
380
381 var md5 = MD5.Create();
382
383 var bs = md5.ComputeHash(Encoding.UTF8.GetBytes(sb.ToString()));
384
385 var sbuilder = new StringBuilder();
386
387 foreach (byte b in bs)
388
389 {
390
391 sbuilder.Append(b.ToString("x2"));
392
393 }
394
395 //所有字符转为大写
396
397 return sbuilder.ToString().ToUpper();
398
399 }
400
401 /// <summary>
402
403 /// 接收post数据
404
405 /// </summary>
406
407 /// <param name="context"></param>
408
409 /// <returns></returns>
410
411 public static string PostInput(Stream stream)
412
413 {
414
415 int count = 0;
416
417 byte[] buffer = new byte[1024];
418
419 StringBuilder builder = new StringBuilder();
420
421 while ((count = stream.Read(buffer, 0, 1024)) > 0)
422
423 {
424
425 builder.Append(Encoding.UTF8.GetString(buffer, 0, count));
426
427 }
428
429 return builder.ToString();
430
431 }
432
433 }
434
435 PayResult类,请求参数基类
436
437 [XmlRoot("xml")]
438
439 [Serializable()]
440
441 public class PayResult
442
443 {
444
445 public virtual bool IsSuccess()
446
447 {
448
449 return this.ReturnCode == "SUCCESS";
450
451 }
452
453 /// <summary>
454
455 /// 返回状态码
456
457 /// SUCCESS/FAIL,此字段是通信标识,非交易标识,交易是否成功需要查看result_code来判断
458
459 /// </summary>
460
461 [XmlElement("return_code")]
462
463 public string ReturnCode { get; set; }
464
465
466
467 /// <summary>
468
469 /// 返回信息,返回信息,如非空,为错误原因,签名失败,参数格式校验错误
470
471 /// </summary>
472
473 [XmlElement("return_msg")]
474
475 public string Message { get; set; }
476
477
478
479 /// <summary>
480
481 /// 详细内容
482
483 /// </summary>
484
485 [XmlIgnore]
486
487 public string DetailResult { get; set; }
488
489 }
支付完成之后,异步回掉的处理
1 /// <summary>
2
3 /// 微信支付回调地址
4
5 /// </summary>
6
7 /// <param name="tenantId"></param>
8
9 /// <returns></returns>
10
11 [Route("PayNotify/{tenantId}")]
12
13 [AllowAnonymous]
14
15 public ActionResult PayNotify(int tenantId)
16
17 {
18
19 Action<NotifyResult> successAction = (result) =>
20
21 {
22
23 using (var context = new AppDbContext())
24
25 {
26
27 var order = context.Order_Infos.FirstOrDefault(o => o.Code == result.OutTradeNo);
28
29 if (null != order)
30
31 {
32
33 //修改订单状态
34
35 order.State = EnumOrderStatus.Overhang;
36
37 order.ThirdPayType = EnumThirdPayType.WX;
38
39 order.PaymentOn = DateTime.Now;
40
41 order.UpdateTime = DateTime.Now;
42
43 context.SaveChanges();
44
45
46
47
48
49 }
50
51 else
52
53 {
54
55 logger.Log(LoggerLevels.Debug, "Order information does not exist!");
56
57 }
58
59 }
60
61 //此处编写成功处理逻辑
62
63 logger.Log(LoggerLevels.Debug, "Success: JSON:" + JsonConvert.SerializeObject(result));
64
65 };
66
67 Action<NotifyResult> failAction = (result) =>
68
69 {
70
71 //此处编写失败处理逻辑
72
73 logger.Log(LoggerLevels.Debug, "Fail: JSON:" + JsonConvert.SerializeObject(result));
74
75 };
76
77 return Content(WeiChatApisContext.Current.TenPayV3Api.NotifyAndReurnResult(Request.InputStream, successAction, failAction));
78
79 }
当前是mvc项目,所以我建了一个webapi控制器,路由地址是http://xx.com/api/ PayNotify/{tenantId},tenantId是租户id,非必填,但是如果没有,要记得把路由里面的参数也去掉。这个地址就是本文前面配置的回调地址,一定要配置正确
然后是调用微信的统一下单的方法
1 /// <summary>
2
3 /// 微信支付(统一下单)
4
5 /// </summary>
6
7 /// <param name="id"></param>
8
9 /// <returns></returns>
10
11 [HttpGet]
12
13 [Route("Pay/{id}")]
14
15 public IHttpActionResult WechatPay(Guid id)
16
17 {
18
19 try
20
21 {
22
23 //查询订单
24
25 var order = db.Order_Infos.SingleOrDefault(o => o.Id == id && o.OpenId == WeiChatApplicationContext.Current.WeiChatUser.OpenId);
26
27 if (null == order)
28
29 return BadRequest("订单信息不存在");
30
31 #region 统一下单
32
33 var model = new UnifiedorderRequest();
34
35 model.OpenId = WeiChatApplicationContext.Current.WeiChatUser.OpenId;
36
37 model.SpbillCreateIp = "8.8.8.8";
38
39 model.OutTradeNo = order.Code;
40
41 model.TotalFee = Convert.ToInt32((order.TotalPrice + order.Shipping) * 100).ToString();
42
43 model.NonceStr = PayUtil.GetNoncestr();
44
45 model.TradeType = "JSAPI";
46
47 model.Body = "购买商品";
48
49 model.DeviceInfo = "WEB";
50
51 var result = WeiChatApisContext.Current.TenPayV3Api.Unifiedorder(model);
52
53
54
55 Dictionary<string, string> _dict = new Dictionary<string, string>();
56
57 _dict.Add("appId", result.AppId);
58
59 _dict.Add("timeStamp", PayUtil.GetTimestamp());
60
61 _dict.Add("nonceStr", result.NonceStr);
62
63 _dict.Add("package", "prepay_id=" + result.PrepayId);
64
65 _dict.Add("signType", "MD5");
66
67 _dict.Add("paySign", PayUtil.CreateMd5Sign(_dict, WeiChatConfigManager.Current.GetPayConfig().TenPayKey));
68
69 #endregion
70
71 return Ok(_dict);
72
73 }
74
75 catch (Exception ex)
76
77 {
78
79 log.Log(LoggerLevels.Error, "WechatPay:" + ex.Message);
80
81 }
82
83 return BadRequest("操作失败,请联系管理员!");
84
85 }
这也是一个webapi或mvc控制器,给前台页面调用,作用是通过微信的统一下单方法获取微信的预付单,然后再生成一个供给jssdk调用的对象。
页面上的jssdk调用方法
1 function onBridgeReady(data) {
2
3 WeixinJSBridge.invoke('getBrandWCPayRequest', data, function (res) {
4
5 is_suc = true;
6
7 if (res.err_msg == "get_brand_wcpay_request:ok") { //支付成功后
8
9 location.href = '@Url.TenantAction("PaySuccess", "Personal")';
10
11 } else {
12
13
14
15 }
16
17 });
18
19 }
20
21 function CallPay(data) {
22
23 if (typeof WeixinJSBridge == "undefined") {
24
25 if (document.addEventListener) {
26
27 document.addEventListener('WeixinJSBridgeReady', onBridgeReady(data), false);
28
29 } else if (document.attachEvent) {
30
31 document.attachEvent('WeixinJSBridgeReady', onBridgeReady(data));
32
33 document.attachEvent('onWeixinJSBridgeReady', onBridgeReady(data));
34
35 }
36
37 } else {
38
39 onBridgeReady(data);
40
41 }
42
43 }
44
45
46
47 this.toPay = function () {
48
49 wc.restApi.get({
50
51 isBlockUI: false,
52
53 url: '/api/MyOrder/Pay/' + _orderid,
54
55 success: function (result) {
56
57 CallPay(ko.toJS(result));
58
59 }
60
61 });
62
63 }
注意这里,这里先是通过ajax请求到我们前面所写的微信统一下单那个action,然后获取到返回的那个对象
关于ko.toJS(),这是knockout里面的方法,作用是把result处理成json对象。
好了,到此微信支付已经处理完了,详细代码请移步http://github.com/xin-lai下载最新源码。