公众号微信支付

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下载最新源码。

时间: 2024-10-23 10:10:16

公众号微信支付的相关文章

公众号微信支付开发

一.前期准备工作 服务号认证,申请商户号 二.账号配置 1.公众号接口配置 由于调用支付接口时需要获取用户基本信息,需要在公众号配置授权回调页面域名. 登录公众号后点击接口权限,找到网页授权获取用户基本信息,点击右侧的修改链接,位置格式如下 2.配置JS接口安全域名 点击公众号配置,在右侧窗口点击开发设置,找到JS接口安全域名,点击对应的修改链接,格式如下 3.设置支付授权目录 点击微信支付链接,切换到开发设置标签,配置授权目录,配置如下 注:授权目录的链接大小写敏感,请准确填写 4.配置商户安

大商创微信公众号微信支付失败报错

支付失败链接 支付失败 https://XXX/mobile/onlinepay/index/index/order_sn/2018042715565982911.html 支付成功 https://XXX/mobile/index.php?m=onlinepay&order_sn=2018042714596408963 原文地址:https://www.cnblogs.com/behindman/p/8962996.html

微信公众号JSAPI支付

微信公众号JSAPI支付 一:配置参数 申请成功后,获取接口文件, 将所有文件放入项目根目录weixin下,在WxPay.ub.config.php中填入配置账户信息; 二:设置授权 开发者中心->网页服务->网页授权获取用户基本信息->修改; “授权回调页面域名修改成你的域名地址即可,须保证网页授权已获得,不然会报redirect_uri 参数错误; 三:网页授权获取用户openid js_api_call.php 请求文件中改动(所有传给微信的参数都在入口文件中接收) $out_tr

微信公众号H5支付-JAVA版

微信开发之微信公众号H5支付-JAVA版 引子 从事JAVA开发一年多了,一直都在看博客园,CSDN的博客,从很多前人哪里学习了很多,突然觉得自己也要尽一份力,写点博客自己给自己做做记录,也给要开发微信人提提醒少遇点坑. 很多人开发微信的时候,总是在抱怨微信的开发文档很坑,里面的参数和使用方式很含糊,其实有时候自己想想,如果自己去研发API的时候,是否能够做的比微信更好呢?,大师都有一颗虔诚学徒的心,希望这篇文档能给予从事微信公众号H5支付焦头烂额的朋友,一点帮助. 一.前言 先给大家提提从事微

微信小程序与微信公众号之间支付问题解决方案

前言 大家好,我是一名对编程有兴趣的小伙子,IT届称我为xiager,工作中叫我jake 就好了,如果此文对你有帮助希望多多关注哦. 准备 微信公众平台 微信支付平台 微信开放平台 一. 小程序    二 微信公众号 两个微信支付绑定同一个商户号 1.微信支付平台 产品中心 授权目录要添加根目录并准确  要用https 形式 否则小程序内不支持 http形式的. 2.小程序中js授权等域名 填写一致  微信公众号网页授权地址一致 3.在jsapi支付的时候切记一定传openid.  在微信浏览器

项目中微信公众号调取支付控件demo

微信支付官方文档:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=7_7&index=6 前端代码demo (JSP页面): <%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="utf-8"%> <%@ taglib prefix="c&quo

微信公众号jsapi支付php源码分析

微信公众号支付,首先需要通过授权跳转地址里获取code,并进一步向微信获取openid,然后拉起统一支付获取prepay_id,然后再等待用户按下支付,调起支付.支付部分在前端,很多初次使用微信公众号支付的人人对获取code和opendi部分不懂,微信的php,java等demo源代码地址如下 https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=11_1. 微信支付的演示地址(在微信内复制黏贴后点击打开)http://paysdk.w

QQ公众号&amp;微信公众号,左右互搏?

自从微信推出以后,微信和QQ的关系就一直很微妙.近日,QQ公众号上线.商家可以申请,用户在关注后就可以通过菜单或者语音获得即时的服务.这和目前的微信公众号非常类似. QQ公众号的特色是在显著位置保留了与商家客服一键语音通话的功能,但是这个语音不是传统的客服人员,而是专为手机QQ用户设计的.带有可视化 菜单的服务,通过语音引导来完成服务.不过目前大多数QQ公众号,还是以菜单操作为主,完成各类服务,和微信公共号如出一辙.腾讯的微信公众号运行的有声 有色,为什么要再搞一个QQ的公众号呢?这样不会乱吗?

C#开发微信门户及应用(26)-公众号微信素材管理

微信公众号最新修改了素材的管理模式,提供了两类素材的管理:临时素材和永久素材的管理,原先的素材管理就是临时素材管理,永久素材可以永久保留在微信服务器上,微信素材可以在上传后,进行图片文件或者图文消息的发送,关注的公众号可以在素材有效期内查看相关的资源,对于永久素材,那就不会存在过期的问题,只是纯粹数量上限的限制.本文综合两方面进行介绍素材管理的各种接口和实现. 1.素材类型和功能点 关于素材的官方说明: 临时素材: 公众号经常有需要用到一些临时性的多媒体素材的场景,例如在使用接口特别是发送消息时