微信支付現在已經變得越來越流行了,隨之也出現了很多以可以快速接入微信支付為噱頭的產品,不過方便之余也使得我們做東西慢慢依賴第三方,喪失了獨立思考的能力,這次打算分享下我之前開發過的微信支付。
一 、H5公眾號支付
要點:正確獲取openId以及統一下單接口,正確處理支付結果通知,正確配置支付授權目錄
H5的支付方式是使用較為廣泛的方式,這種支付方式主要用于微信內自定義菜單的網頁,依賴手機上安裝的微信客戶端,高版本的微信才支持微信支付,下面按我的流程注意說明
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
|
<%@ page language= "java" import = "java.util.*" pageEncoding= "UTF-8" %> <% String path = request.getContextPath(); String basePath = request.getScheme()+ "://" +request.getServerName()+ ":" +request.getServerPort()+path+ "/" ; %> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" > <html> <head> <base href= "<%=basePath%>" > <title>微信支付樣例</title> <meta name= "viewport" content= "width=device-width, initial-scale=1.0, maximum-scale=1.0" > <!-- <link rel= "stylesheet" type= "text/css" href= "styles.css" > --> </head> <body> <form action= "oauthServlet" method= "POST" > 訂單號:<input type= "text" name= "orderNo" /> <input type= "submit" value= "H5支付" /> </form> </br></br> <form action= "scanCodePayServlet?flag=createCode" method= "POST" > 訂單號:<input type= "text" name= "orderNo" /> <input type= "submit" value= "掃碼支付" /> </form> </body> </html> |
2 編寫一個servlet用于通過Oauth獲取code
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
|
package com.debug.weixin.servlet; import java.io.IOException; import java.io.PrintWriter; import javax.servlet.RequestDispatcher; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import com.debug.weixin.util.CommonUtil; import com.debug.weixin.util.ServerConfig; public class OauthServlet extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this .doPost(request, response); } public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String orderNo=request.getParameter( "orderNo" ); //調用微信Oauth2.0獲取openid String redirectURL=ServerConfig.SERVERDOMAIN+ "/BasicWeixin/payServletForH5?orderNo=" +orderNo; String redirectURI= "" ; try { redirectURI=CommonUtil.initOpenId(redirectURL); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } //System.out.println(redirectURI); //RequestDispatcher dis= request.getRequestDispatcher(redirectURI); //dis.forward(request, response); response.sendRedirect(redirectURI); } } |
3 獲取到code后,通過REDIRECTURI獲取openId,調用統一下單接口
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
|
package com.debug.weixin.servlet; import java.io.IOException; import java.io.PrintWriter; import java.util.SortedMap; import java.util.TreeMap; import javax.servlet.RequestDispatcher; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import com.debug.weixin.pojo.WeixinOauth2Token; import com.debug.weixin.pojo.WeixinQRCode; import com.debug.weixin.util.AdvancedUtil; import com.debug.weixin.util.CommonUtil; import com.debug.weixin.util.ConfigUtil; import com.debug.weixin.util.PayCommonUtil; public class PayServletForH5 extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this .doPost(request, response); } public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String orderNo=request.getParameter( "orderNo" ); String code=request.getParameter( "code" ); //獲取AccessToken WeixinOauth2Token token=AdvancedUtil.getOauth2AccessToken(ConfigUtil.APPID, ConfigUtil.APP_SECRECT, code); String openId=token.getOpenId(); //調用微信統一支付接口 SortedMap<Object, Object> parameters = new TreeMap<Object, Object>(); parameters.put( "appid" , ConfigUtil.APPID); parameters.put( "mch_id" , ConfigUtil.MCH_ID); parameters.put( "device_info" , "1000" ); parameters.put( "body" , "我的測試訂單" ); parameters.put( "nonce_str" , PayCommonUtil.CreateNoncestr()); parameters.put( "out_trade_no" , orderNo); //parameters.put("total_fee", String.valueOf(total)); parameters.put( "total_fee" , "1" ); parameters.put( "spbill_create_ip" , request.getRemoteAddr()); parameters.put( "notify_url" , ConfigUtil.NOTIFY_URL); parameters.put( "trade_type" , "JSAPI" ); parameters.put( "openid" , openId); String sign = PayCommonUtil.createSign( "UTF-8" , parameters); parameters.put( "sign" , sign); String requestXML = PayCommonUtil.getRequestXml(parameters); String result = CommonUtil.httpsRequestForStr(ConfigUtil.UNIFIED_ORDER_URL, "POST" , requestXML); System.out.println( "----------------------------------" ); System.out.println(result); System.out.println( "----------------------------------" ); request.setAttribute( "orderNo" , orderNo); request.setAttribute( "totalPrice" , "0.01" ); String payJSON= "" ; try { payJSON=CommonUtil.getH5PayStr(result,request); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } //System.out.println(payJSON); request.setAttribute( "unifiedOrder" ,payJSON); RequestDispatcher dis= request.getRequestDispatcher( "h5Pay.jsp" ); dis.forward(request, response); } } |
調用微信統一下單接口,需要注意簽名算法,只有簽名計算正確才能順利支付
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
public static String getH5PayStr(String result,HttpServletRequest request) throws Exception{ Map<String, String> map = XMLUtil.doXMLParse(result); SortedMap<Object,Object> params = new TreeMap<Object,Object>(); params.put( "appId" , ConfigUtil.APPID); params.put( "timeStamp" , Long.toString( new Date().getTime())); params.put( "nonceStr" , PayCommonUtil.CreateNoncestr()); params.put( "package" , "prepay_id=" +map.get( "prepay_id" )); params.put( "signType" , ConfigUtil.SIGN_TYPE); String paySign = PayCommonUtil.createSign( "UTF-8" , params); params.put( "paySign" , paySign); //paySign的生成規則和Sign的生成規則一致 String json = JSONObject.fromObject(params).toString(); return json; } |
4 編寫最終的支付界面調起微信H5支付
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
|
<%@ page language= "java" import = "java.util.*" pageEncoding= "UTF-8" %> <% String path = request.getContextPath(); String basePath = request.getScheme()+ "://" +request.getServerName()+ ":" +request.getServerPort()+path+ "/" ; %> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" > <html> <head> <base href= "<%=basePath%>" > <title>微信H5支付</title> <meta name= "viewport" content= "width=device-width, initial-scale=1.0, maximum-scale=1.0" > <script type= "text/javascript" > function jsApiCall(){ WeixinJSBridge.invoke( 'getBrandWCPayRequest' ,<%=(String)request.getAttribute( "unifiedOrder" )%>, function(res){ WeixinJSBridge.log(res.err_msg); //alert(res.err_code+res.err_desc+res.err_msg); if (res.err_msg == "get_brand_wcpay_request:ok" ) { alert( "恭喜你,支付成功!" ); } else { alert(res.err_code+res.err_desc+res.err_msg); } } ); } function callpay(){ if (typeof WeixinJSBridge == "undefined" ){ if ( document.addEventListener ){ document.addEventListener( 'WeixinJSBridgeReady' , jsApiCall, false ); } else if (document.attachEvent){ document.attachEvent( 'WeixinJSBridgeReady' , jsApiCall); document.attachEvent( 'onWeixinJSBridgeReady' , jsApiCall); } } else { jsApiCall(); } } </script> </head> <body> <input type= "button" value= "支付" onclick= "callpay()" /> </body> </html> |
5 處理微信支付結果通知
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
|
package com.debug.weixin.servlet; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.PrintWriter; import java.util.Map; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.jdom.JDOMException; import com.debug.weixin.util.PayCommonUtil; import com.debug.weixin.util.XMLUtil; public class PayHandlerServlet extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this .doPost(request, response); } public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { InputStream inStream = request.getInputStream(); ByteArrayOutputStream outSteam = new ByteArrayOutputStream(); byte [] buffer = new byte [ 1024 ]; int len = 0 ; while ((len = inStream.read(buffer)) != - 1 ) { outSteam.write(buffer, 0 , len); } outSteam.close(); inStream.close(); String result = new String(outSteam.toByteArray(), "utf-8" ); //獲取微信調用我們notify_url的返回信息 Map<Object, Object> map= null ; try { map = XMLUtil.doXMLParse(result); } catch (JDOMException e) { // TODO Auto-generated catch block e.printStackTrace(); } for (Object keyValue : map.keySet()){ System.out.println(keyValue+ "=" +map.get(keyValue)); } if (map.get( "result_code" ).toString().equalsIgnoreCase( "SUCCESS" )) { //對訂單進行業務操作 System.out.println( "-------------OK" ); response.getWriter().write(PayCommonUtil.setXML( "SUCCESS" , "" )); //告訴微信服務器,我收到信息了,不要在調用回調action了 } } } |
對于上面的代碼,有很多都是參考http://blog.csdn.net/u011160656/article/details/41759195,因此這部分的代碼就不貼出來了,需要的話看這個博客就知道了。
二 微信掃碼支付(模式一)
要點:必須調用長鏈接轉短鏈接接口、正確配置掃碼支付回調URL
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
|
package com.debug.weixin.util; import com.google.zxing.common.BitMatrix; import javax.imageio.ImageIO; import java.io.File; import java.io.OutputStream; import java.io.IOException; import java.awt.image.BufferedImage; public final class MatrixToImageWriter { private static final int BLACK = 0xFF000000 ; private static final int WHITE = 0xFFFFFFFF ; private MatrixToImageWriter() {} public static BufferedImage toBufferedImage(BitMatrix matrix) { int width = matrix.getWidth(); int height = matrix.getHeight(); BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); for ( int x = 0 ; x < width; x++) { for ( int y = 0 ; y < height; y++) { image.setRGB(x, y, matrix.get(x, y) ? BLACK : WHITE); } } return image; } public static void writeToFile(BitMatrix matrix, String format, File file) throws IOException { BufferedImage image = toBufferedImage(matrix); if (!ImageIO.write(image, format, file)) { throw new IOException( "Could not write an image of format " + format + " to " + file); } } public static void writeToStream(BitMatrix matrix, String format, OutputStream stream) throws IOException { BufferedImage image = toBufferedImage(matrix); if (!ImageIO.write(image, format, stream)) { throw new IOException( "Could not write an image of format " + format); } } } |
這個算是工具類,還有一個就是把二維碼顯示在界面上的方法,CreateQRCode主要用到代碼塊:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
public static void createCodeStream(String text,HttpServletResponse response) throws Exception{ // response.setContentType("image/jpeg"); ServletOutputStream sos = response.getOutputStream(); int width = 500 ; int height = 500 ; //二維碼的圖片格式 String format = "jpg" ; MultiFormatWriter multiFormatWriter = new MultiFormatWriter(); Map hints = new HashMap(); //內容所使用編碼 hints.put(EncodeHintType.CHARACTER_SET, "UTF-8" ); BitMatrix bitMatrix = multiFormatWriter.encode(text, BarcodeFormat.QR_CODE, width, height, hints); //生成二維碼 MatrixToImageWriter.writeToStream(bitMatrix, format,sos); sos.close(); } |
2 長鏈接轉短鏈接生成二維碼,編寫掃碼支付回調方法并調用統一下單接口
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
|
package com.debug.weixin.servlet; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.PrintWriter; import java.util.Date; import java.util.Map; import java.util.SortedMap; import java.util.TreeMap; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.jdom.JDOMException; import com.debug.weixin.util.CommonUtil; import com.debug.weixin.util.ConfigUtil; import com.debug.weixin.util.CreateQRCode; import com.debug.weixin.util.PayCommonUtil; import com.debug.weixin.util.XMLUtil; import com.mongodb.DBObject; public class ScanCodePayServlet extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this .doPost(request, response); } public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String flag=request.getParameter( "flag" ); if ( "createCode" .equals(flag)){ createPayCode(request,response); } else { try { wxScanCodeHandler(request,response); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } } public void createPayCode(HttpServletRequest request,HttpServletResponse response){ String orderNo=request.getParameter( "orderNo" ); SortedMap<Object,Object> paras = new TreeMap<Object,Object>(); paras.put( "appid" , ConfigUtil.APPID); paras.put( "mch_id" , ConfigUtil.MCH_ID); paras.put( "time_stamp" , Long.toString( new Date().getTime())); paras.put( "nonce_str" , PayCommonUtil.CreateNoncestr()); paras.put( "product_id" , orderNo); //商品號要唯一 String sign = PayCommonUtil.createSign( "UTF-8" , paras); paras.put( "sign" , sign); String url = "weixin://wxpay/bizpayurl?sign=SIGN&appid=APPID&mch_id=MCHID&product_id=PRODUCTID&time_stamp=TIMESTAMP&nonce_str=NOCESTR" ; String nativeUrl = url.replace( "SIGN" , sign).replace( "APPID" , ConfigUtil.APPID).replace( "MCHID" , ConfigUtil.MCH_ID).replace( "PRODUCTID" , (String)paras.get( "product_id" )).replace( "TIMESTAMP" , (String)paras.get( "time_stamp" )).replace( "NOCESTR" , (String)paras.get( "nonce_str" )); SortedMap<Object,Object> parameters = new TreeMap<Object,Object>(); parameters.put( "appid" , ConfigUtil.APPID); parameters.put( "mch_id" , ConfigUtil.MCH_ID); parameters.put( "nonce_str" , PayCommonUtil.CreateNoncestr()); parameters.put( "long_url" , CommonUtil.urlEncodeUTF8(nativeUrl)); String sign2 = PayCommonUtil.createSign( "UTF-8" , parameters); parameters.put( "sign" , sign2); String requestXML = PayCommonUtil.getRequestXml(parameters); String result =CommonUtil.httpsRequestForStr(ConfigUtil.SHORT_URL, "POST" , requestXML); Map<String, String> map= null ; try { map = XMLUtil.doXMLParse(result); } catch (JDOMException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } String returnCode = map.get( "return_code" ); String resultCode = map.get( "result_code" ); if (returnCode.equalsIgnoreCase( "SUCCESS" )&&resultCode.equalsIgnoreCase( "SUCCESS" )){ String shortUrl = map.get( "short_url" ); //TODO 拿到shortUrl,寫代碼生成二維碼 System.out.println( "shortUrl=" +shortUrl); try { CreateQRCode.createCodeStream(shortUrl,response); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } } public void wxScanCodeHandler(HttpServletRequest request,HttpServletResponse response) throws Exception { InputStream inStream = request.getInputStream(); ByteArrayOutputStream outSteam = new ByteArrayOutputStream(); byte [] buffer = new byte [ 1024 ]; int len = 0 ; while ((len = inStream.read(buffer)) != - 1 ) { outSteam.write(buffer, 0 , len); } outSteam.close(); inStream.close(); String result = new String(outSteam.toByteArray(), "utf-8" ); //獲取微信調用我們notify_url的返回信息 Map<Object, Object> map= null ; try { map = XMLUtil.doXMLParse(result); } catch (JDOMException e) { // TODO Auto-generated catch block e.printStackTrace(); } for (Object keyValue : map.keySet()){ System.out.println(keyValue+ "=" +map.get(keyValue)); } String orderNo=map.get( "product_id" ).toString(); //接收到請求參數后調用統一下單接口 SortedMap<Object, Object> parameters = new TreeMap<Object, Object>(); parameters.put( "appid" , ConfigUtil.APPID); parameters.put( "mch_id" , ConfigUtil.MCH_ID); parameters.put( "device_info" , "1000" ); parameters.put( "body" , "測試掃碼支付訂單" ); parameters.put( "nonce_str" , PayCommonUtil.CreateNoncestr()); parameters.put( "out_trade_no" , map.get( "product_id" )); //parameters.put("total_fee", String.valueOf(totalPrice)); parameters.put( "total_fee" , "1" ); parameters.put( "spbill_create_ip" , request.getRemoteAddr()); parameters.put( "notify_url" , ConfigUtil.NOTIFY_URL); parameters.put( "trade_type" , "NATIVE" ); parameters.put( "openid" , map.get( "openid" )); String sign = PayCommonUtil.createSign( "UTF-8" , parameters); parameters.put( "sign" , sign); String requestXML = PayCommonUtil.getRequestXml(parameters); String result2 = CommonUtil.httpsRequestForStr(ConfigUtil.UNIFIED_ORDER_URL, "POST" , requestXML); System.out.println( "-----------------------------統一下單結果---------------------------" ); System.out.println(result2); Map<String, String> mm= null ; try { mm=getH5PayMap(result2,request); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } //String prepayId=getPrepayId(result2,request); //String returnNoneStr=getReturnNoneStr(result2,request); String prepayId=mm.get( "prepay_id" ); String returnNoneStr=mm.get( "nonce_str" );; SortedMap<Object, Object> lastSign = new TreeMap<Object, Object>(); lastSign.put( "return_code" , "SUCCESS" ); lastSign.put( "appid" , ConfigUtil.APPID); lastSign.put( "mch_id" , ConfigUtil.MCH_ID); lastSign.put( "nonce_str" , returnNoneStr); lastSign.put( "prepay_id" , prepayId); lastSign.put( "result_code" , "SUCCESS" ); lastSign.put( "key" , ConfigUtil.API_KEY); String lastSignpara = PayCommonUtil.createSign( "UTF-8" , lastSign); StringBuffer buf= new StringBuffer(); buf.append( "<xml>" ); buf.append( "<return_code>SUCCESS</return_code>" ); buf.append( "<appid>" +ConfigUtil.APPID+ "</appid>" ); buf.append( "<mch_id>" +ConfigUtil.MCH_ID+ "</mch_id>" ); buf.append( "<nonce_str>" +returnNoneStr+ "</nonce_str>" ); buf.append( "<prepay_id>" +prepayId+ "</prepay_id>" ); buf.append( "<result_code>SUCCESS</result_code>" ); buf.append( "<sign>" +lastSignpara+ "</sign>" ); buf.append( "</xml>" ); response.getWriter().print(buf.toString()); } public Map<String, String> getH5PayMap(String result,HttpServletRequest request) throws Exception{ Map<String, String> map = XMLUtil.doXMLParse(result); return map; } } |
最終看下公眾號支付和掃碼支付的微信配置:
希望通過這篇文章,大家能明白就算通過Java來做微信公眾號、微信支付而不借助github提供的那些坑人的代碼也可以開發出另自己和客戶滿意的微信應用。雖然微信給出的demo都是PHP的,但這些都是浮云,開發語言是其次,理解接口調用需具備的底層只是才是程序員的必修課。