前言
由於項目需要做微信支付和支付寶支付,自己花時間研究了一下,也百度了很久,最
后發現百度上很多都講得不是很詳細,對於新手小白來說,還是比較難得,所以自己整理
了一下自己寫的,也有很多是參考的,希望能給大家帶來幫助
一 圖示(亂畫的,方便看)
二 工具類准備
2.1由於微信支付發送的https的post請求,所以需要寫個工具類來實現https的發送
發送https需要自定義一個TrustManager實現X509TrustManager類
package com.rubbish.jinzhu.utils; import javax.net.ssl.X509TrustManager; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; public class TrustManager implements X509TrustManager { @Override public void checkClientTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException { } @Override public void checkServerTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException { } @Override public X509Certificate[] getAcceptedIssuers() { return new X509Certificate[0]; } }
發送https請求工具類
package com.rubbish.jinzhu.utils; import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLSocketFactory; import java.io.BufferedReader; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.net.URL; public class HttpsUtil { /** * * @param requestUrl * @param requestMethod * @param outputStr * @return * 發送https請求 */ public static String httpsRequest(String requestUrl,String requestMethod,String outputStr){ StringBuffer buffer=null; try{ SSLContext sslContext=SSLContext.getInstance("SSL"); TrustManager[] tm={new TrustManager()}; sslContext.init(null, tm, new java.security.SecureRandom());; SSLSocketFactory ssf=sslContext.getSocketFactory(); URL url=new URL(requestUrl); HttpsURLConnection conn=(HttpsURLConnection)url.openConnection(); conn.setDoOutput(true); conn.setDoInput(true); conn.setUseCaches(false); conn.setRequestMethod(requestMethod); conn.setSSLSocketFactory(ssf); conn.connect(); if(null!=outputStr){ OutputStream os=conn.getOutputStream(); os.write(outputStr.getBytes("utf-8")); os.close(); } //讀取服務器端返回的內容 InputStream is=conn.getInputStream(); InputStreamReader isr=new InputStreamReader(is,"utf-8"); BufferedReader br=new BufferedReader(isr); buffer=new StringBuffer(); String line=null; while((line=br.readLine())!=null){ buffer.append(line); } }catch(Exception e){ e.printStackTrace(); } return buffer.toString(); } }
2.2 微信統一下單需要的參數(接口文檔地址https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=9_1)
2.2.1 隨機字符串生成,簽名,xml轉換工具類
package com.rubbish.jinzhu.utils; import org.jdom.JDOMException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.xml.sax.SAXException; import javax.xml.parsers.ParserConfigurationException; import java.io.IOException; import java.util.Iterator; import java.util.Map; import java.util.Map.Entry; import java.util.Random; import java.util.SortedMap; public class PayCommonUtil { private static Logger logger = LoggerFactory.getLogger(PayCommonUtil.class); /** * 自定義長度隨機字符串 * @param length * @return */ public static String createConceStr(int length) { String strs = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; String str = ""; for (int i = 0; i < length; i++) { // str +=strs.substring(0, new Random().nextInt(strs.length())); char achar = strs.charAt(new Random().nextInt(strs.length() - 1)); str += achar; } return str; } /** * 默認16 位隨機字符串 * @return */ 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; } /** * 簽名工具 * @date 2014-12-5下午2:29:34 * @Description:sign簽名 * @param characterEncoding * 編碼格式 UTF-8 * @param parameters * 請求參數 * @return */ public static String createSign(String characterEncoding, Map<String, Object> parameters) { StringBuffer sb = new StringBuffer(); Iterator<Entry<String, Object>> it = parameters.entrySet().iterator(); while (it.hasNext()) { Entry <String,Object>entry = (Entry<String,Object>) it.next(); String key = (String) entry.getKey(); Object value = entry.getValue();//去掉帶sign的項 if (null != value && !"".equals(value) && !"sign".equals(key) && !"key".equals(key)) { sb.append(key + "=" + value + "&"); } } sb.append("key=" + ConfigUtil.API_KEY); //注意sign轉為大寫 return MD5Util.MD5Encode(sb.toString(), characterEncoding).toUpperCase(); } /** * @date * @Description:將請求參數轉換為xml格式的string * @param parameters * 請求參數 * @return */ public static String getRequestXml(SortedMap<String, Object> parameters) { StringBuffer sb = new StringBuffer(); sb.append("<xml>"); Iterator<Entry<String, Object>> iterator = parameters.entrySet().iterator(); while (iterator.hasNext()) { Entry<String,Object> entry = (Entry<String,Object>) iterator.next(); String key = (String) entry.getKey(); String value = (String) entry.getValue(); sb.append("<" + key + ">" + value + "</" + key + ">"); } sb.append("</xml>"); return sb.toString(); } public static String setXML(String return_code, String return_msg) { return "<xml><return_code><![CDATA[" + return_code + "]]></return_code><return_msg><![CDATA[" + return_msg + "]]></return_msg></xml>"; } /** * 檢驗API返回的數據里面的簽名是否合法 * * @param responseString API返回的XML數據字符串 * @return API簽名是否合法 * @throws ParserConfigurationException * @throws IOException * @throws SAXException */ public static boolean checkIsSignValidFromResponseString(String responseString) { try { SortedMap<String, Object> map = XMLUtil.doXMLParse(responseString); logger.debug(map.toString()); String signFromAPIResponse = map.get("sign").toString(); if ("".equals(signFromAPIResponse) || signFromAPIResponse == null) { logger.debug("API返回的數據簽名數據不存在,有可能被第三方篡改!!!"); return false; } logger.debug("服務器回包里面的簽名是:" + signFromAPIResponse); map.put("sign", ""); String signForAPIResponse = PayCommonUtil.createSign("UTF-8", map); if (!signForAPIResponse.equals(signFromAPIResponse)) { logger.debug("數據簽名驗證不通過"); return false; } logger.debug("恭喜,數據簽名驗證通過!!!"); return true; } catch (Exception e) { return false; } } }
微信的一些固定參數
package com.rubbish.jinzhu.utils; public class ConfigUtil {
/**
* 服務號相關信息
*/
public final static String APPID = "xxxxxxx";// 應用號
public final static String APP_SECRECT = "xxxxx";// 應用密碼
public final static String MCH_ID = "xxxxx";// 商戶號 xxxx 公眾號商戶id
public final static String API_KEY = "xxxxxx";// API密鑰
public final static String SIGN_TYPE = "MD5";// 簽名加密方式
public final static String TRADE_TYPE = "APP";// 支付類型
public final static String UNIFIED_ORDER_URL = "https://api.mch.weixin.qq.com/pay/unifiedorder"; // 微信支付統一接口(POST)
}
xml讀取的工具類
package com.rubbish.jinzhu.utils; import org.jdom.Document; import org.jdom.Element; import org.jdom.JDOMException; import org.jdom.input.SAXBuilder; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.util.Iterator; import java.util.List; import java.util.SortedMap; import java.util.TreeMap; public class XMLUtil { /** * 解析xml,返回第一級元素鍵值對。 * 如果第一級元素有子節點, * 則此節點的值是子節點的xml數據。 * * @param strxml * @return * @throws JDOMException * @throws IOException */ public static SortedMap<String, Object> doXMLParse(String strxml) throws JDOMException, IOException { strxml = strxml.replaceFirst("encoding=\".*\"", "encoding=\"UTF-8\""); if (null == strxml || "".equals(strxml)) { return null; } SortedMap<String, Object> map = new TreeMap<String, Object>(); InputStream in = new ByteArrayInputStream(strxml.getBytes("UTF-8")); SAXBuilder builder = new SAXBuilder(); Document doc = builder.build(in); Element root = doc.getRootElement(); List list = root.getChildren(); Iterator it = list.iterator(); while (it.hasNext()) { Element e = (Element) it.next(); String key = e.getName(); String value = ""; List children = e.getChildren(); if (children.isEmpty()) { value = e.getTextNormalize(); } else { value = XMLUtil.getChildrenText(children); } map.put(key, value); } // 關閉流 in.close(); return map; } /** * 獲取子結點的xml * @param children * @return */ public static String getChildrenText(List children) { StringBuffer sb = new StringBuffer(); if (!children.isEmpty()) { Iterator it = children.iterator(); while (it.hasNext()) { Element e = (Element) it.next(); String name = e.getName(); String value = e.getTextNormalize(); List list = e.getChildren(); sb.append("<" + name + ">"); if (!list.isEmpty()) { sb.append(XMLUtil.getChildrenText(list)); } sb.append(value); sb.append("</" + name + ">"); } } return sb.toString(); } }
根據key值對map進行ascii排序
package com.rubbish.jinzhu.utils; import java.util.*; import java.util.Map.Entry; public class MapUtils { /** * 對map根據key進行排序 ASCII 順序 * * @param 無序的map * @return */ public static SortedMap<String, Object> sortMap(Map<String, Object> map) { List<Entry<String, Object>> infoIds = new ArrayList<Entry<String, Object>>( map.entrySet()); Collections.sort(infoIds, new Comparator<Entry<String, Object>>() { public int compare(Entry<String, Object> o1, Entry<String, Object> o2) { // return (o2.getValue() - o1.getValue());//value處理 return (o1.getKey()).toString().compareTo(o2.getKey()); } }); SortedMap<String, Object> sortmap = new TreeMap<String, Object>(); for (int i = 0; i < infoIds.size(); i++) { String[] split = infoIds.get(i).toString().split("="); sortmap.put(split[0], split[1]); } return sortmap; } }
三 controller 類
package com.rubbish.jinzhu.controller; import com.google.common.base.Charsets; import com.google.common.base.Strings; import com.google.common.collect.ImmutableMap;import com.rubbish.jinzhu.utils.*;import org.springframework.web.bind.annotation.*; import java.io.UnsupportedEncodingException; import java.net.InetAddress; import java.net.UnknownHostException; import java.util.Map; import java.util.SortedMap; import static com.rubbish.jinzhu.utils.MapUtils.sortMap; @RestController public class TradeController { @RequestMapping("/trade/prepare_pay") public SortedMap<String, Object> preparePay(@RequestParam String ip, @RequestParam String tradeId, @RequestParam int price) { if (Strings.isNullOrEmpty(ip)) { try { InetAddress addr = InetAddress.getLocalHost(); ip = addr.getHostAddress().toString(); } catch (UnknownHostException e) { e.printStackTrace(); } } SortedMap<String, Object> parameters = prepareOrder(ip, tradeId, price); parameters.put("sign", PayCommonUtil.createSign(Charsets.UTF_8.toString(), parameters));// sign簽名 key String requestXML = PayCommonUtil.getRequestXml(parameters);// 生成xml格式字符串 String responseStr = HttpUtil.httpsRequest(ConfigUtil.UNIFIED_ORDER_URL, "POST", requestXML); try { SortedMap<String, Object> resultMap = XMLUtil.doXMLParse(responseStr); SortedMap<String, Object> map = buildClientJson(resultMap); return map; } catch (Exception e) { e.printStackTrace(); return null; } }
//預支付成功,返回給app的參數 private SortedMap<String, Object> buildClientJson( Map<String, Object> resutlMap) throws UnsupportedEncodingException { Map<String, Object> params = ImmutableMap.<String, Object> builder() .put("appid", ConfigUtil.APPID)//應用號 .put("noncestr", PayCommonUtil.CreateNoncestr())//隨機字符串 .put("package", "Sign=WXPay")//固定的字符串,不需要改變 .put("partnerid", ConfigUtil.MCH_ID)//商戶號 .put("prepayid", resutlMap.get("prepay_id"))//預支付微信返回的id .put("timestamp", DateUtils.getTimeStamp()) // 10 位時間戳 .build(); SortedMap<String, Object> sortMap = sortMap(params); sortMap.put("package", "Sign=WXPay"); String paySign = PayCommonUtil.createSign(Charsets.UTF_8.toString(), sortMap); sortMap.put("sign", paySign); return sortMap; }
//預支付參數准備 private SortedMap<String, Object> prepareOrder(String ip, String tradeId, int price) { Map<String, Object> oparams = ImmutableMap.<String, Object> builder() .put("appid", ConfigUtil.APPID)//應用號 .put("mch_id", ConfigUtil.MCH_ID)// 商戶號 .put("nonce_str", PayCommonUtil.CreateNoncestr())// 16隨機字符串(大小寫字母加數字) .put("body", "金株互聯支付")// 商品描述 .put("out_trade_no", tradeId)// 商戶訂單號 .put("total_fee", price) .put("spbill_create_ip", ip)// IP地址 .put("notify_url", "http://127.0.0.1:8082/api/trade/paid/wx") // 微信回調地址 .put("trade_type", ConfigUtil.TRADE_TYPE)// 支付類型 APP .build();//支付金額 return sortMap(oparams); } private String callback(String responseStr) { try { Map<String, Object> map = XMLUtil.doXMLParse(responseStr); if (!PayCommonUtil.checkIsSignValidFromResponseString(responseStr)) { return PayCommonUtil.setXML(WeixinConstant.FAIL, "invalid sign"); } if (WeixinConstant.FAIL.equalsIgnoreCase(map.get("result_code") .toString())) { return PayCommonUtil.setXML(WeixinConstant.FAIL, "weixin pay fail"); } if (WeixinConstant.SUCCESS.equalsIgnoreCase(map.get("result_code") .toString())) { // 對數據庫的操作 String outTradeNo = (String) map.get("out_trade_no"); String transactionId = (String) map.get("transaction_id"); String totlaFee = (String) map.get("total_fee"); Integer totalPrice = Integer.valueOf(totlaFee) / 100;//服務器這邊記錄的是錢的元 // Trade trade = tradeBiz.get(Integer.valueOf(outTradeNo)); // trade.setTransactionId(transactionId); //boolean isOk = tradeBiz.paid(trade); // if (isOk) { // return PayCommonUtil.setXML(WeixinConstant.SUCCESS, "OK"); // } else { // return PayCommonUtil // .setXML(WeixinConstant.FAIL, "update bussiness outTrade fail"); //} } } catch (Exception e) { return PayCommonUtil.setXML(WeixinConstant.FAIL, "weixin pay server exception"); } return PayCommonUtil.setXML(WeixinConstant.FAIL, "weixin pay fail"); } }