最近工作中接觸到一些關(guān)于微信支付方面的東西,看到給的DEMO都是PHP版本的,再加上微信支付文檔寫的確實不敢恭維,趟過不少坑之后閑下來做個總結(jié)。
一、前期準(zhǔn)備
做微信開發(fā)首先要申請一個公共賬號,申請成功后會以郵件形式發(fā)給你一些必要信息,公共賬號中有開發(fā)文檔以及開發(fā)中必要信息,以及測試的數(shù)據(jù)查詢。
二、工具類
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" ); // 使用指定的字節(jié)更新摘要 mdInst.update(btInput); // 獲得密文 byte [] md = mdInst.digest(); // 把密文轉(zhuǎn)換成十六進(jìn)制的字符串形式 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,否則微信訂單中的中文會出現(xiàn)亂碼,改后可以正確顯示。
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以及創(chuàng)建微信訂單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); } /** * 微信創(chuàng)建訂單 * @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); //隨機(jī)字符串 nativeObj.put( "body" , orderDescribe); //商品描述 nativeObj.put( "attach" , "tradeno" ); //附加數(shù)據(jù) nativeObj.put( "out_trade_no" , orderNo); //商戶訂單號(全局唯一) nativeObj.put( "total_fee" , price); //總金額(單位為分,不能帶小數(shù)點) nativeObj.put( "spbill_create_ip" , "192.168.0.144" ); //終端Ip nativeObj.put( "time_start" , timeStart); //交易起始時間 nativeObj.put( "time_expire" , timeExpire); //交易結(jié)束時間 nativeObj.put( "notify_url" , CustomizedPropertyPlaceholderConfigurer.getContextProperty( "wxurl" )+ "/weixin_callback/weixinCallback/init.action" ); //回調(diào)通知地址 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); //隨機(jī)字符串 if (!StringUtils.isEmpty(transactionId)){ nativeObj.put( "transaction_id" , transactionId); } if (!StringUtils.isEmpty(outTradeNo)){ nativeObj.put( "out_trade_no" , outTradeNo); //隨機(jī)字符串 } 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); //隨機(jī)字符串 nativeObj.put( "out_trade_no" , outTradeNo); //商戶訂單號(全局唯一) nativeObj.put( "out_refund_no" , outRefundNo); //商戶退款單號(全局唯一) nativeObj.put( "total_fee" , totalFee); //總金額(單位為分,不能帶小數(shù)點) nativeObj.put( "refund_fee" , refundFee); //退款金額(單位為分,不能帶小數(shù)點) 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); //隨機(jī)字符串 nativeObj.put( "body" , orderDescribe); //商品描述 nativeObj.put( "attach" , "tradeno" ); //附加數(shù)據(jù) nativeObj.put( "out_trade_no" , orderNo); //商戶訂單號(全局唯一) nativeObj.put( "total_fee" , price); //總金額(單位為分,不能帶小數(shù)點) nativeObj.put( "spbill_create_ip" , "192.168.0.144" ); //終端Ip nativeObj.put( "time_start" , timeStart); //交易起始時間 nativeObj.put( "time_expire" , timeExpire) //交易結(jié)束時間 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); } /** * 微信關(guān)閉訂單 * @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); //隨機(jī)字符串 String sign = GetBizSign(nativeObj); nativeObj.put( "sign" , sign.toUpperCase()); return CommonUtil.ArrayToXml(nativeObj); } } |
4.調(diào)用微信支付接口
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( "訂單標(biāo)號orderNo=======" +jsonO.get( "orderNo" ).toString()); System.out.println( "10位隨機(jī)數(shù)=======" +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( "根節(jié)點:" + 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包:下載地址。
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持服務(wù)器之家。