一、首先分享 微信統一下單接口: javascript
https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_1 php
微信接口 簽名 對比網址:html
https://pay.weixin.qq.com/wiki/tools/signverify/前端
微信小程序 微信支付 網址:java
https://mp.weixin.qq.com/debug/wxadoc/dev/api/api-pay.html#wxrequestpaymentobject算法
二、微信小程序端 代碼示例:sql
payment:function(event){ var that = this; console.log('去支付按鈕點擊事件') wx.request({ url: 'https://192.168.0.666:8080/matouwang/wechat/wechatAppletGolf/createUnifiedOrder', method: 'POST', // OPTIONS, GET, HEAD, POST, PUT, DELETE, TRACE, CONNECT // 當method 爲POST 時 設置如下 的header header: { 'content-type': 'application/x-www-form-urlencoded' }, data: { amount: teamMoney, openid: openId }, success: function (res) { if (res.data.prepayId != ''){ console.log('微信統一下單接口調用成功 數據包:' + res.data.prepayId); console.log('微信統一下單接口調用成功 訂單號:' + res.data.outTradeNo); console.log('調用微信支付接口以前先生成簽名') //保存訂單號信息 var outTradeNo = res.data.outTradeNo; wx.request({ url: 'http://192.168.8.50:8080/matouwang/wechat/wechatAppletGolf/generateSignature', method: 'POST', // OPTIONS, GET, HEAD, POST, PUT, DELETE, TRACE, CONNECT // 當method 爲POST 時 設置如下 的header header: { 'content-type': 'application/x-www-form-urlencoded' }, data: { prepayId: res.data.prepayId }, success: function (paryResult) { if (paryResult.data.sign != '') { console.log('微信支付接口以前先生成簽名成功') console.log('簽名:' + paryResult.data.sign) console.log('隨機串:' + paryResult.data.nonceStr) console.log('時間戳:' + paryResult.data.timeStamp) //這個applyId必定要大寫 並且簽名的參數和調用方法的參數值必定要統一 wx.requestPayment({ 'appId': '', 'timeStamp': paryResult.data.timeStamp, 'nonceStr': paryResult.data.nonceStr, 'package': paryResult.data.package, 'signType': 'MD5', 'paySign': paryResult.data.sign, 'success': function (paymentRes) { console.log(paymentRes) that.setData({ notPay: true, paySuccess: false, teamNotPay: true, button:true, outTradeNo: outTradeNo, payDate:new Date() }) }, 'fail': function (error) { console.log(error) } }) } else { console.log('微信支付接口以前先生成簽名失敗') } } }) } } }); },
三、分享微信 統一下單 後臺代碼示例:小程序
@Override public JSONObject createUnifiedOrder(HttpServletRequest request,HttpServletResponse response) { logger.info("微信 統一下單 接口調用"); //設置最終返回對象 JSONObject resultJson = new JSONObject(); //建立條件 Criteria criteria = new Criteria(); //接受參數(金額) String amount = request.getParameter("amount"); //接受參數(openid) String openid = request.getParameter("openid"); //接口調用總金額單位爲分換算一下(測試金額改爲1,單位爲分則是0.01,根據本身業務場景判斷是轉換成float類型仍是int類型) //String amountFen = Integer.valueOf((Integer.parseInt(amount)*100)).toString(); //String amountFen = Float.valueOf((Float.parseFloat(amount)*100)).toString(); String amountFen = "1"; //建立hashmap(用戶得到簽名) SortedMap<String, String> paraMap = new TreeMap<String, String>(); //設置body變量 (支付成功顯示在微信支付 商品詳情中) String body = "啦啦啦測試"; //設置隨機字符串 String nonceStr = Utils.getUUIDString().replaceAll("-", ""); //設置商戶訂單號 String outTradeNo = Utils.getUUIDString().replaceAll("-", ""); //設置請求參數(小程序ID) paraMap.put("appid", APPLYID); //設置請求參數(商戶號) paraMap.put("mch_id", MCHID); //設置請求參數(隨機字符串) paraMap.put("nonce_str", nonceStr); //設置請求參數(商品描述) paraMap.put("body", body); //設置請求參數(商戶訂單號) paraMap.put("out_trade_no", outTradeNo); //設置請求參數(總金額) paraMap.put("total_fee", amountFen); //設置請求參數(終端IP) paraMap.put("spbill_create_ip", WebUtils.getIpAddress(request, response)); //設置請求參數(通知地址) paraMap.put("notify_url", WebUtils.getBasePath()+"wechat/wechatAppletGolf/payCallback"); //設置請求參數(交易類型) paraMap.put("trade_type", "JSAPI"); //設置請求參數(openid)(在接口文檔中 該參數 是否必填項 可是必定要注意 若是交易類型設置成'JSAPI'則必須傳入openid) paraMap.put("openid", openid); //調用邏輯傳入參數按照字段名的 ASCII 碼從小到大排序(字典序) String stringA = formatUrlMap(paraMap, false, false); //第二步,在stringA最後拼接上key獲得stringSignTemp字符串,並對stringSignTemp進行MD5運算,再將獲得的字符串全部字符轉換爲大寫,獲得sign值signValue。(簽名) String sign = MD5Util.MD5(stringA+"&key="+KEY).toUpperCase(); //將參數 編寫XML格式 StringBuffer paramBuffer = new StringBuffer(); paramBuffer.append("<xml>"); paramBuffer.append("<appid>"+APPLYID+"</appid>"); paramBuffer.append("<mch_id>"+MCHID+"</mch_id>"); paramBuffer.append("<nonce_str>"+paraMap.get("nonce_str")+"</nonce_str>"); paramBuffer.append("<sign>"+sign+"</sign>"); paramBuffer.append("<body>"+body+"</body>"); paramBuffer.append("<out_trade_no>"+paraMap.get("out_trade_no")+"</out_trade_no>"); paramBuffer.append("<total_fee>"+paraMap.get("total_fee")+"</total_fee>"); paramBuffer.append("<spbill_create_ip>"+paraMap.get("spbill_create_ip")+"</spbill_create_ip>"); paramBuffer.append("<notify_url>"+paraMap.get("notify_url")+"</notify_url>"); paramBuffer.append("<trade_type>"+paraMap.get("trade_type")+"</trade_type>"); paramBuffer.append("<openid>"+paraMap.get("openid")+"</openid>"); paramBuffer.append("</xml>"); try { //發送請求(POST)(得到數據包ID)(這有個注意的地方 若是不轉碼成ISO8859-1則會告訴你body不是UTF8編碼 就算你改爲UTF8編碼也同樣很差使 因此修改爲ISO8859-1) Map<String,String> map = doXMLParse(getRemotePortData(URL, new String(paramBuffer.toString().getBytes(), "ISO8859-1"))); //應該建立 支付表數據 if(map!=null){ //清空 criteria.clear(); //設置openId條件 criteria.put("openId", openid); //獲取數據 List<WechatAppletGolfPayInfo> payInfoList = appletGolfPayInfoMapper.selectByExample(criteria); //若是等於空 則證實是第一次支付 if(CollectionUtils.isEmpty(payInfoList)){ //建立支付信息對象 WechatAppletGolfPayInfo appletGolfPayInfo = new WechatAppletGolfPayInfo(); //設置主鍵 appletGolfPayInfo.setPayId(outTradeNo); //設置openid appletGolfPayInfo.setOpenId(openid); //設置金額 appletGolfPayInfo.setAmount(Long.valueOf(amount)); //設置支付狀態 appletGolfPayInfo.setPayStatus("0"); //插入Dao int sqlRow = appletGolfPayInfoMapper.insert(appletGolfPayInfo); //判斷 if(sqlRow == 1){ logger.info("微信 統一下單 接口調用成功 而且新增支付信息成功"); resultJson.put("prepayId", map.get("prepay_id")); resultJson.put("outTradeNo", paraMap.get("out_trade_no")); return resultJson; } }else{ //判斷 是否等於一條 if(payInfoList.size() == 1){ //獲取 須要更新數據 WechatAppletGolfPayInfo wechatAppletGolfPayInfo = payInfoList.get(0); //更新 該條的 金額 wechatAppletGolfPayInfo.setAmount(Long.valueOf(amount)); //更新Dao int sqlRow = appletGolfPayInfoMapper.updateByPrimaryKey(wechatAppletGolfPayInfo); //判斷 if(sqlRow == 1){ logger.info("微信 統一下單 接口調用成功 修改支付信息成功"); resultJson.put("prepayId", map.get("prepay_id")); resultJson.put("outTradeNo", paraMap.get("out_trade_no")); return resultJson; } } } } //將 數據包ID 返回 System.out.println(map); } catch (UnsupportedEncodingException e) { logger.info("微信 統一下單 異常:"+e.getMessage()); e.printStackTrace(); } catch (Exception e) { logger.info("微信 統一下單 異常:"+e.getMessage()); e.printStackTrace(); } logger.info("微信 統一下單 失敗"); return resultJson; }
四、生成 微信支付 簽名後臺 代碼示例:微信小程序
@Override public JSONObject generateSignature(HttpServletRequest request, HttpServletResponse response) { logger.info("微信 支付接口生成簽名 方法開始"); //實例化返回對象 JSONObject resultJson = new JSONObject(); //得到參數(微信統一下單接口生成的prepay_id ) String prepayId = request.getParameter("prepayId"); //建立 時間戳 String timeStamp = Long.valueOf(System.currentTimeMillis()).toString(); //建立 隨機串 String nonceStr = Utils.getUUIDString().replaceAll("-", ""); //建立 MD5 String signType = "MD5"; //建立hashmap(用戶得到簽名) SortedMap<String, String> paraMap = new TreeMap<String, String>(); //設置(小程序ID)(這塊必定要是大寫) paraMap.put("appId", APPLYID); //設置(時間戳) paraMap.put("timeStamp", timeStamp); //設置(隨機串) paraMap.put("nonceStr", nonceStr); //設置(數據包) paraMap.put("package", "prepay_id="+prepayId); //設置(簽名方式) paraMap.put("signType", signType); //調用邏輯傳入參數按照字段名的 ASCII 碼從小到大排序(字典序) String stringA = formatUrlMap(paraMap, false, false); //第二步,在stringA最後拼接上key獲得stringSignTemp字符串,並對stringSignTemp進行MD5運算,再將獲得的字符串全部字符轉換爲大寫,獲得sign值signValue。(簽名) String sign = MD5Util.MD5(stringA+"&key="+KEY).toUpperCase(); if(StringUtils.isNotBlank(sign)){ //返回簽名信息 resultJson.put("sign", sign); //返回隨機串(這個隨機串是新建立的) resultJson.put("nonceStr", nonceStr); //返回時間戳 resultJson.put("timeStamp", timeStamp); //返回數據包 resultJson.put("package", "prepay_id="+prepayId); logger.info("微信 支付接口生成簽名 設置返回值"); } logger.info("微信 支付接口生成簽名 方法結束"); return resultJson; }
五、簽名算法 將key Value 字典排序 代碼示例:api
/** * * 方法用途: 對全部傳入參數按照字段名的 ASCII 碼從小到大排序(字典序),而且生成url參數串<br> * 實現步驟: <br> * * @param paraMap 要排序的Map對象 * @param urlEncode 是否須要URLENCODE * @param keyToLower 是否須要將Key轉換爲全小寫 * true:key轉化成小寫,false:不轉化 * @return */ private static String formatUrlMap(Map<String, String> paraMap, boolean urlEncode, boolean keyToLower){ String buff = ""; Map<String, String> tmpMap = paraMap; try { List<Map.Entry<String, String>> infoIds = new ArrayList<Map.Entry<String, String>>(tmpMap.entrySet()); // 對全部傳入參數按照字段名的 ASCII 碼從小到大排序(字典序) Collections.sort(infoIds, new Comparator<Map.Entry<String, String>>() { @Override public int compare(Map.Entry<String, String> o1, Map.Entry<String, String> o2) { return (o1.getKey()).toString().compareTo(o2.getKey()); } }); // 構造URL 鍵值對的格式 StringBuilder buf = new StringBuilder(); for (Map.Entry<String, String> item : infoIds) { if (StringUtils.isNotBlank(item.getKey())) { String key = item.getKey(); String val = item.getValue(); if (urlEncode) { val = URLEncoder.encode(val, "utf-8"); } if (keyToLower) { buf.append(key.toLowerCase() + "=" + val); } else { buf.append(key + "=" + val); } buf.append("&"); } } buff = buf.toString(); if (buff.isEmpty() == false) { buff = buff.substring(0, buff.length() - 1); } } catch (Exception e) { return null; } return buff; }
六、發送遠程請求 得到數據 代碼示例:
/** * 方法名: getRemotePortData * 描述: 發送遠程請求 得到代碼示例 * 參數: @param urls 訪問路徑 * 參數: @param param 訪問參數-字符串拼接格式, 例:port_d=10002&port_g=10007&country_a= * 版本號: v1.0 * 返回類型: String */ private String getRemotePortData(String urls, String param){ logger.info("港距查詢抓取數據----開始抓取外網港距數據"); try { URL url = new URL(urls); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); // 設置鏈接超時時間 conn.setConnectTimeout(30000); // 設置讀取超時時間 conn.setReadTimeout(30000); conn.setRequestMethod("POST"); if(StringUtil.isNotBlank(param)) { conn.setRequestProperty("Origin", "https://sirius.searates.com");// 主要參數 conn.setRequestProperty("Referer", "https://sirius.searates.com/cn/port?A=ChIJP1j2OhRahjURNsllbOuKc3Y&D=567&G=16959&shipment=1&container=20st&weight=1&product=0&request=&weightcargo=1&"); conn.setRequestProperty("X-Requested-With", "XMLHttpRequest");// 主要參數 } // 須要輸出 conn.setDoInput(true); // 須要輸入 conn.setDoOutput(true); // 設置是否使用緩存 conn.setUseCaches(false); // 設置請求屬性 conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); conn.setRequestProperty("Connection", "Keep-Alive");// 維持長鏈接 conn.setRequestProperty("Charset", "UTF-8"); if(StringUtil.isNotBlank(param)) { // 創建輸入流,向指向的URL傳入參數 DataOutputStream dos=new DataOutputStream(conn.getOutputStream()); dos.writeBytes(param); dos.flush(); dos.close(); } // 輸出返回結果 InputStream input = conn.getInputStream(); int resLen =0; byte[] res = new byte[1024]; StringBuilder sb=new StringBuilder(); while((resLen=input.read(res))!=-1){ sb.append(new String(res, 0, resLen)); } return sb.toString(); } catch (MalformedURLException e) { e.printStackTrace(); logger.info("港距查詢抓取數據----抓取外網港距數據發生異常:" + e.getMessage()); } catch (IOException e) { e.printStackTrace(); logger.info("港距查詢抓取數據----抓取外網港距數據發生異常:" + e.getMessage()); } logger.info("港距查詢抓取數據----抓取外網港距數據失敗, 返回空字符串"); return ""; }
七、XML 轉換成 map 對象 代碼示例:
/** * 解析xml,返回第一級元素鍵值對。若是第一級元素有子節點,則此節點的值是子節點的xml數據。 * @param strxml * @return * @throws JDOMException * @throws IOException */ @SuppressWarnings("rawtypes") private Map<String,String> doXMLParse(String strxml) throws Exception { if(null == strxml || "".equals(strxml)) { return null; } Map<String,String> m = new HashMap<String,String>(); InputStream in = String2Inputstream(strxml); 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 k = e.getName(); String v = ""; List children = e.getChildren(); if(children.isEmpty()) { v = e.getTextNormalize(); } else { v = getChildrenText(children); } m.put(k, v); } //關閉流 in.close(); return m; } private InputStream String2Inputstream(String str) { return new ByteArrayInputStream(str.getBytes()); } /** * 獲取子結點的xml * @param children * @return String */ @SuppressWarnings("rawtypes") private 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(getChildrenText(list)); } sb.append(value); sb.append("</" + name + ">"); } } return sb.toString(); }
八、獲取本機IP
/** * @Title: getIpAddress * @Description: 獲取客戶端真實IP地址 * @author yihj * @param @param request * @param @param response * @param @return 參數 * @return String 返回類型 * @throws */ public static String getIpAddress(HttpServletRequest request) { // 避免反向代理不能獲取真實地址, 取X-Forwarded-For中第一個非unknown的有效IP字符串 String ip = request.getHeader("x-forwarded-for"); if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)){ ip = request.getHeader("Proxy-Client-IP"); } if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)){ ip = request.getHeader("WL-Proxy-Client-IP"); } if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)){ ip = request.getRemoteAddr(); } return ip; }
九、通知地址 回調服務器 支付結果(這個回調 若是不返回給微信服務器 是否成功回調標示 則會一直回調8次 一直到返回成功標示位置) 代碼示例:
@Override public void payCallback(HttpServletRequest request,HttpServletResponse response) { logger.info("微信回調接口方法 start"); logger.info("微信回調接口 操做邏輯 start"); String inputLine = ""; String notityXml = ""; try { while((inputLine = request.getReader().readLine()) != null){ notityXml += inputLine; } //關閉流 request.getReader().close(); logger.info("微信回調內容信息:"+notityXml); //解析成Map Map<String,String> map = doXMLParse(notityXml); //判斷 支付是否成功 if("SUCCESS".equals(map.get("result_code"))){ logger.info("微信回調返回是否支付成功:是"); //得到 返回的商戶訂單號 String outTradeNo = map.get("out_trade_no"); logger.info("微信回調返回商戶訂單號:"+outTradeNo); //訪問DB WechatAppletGolfPayInfo payInfo = appletGolfPayInfoMapper.selectByPrimaryKey(outTradeNo); logger.info("微信回調 根據訂單號查詢訂單狀態:"+payInfo.getPayStatus()); if("0".equals(payInfo.getPayStatus())){ //修改支付狀態 payInfo.setPayStatus("1"); //更新Bean int sqlRow = appletGolfPayInfoMapper.updateByPrimaryKey(payInfo); //判斷 是否更新成功 if(sqlRow == 1){ logger.info("微信回調 訂單號:"+outTradeNo +",修改狀態成功"); //封裝 返回值 StringBuffer buffer = new StringBuffer(); buffer.append("<xml>"); buffer.append("<return_code>SUCCESS</return_code>"); buffer.append("<return_msg>OK</return_msg>"); buffer.append("</xml>"); //給微信服務器返回 成功標示 不然會一直詢問 我們服務器 是否回調成功 PrintWriter writer = response.getWriter(); //返回 writer.print(buffer.toString()); } } } } catch (IOException e) { e.printStackTrace(); } catch (Exception e) { e.printStackTrace(); } }
十、注意事項:
1)、全部的簽名和發送微信服務器的數據必須一致 包括Key的大小寫 不然簽名失敗
2)、微信小程序 前端調用 接口的時候 文檔上並無寫appId參數 該參數必定要穿 而且是大寫
3)、交易類型 爲 JSAPI 的時候 則必須傳入openid
4)、body格式問題 寫的是UTF-8 實際要的格式則是ISO8859-1 並且單獨對body進行設置好像很差使 因此必須所有都改爲該格式
5)、生成簽名 最後加上key的那塊 加的格式是 &key = KEY 這種 並且不是直接 + key 這個地方須要注意一下 我碰了個坑 文檔沒看仔細
6)、數據包ID 格式 不是 value直接設置成 數據包ID就能夠 前面須要加 "prepay_id="
7)、最後一點強調 生成簽名的數據和發送服務器的數據 必須保持一致