微信app支付服务端的实现方法,供大家参考,具体内容如下
引言
主要实现app支付统一下单、异步通知、调起支付接口、支付订单查询、申请退款、查询退款功能;封装了https对发起退款的证书校验、签名、xml解析等。
支付流程
具体支付流程参考“微信app”文档,文档地址
app支付:app端点击下单—-服务端生成订单,并调起“统一下单”,返回app支付所需参数—–app端“调起支付接口“,发起支付—-微信服务器端调用服务端回调地址—–服务端按照“支付结果通知”,处理支付结果
app查询:调起“查询订单”
app退款:发起退款请求,调用“申请退款”,发起退款,需双向证书验证
app退款查询:调起“查询退款”
支付代码实现
代码实现签名、证书校验、http和https封装等,项目结构如下:
支付代码
包含支付、支付查询、异步通知、退款申请、退款查询
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
|
package org.andy.wxpay.controller; import java.math.bigdecimal; import java.util.hashmap; import java.util.map; import javax.servlet.servletinputstream; import javax.servlet.http.httpservletrequest; import javax.servlet.http.httpservletresponse; import org.andy.wxpay.model.jsonresult; import org.andy.wxpay.model.responsedata; import org.andy.wxpay.utils.collectionutil; import org.andy.wxpay.utils.configutil; import org.andy.wxpay.utils.fileutil; import org.andy.wxpay.utils.httputils; import org.andy.wxpay.utils.payutil; import org.andy.wxpay.utils.serializerfeatureutil; import org.andy.wxpay.utils.stringutil; import org.andy.wxpay.utils.webutil; import org.andy.wxpay.utils.xmlutil; import org.apache.log4j.logger; import org.springframework.stereotype.controller; import org.springframework.web.bind.annotation.requestmapping; import org.springframework.web.bind.annotation.requestmethod; import org.springframework.web.bind.annotation.requestparam; import com.alibaba.fastjson.json; /** * 创建时间:2016年11月2日 下午4:16:32 * * @author andy * @version 2.2 */ @controller @requestmapping ( "/order" ) public class paycontroller { private static final logger log = logger.getlogger(paycontroller. class ); private static final string order_pay = "https://api.mch.weixin.qq.com/pay/unifiedorder" ; // 统一下单 private static final string order_pay_query = "https://api.mch.weixin.qq.com/pay/orderquery" ; // 支付订单查询 private static final string order_refund = "https://api.mch.weixin.qq.com/secapi/pay/refund" ; // 申请退款 private static final string order_refund_query = "https://api.mch.weixin.qq.com/pay/refundquery" ; // 申请退款 private static final string app_id = configutil.getproperty( "wx.appid" ); private static final string mch_id = configutil.getproperty( "wx.mchid" ); private static final string api_secret = configutil.getproperty( "wx.api.secret" ); /** * 支付下订单 * * @param request * @param response * @param cashnum * 支付金额 * @param mercid * 商品id * @param callback */ @requestmapping (value = "/pay" , method = requestmethod.post) public void orderpay(httpservletrequest request, httpservletresponse response, @requestparam (required = false , defaultvalue = "0" ) double cashnum, string mercid, string callback) { log.info( "[/order/pay]" ); if (! "001" .equals(mercid)) { webutil.response(response, webutil.packjsonp(callback, json .tojsonstring( new jsonresult(- 1 , "商品不存在" , new responsedata()), serializerfeatureutil.features))); } map<string, string> restmap = null ; boolean flag = true ; // 是否订单创建成功 try { string total_fee = bigdecimal.valueof(cashnum).multiply(bigdecimal.valueof( 100 )) .setscale( 0 , bigdecimal.round_half_up).tostring(); map<string, string> parm = new hashmap<string, string>(); parm.put( "appid" , app_id); parm.put( "mch_id" , mch_id); parm.put( "device_info" , "web" ); parm.put( "nonce_str" , payutil.getnoncestr()); parm.put( "body" , "测试付费" ); parm.put( "attach" , "andy" ); parm.put( "out_trade_no" , payutil.gettradeno()); parm.put( "total_fee" , total_fee); parm.put( "spbill_create_ip" , payutil.getremoteaddrip(request)); parm.put( "notify_url" , "https://www.andy.org/wxpay/order/pay/notify.shtml" ); parm.put( "trade_type" , "app" ); parm.put( "sign" , payutil.getsign(parm, api_secret)); string restxml = httputils.post(order_pay, xmlutil.xmlformat(parm, false )); restmap = xmlutil.xmlparse(restxml); } catch (exception e) { log.error(e.getmessage(), e); } map<string, string> paymap = new hashmap<string, string>(); if (collectionutil.isnotempty(restmap) && "success" .equals(restmap.get( "result_code" ))) { paymap.put( "appid" , app_id); paymap.put( "partnerid" , mch_id); paymap.put( "prepayid" , restmap.get( "prepay_id" )); paymap.put( "package" , "sign=wxpay" ); paymap.put( "noncestr" , payutil.getnoncestr()); paymap.put( "timestamp" , payutil.paytimestamp()); try { paymap.put( "sign" , payutil.getsign(paymap, api_secret)); } catch (exception e) { flag = false ; } } if (flag) { webutil.response(response, webutil.packjsonp(callback, json.tojsonstring( new jsonresult( 1 , "订单获取成功" , new responsedata( null , paymap)), serializerfeatureutil.features))); } else { if (collectionutil.isnotempty(restmap)) { log.info( "订单创建失败:" + restmap.get( "err_code" ) + ":" + restmap.get( "err_code_des" )); } webutil.response(response, webutil.packjsonp(callback, json .tojsonstring( new jsonresult(- 1 , "订单获取失败" , new responsedata()), serializerfeatureutil.features))); } } /** * 查询支付结果 * * @param request * @param response * @param tradeid 微信交易订单号 * @param tradeno 商品订单号 * @param callback */ @requestmapping (value = "/pay/query" , method = requestmethod.post) public void orderpayquery(httpservletrequest request, httpservletresponse response, string tradeid, string tradeno, string callback) { log.info( "[/order/pay/query]" ); if (stringutil.isempty(tradeno) && stringutil.isempty(tradeid)) { webutil.response(response, webutil.packjsonp(callback, json .tojsonstring( new jsonresult(- 1 , "订单号不能为空" , new responsedata()), serializerfeatureutil.features))); } map<string, string> restmap = null ; try { map<string, string> parm = new hashmap<string, string>(); parm.put( "appid" , app_id); parm.put( "mch_id" , mch_id); parm.put( "transaction_id" , tradeid); parm.put( "out_trade_no" , tradeno); parm.put( "nonce_str" , payutil.getnoncestr()); parm.put( "sign" , payutil.getsign(parm, api_secret)); string restxml = httputils.post(order_pay_query, xmlutil.xmlformat(parm, false )); restmap = xmlutil.xmlparse(restxml); } catch (exception e) { log.error(e.getmessage(), e); } if (collectionutil.isnotempty(restmap) && "success" .equals(restmap.get( "result_code" ))) { // 订单查询成功 处理业务逻辑 log.info( "订单查询:订单" + restmap.get( "out_trade_no" ) + "支付成功" ); webutil.response(response, webutil.packjsonp(callback, json .tojsonstring( new jsonresult( 1 , "订单支付成功" , new responsedata()), serializerfeatureutil.features))); } else { if (collectionutil.isnotempty(restmap)) { log.info( "订单支付失败:" + restmap.get( "err_code" ) + ":" + restmap.get( "err_code_des" )); } webutil.response(response, webutil.packjsonp(callback, json .tojsonstring( new jsonresult(- 1 , "订单支付失败" , new responsedata()), serializerfeatureutil.features))); } } /** * 订单支付微信服务器异步通知 * * @param request * @param response */ @requestmapping ( "/pay/notify" ) public void orderpaynotify(httpservletrequest request, httpservletresponse response) { log.info( "[/order/pay/notify]" ); response.setcharacterencoding( "utf-8" ); response.setcontenttype( "text/xml" ); try { servletinputstream in = request.getinputstream(); string resxml = fileutil.readinputstream2string(in); map<string, string> restmap = xmlutil.xmlparse(resxml); log.info( "支付结果通知:" + restmap); if ( "success" .equals(restmap.get( "result_code" ))) { // 订单支付成功 业务处理 string out_trade_no = restmap.get( "out_trade_no" ); // 商户订单号 // 通过商户订单判断是否该订单已经处理 如果处理跳过 如果未处理先校验sign签名 再进行订单业务相关的处理 string sing = restmap.get( "sign" ); // 返回的签名 restmap.remove( "sign" ); string signnow = payutil.getsign(restmap, api_secret); if (signnow.equals(sing)) { // 进行业务处理 log.info( "订单支付通知: 支付成功,订单号" + out_trade_no); // 处理成功后相应给响应xml map<string, string> respmap = new hashmap<>(); respmap = new hashmap<string, string>(); respmap.put( "return_code" , "success" ); //相应给微信服务器 respmap.put( "return_msg" , "ok" ); string resxml = xmlutil.xmlformat(restmap, true ); response.getwriter().write(resxml); } else { log.info( "订单支付通知:签名错误" ); } } else { log.info( "订单支付通知:支付失败," + restmap.get( "err_code" ) + ":" + restmap.get( "err_code_des" )); } } catch (exception e) { log.error(e.getmessage(), e); } } /** * 订单退款 需要双向证书验证 * * @param request * @param response * @param tradeno 微信订单号 * @param orderno 商家订单号 * @param callback */ @requestmapping (value = "/pay/refund" , method = requestmethod.post) public void orderpayrefund(httpservletrequest request, httpservletresponse response, string tradeno, string orderno, string callback) { log.info( "[/pay/refund]" ); if (stringutil.isempty(tradeno) && stringutil.isempty(orderno)) { webutil.response(response, webutil.packjsonp(callback, json .tojsonstring( new jsonresult(- 1 , "订单号不能为空" , new responsedata()), serializerfeatureutil.features))); } map<string, string> restmap = null ; try { map<string, string> parm = new hashmap<string, string>(); parm.put( "appid" , app_id); parm.put( "mch_id" , mch_id); parm.put( "nonce_str" , payutil.getnoncestr()); parm.put( "transaction_id" , tradeno); parm.put( "out_trade_no" , orderno); //订单号 parm.put( "out_refund_no" , payutil.getrefundno()); //退款单号 parm.put( "total_fee" , "10" ); // 订单总金额 从业务逻辑获取 parm.put( "refund_fee" , "10" ); // 退款金额 parm.put( "op_user_id" , mch_id); parm.put( "refund_account" , "refund_source_recharge_funds" ); //退款方式 parm.put( "sign" , payutil.getsign(parm, api_secret)); //string restxml = httputils.posts(order_refund, xmlutil.xmlformat(parm, false)); string restxml = httputils.posts(order_refund, xmlutil.xmlformat(parm, false )); restmap = xmlutil.xmlparse(restxml); } catch (exception e) { log.error(e.getmessage(), e); } map<string, string> refundmap = new hashmap<>(); if (collectionutil.isnotempty(restmap) && "success" .equals(restmap.get( "result_code" ))) { refundmap.put( "transaction_id" , restmap.get( "transaction_id" )); refundmap.put( "out_trade_no" , restmap.get( "out_trade_no" )); refundmap.put( "refund_id" , restmap.get( "refund_id" )); refundmap.put( "out_refund_no" , restmap.get( "out_refund_no" )); log.info( "订单退款:订单" + restmap.get( "out_trade_no" ) + "退款成功,商户退款单号" + restmap.get( "out_refund_no" ) + ",微信退款单号" + restmap.get( "refund_id" )); webutil.response(response, webutil.packjsonp(callback, json.tojsonstring( new jsonresult( 1 , "订单获取成功" , new responsedata( null , refundmap)), serializerfeatureutil.features))); } else { if (collectionutil.isnotempty(restmap)) { log.info( "订单退款失败:" + restmap.get( "err_code" ) + ":" + restmap.get( "err_code_des" )); } webutil.response(response, webutil.packjsonp(callback, json .tojsonstring( new jsonresult(- 1 , "订单退款失败" , new responsedata()), serializerfeatureutil.features))); } } /** * 订单退款查询 * @param request * @param response * @param tradeid 微信订单号 * @param tradeno 商户订单号 * @param refundid 微信退款号 * @param refundno 商家退款号 * @param callback */ @requestmapping (value = "/pay/refund/query" , method = requestmethod.post) public void orderpayrefundquery(httpservletrequest request, httpservletresponse response, string refundid, string refundno, string tradeid, string tradeno, string callback) { log.info( "[/pay/refund/query]" ); if (stringutil.isempty(tradeid) && stringutil.isempty(tradeno) && stringutil.isempty(refundno) && stringutil.isempty(refundid)) { webutil.response(response, webutil.packjsonp(callback, json .tojsonstring( new jsonresult(- 1 , "退单号或订单号不能为空" , new responsedata()), serializerfeatureutil.features))); } map<string, string> restmap = null ; try { map<string, string> parm = new hashmap<string, string>(); parm.put( "appid" , app_id); parm.put( "mch_id" , mch_id); parm.put( "transaction_id" , tradeid); parm.put( "out_trade_no" , tradeno); parm.put( "refund_id" , refundid); parm.put( "out_refund_no" , refundno); parm.put( "nonce_str" , payutil.getnoncestr()); parm.put( "sign" , payutil.getsign(parm, api_secret)); string restxml = httputils.post(order_refund_query, xmlutil.xmlformat(parm, false )); restmap = xmlutil.xmlparse(restxml); } catch (exception e) { log.error(e.getmessage(), e); } map<string, string> refundmap = new hashmap<>(); if (collectionutil.isnotempty(restmap) && "success" .equals(restmap.get( "result_code" )) && "success" .equals(restmap.get( "result_code" ))) { // 订单退款查询成功 处理业务逻辑 log.info( "退款订单查询:订单" + restmap.get( "out_trade_no" ) + "退款成功,退款状态" + restmap.get( "refund_status_0" )); refundmap.put( "transaction_id" , restmap.get( "transaction_id" )); refundmap.put( "out_trade_no" , restmap.get( "out_trade_no" )); refundmap.put( "refund_id" , restmap.get( "refund_id_0" )); refundmap.put( "refund_no" , restmap.get( "out_refund_no_0" )); refundmap.put( "refund_status" , restmap.get( "refund_status_0" )); webutil.response(response, webutil.packjsonp(callback, json .tojsonstring( new jsonresult( 1 , "订单退款成功" , new responsedata( null , refundmap)), serializerfeatureutil.features))); } else { if (collectionutil.isnotempty(restmap)) { log.info( "订单退款失败:" + restmap.get( "err_code" ) + ":" + restmap.get( "err_code_des" )); } webutil.response(response, webutil.packjsonp(callback, json .tojsonstring( new jsonresult(- 1 , "订单退款失败" , new responsedata()), serializerfeatureutil.features))); } } } |
微信支付接口参数含义具体参考微信app支付文档。
微信支付工具类
包含签名、订单号、退单号、随机串、服务器ip地址、客户端ip地址等方法。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
|
package org.andy.wxpay.utils; import java.io.unsupportedencodingexception; import java.net.urlencoder; import java.util.arrays; import java.util.date; import java.util.map; import java.util.set; import javax.servlet.http.httpservletrequest; /** * 创建时间:2016年11月2日 下午7:12:44 * * @author andy * @version 2.2 */ public class payutil { /** * 生成订单号 * * @return */ public static string gettradeno() { // 自增8位数 00000001 return "tno" + datetimeutil.formatdate( new date(), datetimeutil.time_stamp_pattern) + "00000001" ; } /** * 退款单号 * * @return */ public static string getrefundno() { // 自增8位数 00000001 return "rno" + datetimeutil.formatdate( new date(), datetimeutil.time_stamp_pattern) + "00000001" ; } /** * 退款单号 * * @return */ public static string gettransferno() { // 自增8位数 00000001 return "tno" + datetimeutil.formatdate( new date(), datetimeutil.time_stamp_pattern) + "00000001" ; } /** * 返回客户端ip * * @param request * @return */ public static string getremoteaddrip(httpservletrequest request) { string ip = request.getheader( "x-forwarded-for" ); if (stringutil.isnotempty(ip) && ! "unknown" .equalsignorecase(ip)) { // 多次反向代理后会有多个ip值,第一个ip才是真实ip int index = ip.indexof( "," ); if (index != - 1 ) { return ip.substring( 0 , index); } else { return ip; } } ip = request.getheader( "x-real-ip" ); if (stringutil.isnotempty(ip) && ! "unknown" .equalsignorecase(ip)) { return ip; } return request.getremoteaddr(); } /** * 获取服务器的ip地址 * * @param request * @return */ public static string getlocalip(httpservletrequest request) { return request.getlocaladdr(); } public static string getsign(map<string, string> params, string paternerkey) throws unsupportedencodingexception { return md5utils.getmd5(createsign(params, false ) + "&key=" + paternerkey).touppercase(); } /** * 构造签名 * * @param params * @param encode * @return * @throws unsupportedencodingexception */ public static string createsign(map<string, string> params, boolean encode) throws unsupportedencodingexception { set<string> keysset = params.keyset(); object[] keys = keysset.toarray(); arrays.sort(keys); stringbuffer temp = new stringbuffer(); boolean first = true ; for (object key : keys) { if (key == null || stringutil.isempty(params.get(key))) // 参数为空不参与签名 continue ; if (first) { first = false ; } else { temp.append( "&" ); } temp.append(key).append( "=" ); object value = params.get(key); string valuestr = "" ; if ( null != value) { valuestr = value.tostring(); } if (encode) { temp.append(urlencoder.encode(valuestr, "utf-8" )); } else { temp.append(valuestr); } } return temp.tostring(); } /** * 创建支付随机字符串 * @return */ public static string getnoncestr(){ return randomutil.randomstring(randomutil.letter_number_char, 32 ); } /** * 支付时间戳 * @return */ public static string paytimestamp() { return long .tostring(system.currenttimemillis() / 1000 ); } } |
其他所需工具类参考项目源码
支付结果
app支付测试完成
源代码地址:微信app支付
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持服务器之家。
原文链接:https://blog.csdn.net/fengshizty/article/details/53199356