上一篇已經獲取到了用戶的OpenId
這篇主要是調用微信公眾支付的統一下單API
API地址:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_1
看文檔,主要流程就是把20個左右的參數封裝為XML格式發送到微信給的接口地址,然后就可以獲取到返回的內容了,如果成功里面就有支付所需要的預支付ID
請求參數就不解釋了。
其中,隨機字符串:我用的是UUID去中劃線
1
2
3
|
public static String create_nonce_str() { return UUID.randomUUID().toString().replace( "-" , "" ); } |
商戶訂單號:每個訂單號只能使用一次,所以用的是系統的訂單號加的時間戳。
總金額:不能為
通知地址:微信支付成功或失敗回調給系統的地址
簽名:
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
|
import java.io.Serializable; public class PayInfo implements Serializable{ private static final long serialVersionUID = L; private String appid; private String mch_id; private String device_info; private String nonce_str; private String sign; private String body; private String attach; private String out_trade_no; private int total_fee; private String spbill_create_ip; private String notify_url; private String trade_type; private String openid; //下面是get,set方法 } /** * 創建統一下單的xml的java對象 * @param bizOrder 系統中的業務單號 * @param ip 用戶的ip地址 * @param openId 用戶的openId * @return */ public PayInfo createPayInfo(BizOrder bizOrder,String ip,String openId) { PayInfo payInfo = new PayInfo(); payInfo.setAppid(Constants.appid); payInfo.setDevice_info( "WEB" ); payInfo.setMch_id(Constants.mch_id); payInfo.setNonce_str(CommonUtil.create_nonce_str().replace( "-" , "" )); payInfo.setBody( "這里是某某白米飯的body" ); payInfo.setAttach(bizOrder.getId()); payInfo.setOut_trade_no(bizOrder.getOrderCode().concat( "A" ).concat(DateFormatUtils.format( new Date(), "MMddHHmmss" ))); payInfo.setTotal_fee(( int )bizOrder.getFeeAmount()); payInfo.setSpbill_create_ip(ip); payInfo.setNotify_url(Constants.notify_url); payInfo.setTrade_type( "JSAPI" ); payInfo.setOpenid(openId); return payInfo; } |
獲取簽名:
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
|
/** * 獲取簽名 * @param payInfo * @return * @throws Exception */ public String getSign(PayInfo payInfo) throws Exception { String signTemp = "appid=" +payInfo.getAppid() + "&attach=" +payInfo.getAttach() + "&body=" +payInfo.getBody() + "&device_info=" +payInfo.getDevice_info() + "&mch_id=" +payInfo.getMch_id() + "&nonce_str=" +payInfo.getNonce_str() + "¬ify_url=" +payInfo.getNotify_url() + "&openid=" +payInfo.getOpenid() + "&out_trade_no=" +payInfo.getOut_trade_no() + "&spbill_create_ip=" +payInfo.getSpbill_create_ip() + "&total_fee=" +payInfo.getTotal_fee() + "&trade_type=" +payInfo.getTrade_type() + "&key=" +Constants.key; //這個key注意 MessageDigest md = MessageDigest.getInstance( "MD" ); md.reset(); md.update(signTemp.getBytes( "UTF-" )); String sign = CommonUtil.byteToStr(md.digest()).toUpperCase(); return sign; } |
注意:上面的Constants.key取值在商戶號API安全的API密鑰中。
一些工具方法:獲取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
|
/** * 將字節數組轉換為十六進制字符串 * * @param byteArray * @return */ public static String byteToStr( byte [] byteArray) { String strDigest = "" ; for ( int i = ; i < byteArray.length; i++) { strDigest += byteToHexStr(byteArray[i]); } return strDigest; } /** * 將字節轉換為十六進制字符串 * * @param btyes * @return */ public static String byteToHexStr( byte bytes) { char [] Digit = { '' , '' , '' , '' , '' , '' , '' , '' , '' , '' , 'A' , 'B' , 'C' , 'D' , 'E' , 'F' }; char [] tempArr = new char []; tempArr[] = Digit[(bytes >>> ) & XF]; tempArr[] = Digit[bytes & XF]; String s = new String(tempArr); return s; } /** * 獲取ip地址 * @param request * @return */ public static String getIpAddr(HttpServletRequest request) { InetAddress addr = null ; try { addr = InetAddress.getLocalHost(); } catch (UnknownHostException e) { return request.getRemoteAddr(); } byte [] ipAddr = addr.getAddress(); String ipAddrStr = "" ; for ( int i = ; i < ipAddr.length; i++) { if (i > ) { ipAddrStr += "." ; } ipAddrStr += ipAddr[i] & xFF; } return ipAddrStr; } |
這樣就獲取了簽名,把簽名與PayInfo中的其他數據轉成XML格式,當做參數傳遞給統一下單地址。
1
2
3
|
PayInfo pi = pu.createPayInfo(bo, "..." , "" ); String sign = pu.getSign(pi); pi.setSign(sign); |
對象轉XML
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
|
/** * 擴展xstream使其支持CDATA */ private static XStream xstream = new XStream( new XppDriver() { public HierarchicalStreamWriter createWriter(Writer out) { return new PrettyPrintWriter(out) { //增加CDATA標記 boolean cdata = true ; @SuppressWarnings ( "rawtypes" ) public void startNode(String name, Class clazz) { super .startNode(name, clazz); } protected void writeText(QuickWriter writer, String text) { if (cdata) { writer.write( "<![CDATA[" ); writer.write(text); writer.write( "]]>" ); } else { writer.write(text); } } }; } }); public static String payInfoToXML(PayInfo pi) { xstream.alias( "xml" , pi.getClass()); return xstream.toXML(pi); } |
xml轉Map
1
2
3
4
5
6
7
8
9
10
|
@SuppressWarnings ( "unchecked" ) public static Map<String, String> parseXml(String xml) throws Exception { Map<String, String> map = new HashMap<String, String>(); Document document = DocumentHelper.parseText(xml); Element root = document.getRootElement(); List<Element> elementList = root.elements(); for (Element e : elementList) map.put(e.getName(), e.getText()); return map; } |
下面就是調用統一下單的URL了
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
log.info(MessageUtil.payInfoToXML(pi).replace( "__" , "_" )); Map<String, String> map = CommonUtil.httpsRequestToXML( "https://api.mch.weixin.qq.com/pay/unifiedorder" , "POST" , MessageUtil.payInfoToXML(pi).replace( "__" , "_" ).replace( "<![CDATA[" , "" ).replace( "]]>" , "" )); log.info(map); public static Map<String, String> httpsRequestToXML(String requestUrl, String requestMethod, String outputStr) { Map<String, String> result = new HashMap<>(); try { StringBuffer buffer = httpsRequest(requestUrl, requestMethod, outputStr); result = MessageUtil.parseXml(buffer.toString()); } catch (ConnectException ce) { log.error( "連接超時:" +ce.getMessage()); } catch (Exception e) { log.error( "https請求異常:" +ece.getMessage()); } return result; } |
httpsRequest()這個方法在第一篇中
上面獲取到的Map如果成功的話,里面就會有
1
2
3
4
5
6
7
8
9
10
|
String return_code = map.get( "return_code" ); if (StringUtils.isNotBlank(return_code) && return_code.equals( "SUCCESS" )){ String return_msg = map.get( "return_msg" ); if (StringUtils.isNotBlank(return_msg) && !return_msg.equals( "OK" )) { return "統一下單錯誤!" ; } } else { return "統一下單錯誤!" ; } String prepay_Id = map.get( "prepay_id" ); |
這個prepay_id就是預支付的ID。后面支付需要它。