最近公司在開發新項目,須要微信支付 經歷了幾天時間 終於寫出來了 不過微信文檔也不是很容易閱讀 東西不全 不少東西全靠猜php
注意:全部帶加密的都須要帶上微信商戶祕鑰(32位) 即key前端
這邊主要是java服務端代碼 :java
1.1.首先是統一下單接口git
一。後端
微信提供的下單接口 https://api.mch.weixin.qq.com/pay/unifiedorderapi
經過POST接口訪問微信
Url是訪問地址 data是微信轉成xml格式的傳輸參數 必須是UTF-8格式的參數app
微信要求的格式是這樣 咱們只須要填寫必要參數就好了。dom
<xml> <appid>wx2421b1c4370ec43b</appid> <attach>支付測試</attach> <body>APP支付測試</body> <mch_id>10000100</mch_id> <nonce_str>1add1a30ac87aa2db72f57a2375d8fec</nonce_str> <notify_url>http://wxpay.wxutil.com/pub_v2/pay/notify.v2.php</notify_url> <out_trade_no>1415659990</out_trade_no> <spbill_create_ip>14.23.150.211</spbill_create_ip> <total_fee>1</total_fee> <trade_type>APP</trade_type> <sign>0CB01533B8C1EF103065174F50BCA001</sign> </xml>
==============Post 請求封裝方法===================ide
public static String POST(String url, String data){ CloseableHttpClient client = HttpClients.createDefault(); CloseableHttpResponse response = null; try { HttpPost method = new HttpPost(url); method.addHeader(HTTP.CONTENT_TYPE, "application/xml"); StringEntity entity = new StringEntity(data,"UTF-8"); method.setEntity(entity); response = client.execute(method); String s = EntityUtils.toString(response.getEntity(), "UTF-8"); return s; } catch (IOException e) { e.printStackTrace(); }finally { try { response.close(); } catch (Exception e) { e.printStackTrace(); } } return null; }
//返回的數據也是xml格式的
https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=9_1 微信文檔寫的很清楚
1.2.生成隨機字符串
這裏我是經過UUID生成的32位隨機字符串
/** * 生成32位隨機數 * @return */ public static String getRandomNumberAlgorithm(){ String uuid = UUID.randomUUID().toString().toUpperCase(); String replace = uuid.replace("-", ""); return replace; }
1.3 將Map 轉換成xml格式的數據字符串
/** * @author * @Description:將請求參數轉換爲xml格式的string 不包含CDATA標籤 * @param parameters * 請求參數 * @return */ public static String getRequestXml2(SortedMap<String, Object> parameters) { StringBuffer sb = new StringBuffer(); sb.append("<xml>"); Set<?> es = parameters.entrySet(); Iterator<?> it = es.iterator(); while (it.hasNext()) { @SuppressWarnings("rawtypes") Map.Entry entry = (Map.Entry) it.next(); String k = entry.getKey().toString(); String v = entry.getValue().toString(); sb.append("<" + k + ">" + v + "</" + k + ">"); } sb.append("</xml>"); return sb.toString(); }
1.4 生成簽名
public static String createSign(SortedMap<String, Object> packageParams, String apiKey) { StringBuffer sb = new StringBuffer(); Set<?> es = packageParams.entrySet(); Iterator<?> it = es.iterator(); while (it.hasNext()) { @SuppressWarnings("rawtypes") Map.Entry entry = (Map.Entry) it.next(); String k = entry.getKey().toString(); String v = entry.getValue().toString(); if (null != v && !"".equals(v) && !"sign".equals(k) && !"key".equals(k)) { sb.append(k + "=" + v + "&"); } } sb.append("key=" + apiKey); String sign = MD5.encoder(sb.toString()).toUpperCase(); return sign; }
這個時候你將下單返回的參數 傳遞過去 你覺得就成功了? 相信我 幻覺 絕對是幻覺 這個時候瀏覽安卓支付起掉文檔 OK 須要二次加密
ok 咱們按照參數 依次寫進去
//返回數據 CreateWeiXinPayOrderResponse createWeiXinPayOrderResponse = payResult(stringObjectSortedMap); //或者封裝到map中將數據 SortedMap<String, String> secondaryEncryption=new TreeMap<>(); String appid = stringObjectSortedMap.get("appid").toString(); secondaryEncryption.put("appid",appid); String partnerid = request.getMch_id().toString(); secondaryEncryption.put("partnerid",partnerid); String prepay_id = stringObjectSortedMap.get("prepay_id").toString(); secondaryEncryption.put("prepayid",prepay_id); String nonce_str = stringObjectSortedMap.get("nonce_str").toString(); secondaryEncryption.put("noncestr",nonce_str); secondaryEncryption.put("package","Sign=WXPay"); secondaryEncryption.put("timestamp",String.valueOf(time/1000)); String sign1 = createSign("UTF-8", secondaryEncryption, request.getKey()); stringObjectSortedMap.put("sign",sign1); createWeiXinPayOrderResponse.setMap(stringObjectSortedMap);
這個時候 APP 前端有報錯 -1 來咱們看看微信對這個描述是怎麼樣的
坑爹的其餘異常 搞了一天多 然後查看簽名 規則 發現
也就是說 無論是簽名幾回 什麼簽名 都須要帶上key 即商戶密匙!!!
順帶貼上加密 代碼:
public static String createSign(String characterEncoding, SortedMap<String, String> parameters,String key) { StringBuffer sb = new StringBuffer(); Set es = parameters.entrySet(); Iterator it = es.iterator(); while (it.hasNext()) { Map.Entry entry = (Map.Entry) it.next(); String k = (String) entry.getKey(); Object v = entry.getValue(); if (null != v && !"".equals(v) && !"sign".equals(k) && !"key".equals(k)) { sb.append(k + "=" + v + "&"); } } sb.append("key=" + key);//最後加密時添加商戶密鑰,因爲key值放在最後,因此不用添加到SortMap裏面去,單獨處理,編碼方式採用UTF-8 String sign = MD5Encode(sb.toString(), characterEncoding).toUpperCase(); return sign; } public static String MD5Encode(String origin, String charsetname) { String resultString = null; try { resultString = new String(origin); MessageDigest md = MessageDigest.getInstance("MD5"); if (charsetname == null || "".equals(charsetname)) resultString = byteArrayToHexString(md.digest(resultString .getBytes())); else resultString = byteArrayToHexString(md.digest(resultString .getBytes(charsetname))); } catch (Exception exception) { } return resultString; } private static String byteToHexString(byte b) { int n = b; if (n < 0) n += 256; int d1 = n / 16; int d2 = n % 16; return hexDigits[d1] + hexDigits[d2]; } private static final String hexDigits[] = { "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f" }; private static String byteArrayToHexString(byte b[]) { StringBuffer resultSb = new StringBuffer(); for (int i = 0; i < b.length; i++) resultSb.append(byteToHexString(b[i])); return resultSb.toString(); }
OK 完事!!!
而後是最重要的回調
回調
該連接是經過【統一下單API】中提交的參數notify_url設置,若是連接沒法訪問,商戶將沒法接收到微信通知。
通知url必須爲直接可訪問的url,不能攜帶參數。示例:notify_url:「https://pay.weixin.qq.com/wxpay/pay.action」
不能帶參數這個回調接口 須要本身提供 須要有證書的
這樣的話咱們就只能從二進制流中取得咱們須要的數據了
將數據轉換爲xml格式的數據 後端進行驗籤
@Override public String weiXinNotify(HttpServletRequest request, HttpServletResponse response) { StringBuffer xmlStr = new StringBuffer(); try { BufferedReader reader = request.getReader(); String line = null; while ((line = reader.readLine()) != null) { xmlStr.append(line); } reader.close(); logger.info("微信支付回調返回參數:" + xmlStr + ", 即將調用支付模塊回調接口"); //調用支付模塊的回調接口,處理返回出具 NotifyRequest req = new NotifyRequest(); req.setXmlStr(xmlStr.toString().trim()); NotifyResponse resp = paymentClient.weiXinPayNotify(req); logger.info("微信支付回調返回給微信端的數據:" + resp.getResultXml()); BufferedOutputStream out = new BufferedOutputStream( response.getOutputStream()); out.write(resp.getResultXml().getBytes()); out.flush(); out.close(); } catch (Exception e) { logger.error("微信回調出錯: " + e.getMessage()); throw new WebApiCommonException(WebApiCommonErrorCodeType.PayBackError); } return xmlStr.toString(); }
後端取 驗籤 開發時間緊 我就沒封裝了
public NotifyResponse weiXinPayNotify(String xmlStr) { NotifyResponse notifyResponse=new NotifyResponse(); // 獲取微信配置信息並賦值 QueryAppliactionConfigRequest req = new QueryAppliactionConfigRequest(); req.setConfigType("weixinPay"); req.setConfigName("key"); QueryAppliactionConfigResponse resp = appliactionConfigClient.queryAppliactionConfig(req); if (resp.getData()==null||resp.getData().getList().size()<0){ throw new PayCommonException(PayCommonErrorCodeType.PAY_CONFIG_ERROR); } try { SortedMap<String, Object> map = xmlParse(xmlStr); logger.info(xmlStr); //返回結果 String return_code = map.get("return_code").toString(); if ("SUCCESS".equals(return_code)){ //簽名 String sign1 = map.get("sign").toString().toUpperCase(); String sign = PayCommonUtil.createSign(map, resp.getData().getList().get(0).getConfigVal()); //回調驗籤 if(sign.equals(sign1)||runInTest){ //業務結果 String result_code = map.get("result_code").toString(); if ("SUCCESS".equals(return_code)&&"SUCCESS".equals(result_code)){ //商戶訂單號 String out_trade_no = map.get("out_trade_no").toString(); notifyResponse.setTradeNo(out_trade_no); //根據商戶訂單號獲取訂單信息 PayOrderInfo payOrderInfo = iPayOrderService.queryPayOrderInfoByTradeNo(out_trade_no); if ("5".equals(payOrderInfo.getCurrencyType())||"4".equals(payOrderInfo.getCurrencyType())){ notifyResponse.setResultXml(setXml("FAIL", "該訂單已失敗或超時")); return notifyResponse; } //根據商戶訂單號查到的狀態判斷是否進行下一步驟 payOrderInfo.setCurrencyType("1"); //交易金額 String total_fee = map.get("total_fee").toString(); //商戶系統對於支付結果通知的內容必定要作簽名驗證,並校驗返回的訂單金額是否與商戶側的訂單金額一致,防止數據泄漏致使出現「假通知」,形成資金損失。 if (Integer.valueOf(total_fee)!= payOrderInfo.getTradeAmount()) { throw new PayCommonException(PayCommonErrorCodeType.PAY_INCORRECT_AMOUNT); } payOrderInfo.setTradeAmount(Integer.valueOf(total_fee)); //支付完成時間 String time_end = map.get("time_end").toString(); if (time_end!=null){ payOrderInfo.setTradeEndTime(new Date()); } //充值方式 payOrderInfo.setTradeChannel(1004); //微信訂單號 String transaction_id = map.get("transaction_id").toString(); notifyResponse.setTradeNo(transaction_id); payOrderInfo.setTransactionId(transaction_id); //加入訂單信息 if(time_end!=null){ //成功 payOrderInfo.setTradeStatus(3); } iPayOrderService.updatePayOrderByOrderNo(payOrderInfo); logger.info("支付成功!訂單號爲:"+payOrderInfo.getTradeNo()+"-----"+"第三方訂單號:"+payOrderInfo.getTransactionId()); notifyResponse.setResultXml(setXml("SUCCESS", "OK")); return notifyResponse; }else { // 支付出錯 notifyResponse.setResultXml(setXml("FAIL","支付訂單校驗出錯")); } }else { //驗籤失敗 notifyResponse.setResultXml(setXml("FAIL","支付驗籤失敗")); } }else { notifyResponse.setResultXml(setXml("FAIL","支付不成功")); } } catch (Exception e) { logger.error("支付回調異常: " + e.getMessage()); notifyResponse.setResultXml(setXml("FAIL","支付回調異常")); return notifyResponse; } return null; }
搞定