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