最近工作中接触到一些关于微信支付方面的东西,看到给的DEMO都是PHP版本的,再加上微信支付文档写的确实不敢恭维,趟过不少坑之后闲下来做个总结。
一、前期准备
做微信开发首先要申请一个公共账号,申请成功后会以邮件形式发给你一些必要信息,公共账号中有开发文档以及开发中必要信息,以及测试的数据查询。
二、工具类
1.MD5加密工具类
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
|
package com.pay.utils.weixin; import java.security.MessageDigest; public class MD5Util { public final static String MD5(String s) { char hexDigits[]={ '0' , '1' , '2' , '3' , '4' , '5' , '6' , '7' , '8' , '9' , 'A' , 'B' , 'C' , 'D' , 'E' , 'F' }; try { byte [] btInput = s.getBytes(); // 获得MD5摘要算法的 MessageDigest 对象 MessageDigest mdInst = MessageDigest.getInstance( "MD5" ); // 使用指定的字节更新摘要 mdInst.update(btInput); // 获得密文 byte [] md = mdInst.digest(); // 把密文转换成十六进制的字符串形式 int j = md.length; char str[] = new char [j * 2 ]; int k = 0 ; for ( int i = 0 ; i < j; i++) { byte byte0 = md[i]; str[k++] = hexDigits[byte0 >>> 4 & 0xf ]; str[k++] = hexDigits[byte0 & 0xf ]; } return new String(str); } catch (Exception e) { e.printStackTrace(); return null ; } } } |
2.CommonUtil工具类,用于装换成微信所需XML。以下return new String(xml.toString().getBytes(),"ISO8859-1");将工具类中的utf-8改成iso8859-1,否则微信订单中的中文会出现乱码,改后可以正确显示。
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
|
package com.pay.utils.weixin; import java.io.UnsupportedEncodingException; import java.net.URLEncoder; import java.util.*; import java.util.Map.Entry; public class CommonUtil { public static String CreateNoncestr( int length) { String chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" ; String res = "" ; for ( int i = 0 ; i < length; i++) { Random rd = new Random(); res += chars.indexOf(rd.nextInt(chars.length() - 1 )); } return res; } public static String CreateNoncestr() { String chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" ; String res = "" ; for ( int i = 0 ; i < 16 ; i++) { Random rd = new Random(); res += chars.charAt(rd.nextInt(chars.length() - 1 )); } return res; } public static String FormatQueryParaMap(HashMap<String, String> parameters) throws SDKRuntimeException { String buff = "" ; try { List<Map.Entry<String, String>> infoIds = new ArrayList<Map.Entry<String, String>>( parameters.entrySet()); Collections.sort(infoIds, new Comparator<Map.Entry<String, String>>() { public int compare(Map.Entry<String, String> o1, Map.Entry<String, String> o2) { return (o1.getKey()).toString().compareTo( o2.getKey()); } }); for ( int i = 0 ; i < infoIds.size(); i++) { Map.Entry<String, String> item = infoIds.get(i); if (item.getKey() != "" ) { buff += item.getKey() + "=" + URLEncoder.encode(item.getValue(), "utf-8" ) + "&" ; } } if (buff.isEmpty() == false ) { buff = buff.substring( 0 , buff.length() - 1 ); } } catch (Exception e) { throw new SDKRuntimeException(e.getMessage()); } return buff; } public static String FormatBizQueryParaMap(HashMap<String, String> paraMap, boolean urlencode) throws SDKRuntimeException { String buff = "" ; try { List<Map.Entry<String, String>> infoIds = new ArrayList<Map.Entry<String, String>>( paraMap.entrySet()); Collections.sort(infoIds, new Comparator<Map.Entry<String, String>>() { public int compare(Map.Entry<String, String> o1, Map.Entry<String, String> o2) { return (o1.getKey()).toString().compareTo( o2.getKey()); } }); for ( int i = 0 ; i < infoIds.size(); i++) { Map.Entry<String, String> item = infoIds.get(i); //System.out.println(item.getKey()); if (item.getKey() != "" ) { String key = item.getKey(); String val = item.getValue(); if (urlencode) { val = URLEncoder.encode(val, "utf-8" ); } buff += key.toLowerCase() + "=" + val + "&" ; } } if (buff.isEmpty() == false ) { buff = buff.substring( 0 , buff.length() - 1 ); } } catch (Exception e) { throw new SDKRuntimeException(e.getMessage()); } return buff; } public static boolean IsNumeric(String str) { if (str.matches( "\\d *" )) { return true ; } else { return false ; } } public static String ArrayToXml(HashMap<String, String> arr) { String xml = "<xml>" ; Iterator<Entry<String, String>> iter = arr.entrySet().iterator(); while (iter.hasNext()) { Entry<String, String> entry = iter.next(); String key = entry.getKey(); String val = entry.getValue(); if (IsNumeric(val)) { xml += "<" + key + ">" + val + "</" + key + ">" ; } else xml += "<" + key + "><![CDATA[" + val + "]]></" + key + ">" ; } xml += "</xml>" ; try { return new String(xml.toString().getBytes(), "ISO8859-1" ); } catch (UnsupportedEncodingException e) { // TODO Auto-generated catch block e.printStackTrace(); } return "" ; } } |
3.ClientCustomSSL工具类,用于生成sign以及创建微信订单package com.pay.utils.weixin;
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
|
import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; import org.springframework.util.StringUtils; /** * This example demonstrates how to create secure connections with a custom SSL * context. */ public class ClientCustomSSL { public static String GetBizSign(HashMap<String, String> bizObj) throws SDKRuntimeException { HashMap<String, String> bizParameters = new HashMap<String, String>(); List<Map.Entry<String, String>> infoIds = new ArrayList<Map.Entry<String, String>>( bizObj.entrySet()); System.out.println(infoIds); Collections.sort(infoIds, new Comparator<Map.Entry<String, String>>() { public int compare(Map.Entry<String, String> o1, Map.Entry<String, String> o2) { return (o1.getKey()).toString().compareTo(o2.getKey()); } }); System.out.println( "--------------------" ); System.out.println(infoIds); for ( int i = 0 ; i < infoIds.size(); i++) { Map.Entry<String, String> item = infoIds.get(i); if (item.getKey() != "" ) { bizParameters.put(item.getKey().toLowerCase(), item.getValue()); } } //bizParameters.put("key", "12345678123456781234567812345671"); String bizString = CommonUtil.FormatBizQueryParaMap(bizParameters, false ); bizString += "&key=12345678123456781234567812345671" ; System.out.println( "***************" ); System.out.println(bizString); // return SHA1Util.Sha1(bizString); return MD5Util.MD5(bizString); } /** * 微信创建订单 * @param nonceStr * @param orderDescribe * @param orderNo * @param price * @param timeStart * @param timeExpire * @return * @throws SDKRuntimeException */ public static String CreateNativePackage(String nonceStr,String orderDescribe,String orderNo,String price,String timeStart,String timeExpire) throws SDKRuntimeException { HashMap<String, String> nativeObj = new HashMap<String, String>(); nativeObj.put( "appid" , "见公众账号" ); //公众账号Id nativeObj.put( "mch_id" , "见邮件" ); //商户号 nativeObj.put( "nonce_str" , nonceStr); //随机字符串 nativeObj.put( "body" , orderDescribe); //商品描述 nativeObj.put( "attach" , "tradeno" ); //附加数据 nativeObj.put( "out_trade_no" , orderNo); //商户订单号(全局唯一) nativeObj.put( "total_fee" , price); //总金额(单位为分,不能带小数点) nativeObj.put( "spbill_create_ip" , "192.168.0.144" ); //终端Ip nativeObj.put( "time_start" , timeStart); //交易起始时间 nativeObj.put( "time_expire" , timeExpire); //交易结束时间 nativeObj.put( "notify_url" , CustomizedPropertyPlaceholderConfigurer.getContextProperty( "wxurl" )+ "/weixin_callback/weixinCallback/init.action" ); //回调通知地址 nativeObj.put( "trade_type" , "NATIVE" ); //交易类型 String sign = GetBizSign(nativeObj); nativeObj.put( "sign" , sign.toUpperCase()); return CommonUtil.ArrayToXml(nativeObj); } /** * 微信订单支付查询 * @param nonceStr * @param orderDescribe * @param orderNo * @param price * @param timeStart * @param timeExpire * @return * @throws SDKRuntimeException */ public static String SearchNativePackage(String transactionId,String outTradeNo,String nonceStr) throws SDKRuntimeException { HashMap<String, String> nativeObj = new HashMap<String, String>(); nativeObj.put( "appid" , "见公众共账号" ); //公众账号Id nativeObj.put( "mch_id" , "见邮件" ); //商户号 nativeObj.put( "nonce_str" , nonceStr); //随机字符串 if (!StringUtils.isEmpty(transactionId)){ nativeObj.put( "transaction_id" , transactionId); } if (!StringUtils.isEmpty(outTradeNo)){ nativeObj.put( "out_trade_no" , outTradeNo); //随机字符串 } String sign = GetBizSign(nativeObj); nativeObj.put( "sign" , sign.toUpperCase()); return CommonUtil.ArrayToXml(nativeObj); /** * 微信退款 * @param outTradeNo * @param outRefundNo * @param totalFee * @param refundFee * @return * @throws SDKRuntimeException */ public static String RefundNativePackage(String outTradeNo,String outRefundNo,String totalFee,String refundFee,String nonceStr) throws SDKRuntimeException { HashMap<String, String> nativeObj = new HashMap<String, String>(); nativeObj.put( "appid" , "见公众账号" ); //公众账号Id nativeObj.put( "mch_id" , "见邮件" ); //商户号 nativeObj.put( "nonce_str" , nonceStr); //随机字符串 nativeObj.put( "out_trade_no" , outTradeNo); //商户订单号(全局唯一) nativeObj.put( "out_refund_no" , outRefundNo); //商户退款单号(全局唯一) nativeObj.put( "total_fee" , totalFee); //总金额(单位为分,不能带小数点) nativeObj.put( "refund_fee" , refundFee); //退款金额(单位为分,不能带小数点) nativeObj.put( "op_user_id" , "邮件" ); String sign = GetBizSign(nativeObj); nativeObj.put( "sign" , sign.toUpperCase()); return CommonUtil.ArrayToXml(nativeObj); } /** * 微信待支付 * @param nonceStr * @param orderDescribe * @param orderNo * @param price * @param timeStart * @param timeExpire * @return * @throws SDKRuntimeException */ public static String CreateJsApiPackage(String nonceStr,String orderDescribe,String orderNo,String price,String timeStart,String timeExpire,String openId) throws SDKRuntimeException { HashMap<String, String> nativeObj = new HashMap<String, String>(); nativeObj.put( "appid" , "见公众账号" ); //公众账号Id nativeObj.put( "openid" , openId); //公众账号Id nativeObj.put( "mch_id" , "见邮件" ) //商户号 nativeObj.put( "nonce_str" , nonceStr); //随机字符串 nativeObj.put( "body" , orderDescribe); //商品描述 nativeObj.put( "attach" , "tradeno" ); //附加数据 nativeObj.put( "out_trade_no" , orderNo); //商户订单号(全局唯一) nativeObj.put( "total_fee" , price); //总金额(单位为分,不能带小数点) nativeObj.put( "spbill_create_ip" , "192.168.0.144" ); //终端Ip nativeObj.put( "time_start" , timeStart); //交易起始时间 nativeObj.put( "time_expire" , timeExpire) //交易结束时间 nativeObj.put( "notify_url" ,CustomizedPropertyPlaceholderConfigurer.getContextProperty( "wxurl" )+ "/weixin_callback/weixinCallback/init.action" ); //通知地址 nativeObj.put( "trade_type" , "JSAPI" ); //交易类型 String sign = GetBizSign(nativeObj); nativeObj.put( "sign" , sign.toUpperCase()); return CommonUtil.ArrayToXml(nativeObj); } /** * 微信关闭订单 * @param nonceStr * @param orderDescribe * @param orderNo * @param price * @param timeStart * @param timeExpire * @param openId * @return * @throws SDKRuntimeException */ public static String CreateCloseOrder(String outTradeNo,String nonceStr) throws SDKRuntimeException { HashMap<String, String> nativeObj = new HashMap<String, String>(); nativeObj.put( "appid" , "见公众账号" ); //公众账号Id nativeObj.put( "mch_id" , "见邮件" ); //商户号 nativeObj.put( "out_trade_no" , outTradeNo); //商户订单号(全局唯一) nativeObj.put( "nonce_str" , nonceStr); //随机字符串 String sign = GetBizSign(nativeObj); nativeObj.put( "sign" , sign.toUpperCase()); return CommonUtil.ArrayToXml(nativeObj); } } |
4.调用微信支付接口
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
|
package com.pay.controller.weixin; import java.io.File; import java.io.FileInputStream; import java.security.KeyStore; import java.text.SimpleDateFormat; import java.util.Date; import java.util.List; import javax.net.ssl.SSLContext; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import net.sf.json.JSONArray; import net.sf.json.JSONObject; import org.apache.http.HttpEntity; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpPost; import org.apache.http.conn.ssl.SSLConnectionSocketFactory; import org.apache.http.conn.ssl.SSLContexts; import org.apache.http.entity.StringEntity; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; import org.apache.http.util.EntityUtils; import org.dom4j.Document; import org.dom4j.DocumentHelper; import org.dom4j.Element; import org.dom4j.io.SAXReader; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestController; import com.pay.bo.PayHist; import com.pay.constants.PayHistoryPayStatus; import com.pay.constants.PayHistoryPayType; import com.pay.service.WeiXinPayService; import com.pay.utils.weixin.ClientCustomSSL; import com.pay.utils.weixin.CloseWeiXinOrderUtils; import com.pay.utils.weixin.CustomizedPropertyPlaceholderConfigurer; @RestController @RequestMapping ( "/Pay" ) public class WeiXinPayController { @Autowired WeiXinPayService weiXinPayService; private static long standardTime = 1662652800000L; /** * 返回生成二维码的url * @param request * @param response * @return */ @RequestMapping (value= "/getUrl" ,method=RequestMethod.POST) @ResponseStatus (HttpStatus.OK) public Object getUrl(HttpServletResponse response, @RequestBody String body){ try { JSONObject jsonO = JSONObject.fromObject(body); PayHist ph = null ; // List<Map<String,Object>> td = weiXinPayService.getTrade(orderNo); Date dt = new Date(); SimpleDateFormat sdf = new SimpleDateFormat( "yyyyMMddHHmmss" ); String nonceStr = sdf.format(dt).toString(); Date now = new Date(); String tradePayNo = jsonO.get( "orderNo" ).toString()+String.format( "%10d" ,standardTime - now.getTime()).substring( 0 , 10 ); System.out.println( "订单标号orderNo=======" +jsonO.get( "orderNo" ).toString()); System.out.println( "10位随机数=======" +String.format( "%10d" ,standardTime - now.getTime()).substring( 0 , 10 )); String price = Math.round(Float.valueOf(jsonO.get( "price" ).toString())* 100 )+ "" ; Long timeExpireStrOld = dt.getTime(); Long timeNew = Long.parseLong(CustomizedPropertyPlaceholderConfigurer.getContextProperty( "weixin.send2finish.overtime" ).toString()); Long timeExpireNew = timeExpireStrOld+timeNew; Date dtTimeExpire = new Date(timeExpireNew); SimpleDateFormat dtSdf = new SimpleDateFormat( "yyyyMMddHHmmss" ); String timeExpire = dtSdf.format(dtTimeExpire).toString(); System.out.println( "nonceStr==" +nonceStr); System.out.println( "orderNo==" +jsonO.get( "orderNo" ).toString()); System.out.println( "price==" +price); System.out.println( "timeStart==" +nonceStr); System.out.println( "timeExpire==" +timeExpire); JSONObject result = (JSONObject) setUrl(nonceStr, "订单" ,tradePayNo,price,nonceStr,timeExpire); if (result.get( "status" ).toString().equals( "success" )){ ph = new PayHist(); ph.setTradePayUrl(result.getString( "weixinPayUrl" )); //此字段为支付链接,可以此链接生成二维码扫码支付 ph.setPayTradeNo(jsonO.get( "orderNo" ).toString()); ph.setTradePayNo(tradePayNo); ph.setPayStatus(PayHistoryPayStatus.WECHAT_PAY_STATUS_WAIT); ph.setPayType(PayHistoryPayType.WECHAT); ph.setAppKey(jsonO.getString( "appKey" ).toString()); ph.setPayAmount(price); result.put( "payTradeNo" , ph.getPayTradeNo()); result.put( "tradePayNo" , ph.getTradePayNo()); result.put( "payStatus" , ph.getPayStatus()); result.put( "payType" , ph.getPayType()); } return result; } catch (Exception e){ e.printStackTrace(); JSONObject result = new JSONObject(); result.put( "status" , "error" ); result.put( "msg" ,e.getMessage()); // return result.toString(); } return null ; } public Object setUrl(String nonceStr,String orderDescribe,String orderNo,String price,String timeStart,String timeExpire) { try { KeyStore keyStore = KeyStore.getInstance( "PKCS12" ); FileInputStream instream = new FileInputStream( new File(微信证书绝对路径)); try { keyStore.load(instream, "商户ID" .toCharArray()); } finally { instream.close(); } // Trust own CA and all self-signed certs SSLContext sslcontext = SSLContexts.custom().loadKeyMaterial(keyStore,<span style= "font-family: Arial, Helvetica, sans-serif;" >商户ID</span>.toCharArray()).build(); // Allow TLSv1 protocol only SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory( sslcontext, new String[] { "TLSv1" }, null , SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER); CloseableHttpClient httpclient = HttpClients.custom() .setSSLSocketFactory(sslsf).build(); // HttpGet httpget = new // HttpGet("https://api.mch.weixin.qq.com/secapi/pay/refund"); HttpPost httppost = new HttpPost( String xml = ClientCustomSSL.CreateNativePackage(nonceStr,orderDescribe,orderNo,price,timeStart,timeExpire); try { StringEntity se = new StringEntity(xml); httppost.setEntity(se); System.out.println( "executing request" + httppost.getRequestLine()); CloseableHttpResponse responseEntry = httpclient.execute(httppost); try { HttpEntity entity = responseEntry.getEntity(); System.out.println( "----------------------------------------" ); System.out.println(responseEntry.getStatusLine()); if (entity != null ) { System.out.println( "Response content length: " + entity.getContentLength()); /* BufferedReader bufferedReader = new BufferedReader( new InputStreamReader(entity.getContent())); String text; while ((text = bufferedReader.readLine()) != null) { System.out.println("======="+text); }*/ SAXReader saxReader = new SAXReader(); Document document = saxReader.read(entity.getContent()); Element rootElt = document.getRootElement(); System.out.println( "根节点:" + rootElt.getName()); System.out.println( "===" +rootElt.elementText( "result_code" )); System.out.println( "===" +rootElt.elementText( "return_msg" )); String resultCode = rootElt.elementText( "result_code" ); JSONObject result = new JSONObject(); Document documentXml =DocumentHelper.parseText(xml); Element rootEltXml = documentXml.getRootElement(); if (resultCode.equals( "SUCCESS" )){ System.out.println( "=================prepay_id====================" + rootElt.elementText( "prepay_id" )); System.out.println( "=================sign====================" + rootEltXml.elementText( "sign" )); result.put( "weixinPayUrl" , rootElt.elementText( "code_url" )); result.put( "prepayId" , rootElt.elementText( "prepay_id" )); result.put( "status" , "success" ); result.put( "msg" , "success" ); } else { result.put( "status" , "false" ); result.put( "msg" ,rootElt.elementText( "err_code_des" )); } return result; } EntityUtils.consume(entity); } finally { responseEntry.close(); } } finally { httpclient.close(); } return null ; } catch (Exception e){ e.printStackTrace(); JSONObject result = new JSONObject(); result.put( "status" , "error" ); result.put( "msg" ,e.getMessage()); return result; } } } |
httpclient jar包和json jar包:下载地址。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持服务器之家。