近期公司調完銀聯,調支付寶,調完支付寶調微信.說實話微信的幫助文檔確實是爛,而且有沒有技術支持,害的我頭發都掉了一桌.不說廢話了,看代碼.
首先登陸微信的公眾平台(微信的服務號不是訂閱號),然后選擇微信支付-->開發設置,設置好支付回調URL和支付授權目錄(授權目錄最少精確到二級目錄,比如你的需要使用微信支付的目錄是:www.weixinpay.com/sp/weixin/pay.do,那么對應的是:www.weixinpay.com/sp/weixin/),設置好后編寫代碼.
對了,聯調支付,一般都需要外網能夠訪問的URL地址,這里建議使用過ngrok軟件,直接在本地聯調,使用方式,下載一個ngrok,然后由命令行窗口進入ngrok解壓的目錄,然后執行:
ngrok.exe -config ngrok.cfg -subdomain weixinpay(可以改成自己喜歡的域名) 8080(可以改成自己喜歡的端口)
首先生成微信二維碼(1):
1 /** 2 * 生成微信二維碼圖片 3 * @throws Exception 4 */ 5 public void gainQRCode() throws Exception { 6 try { 7 String orderNu = request.getParameter("orderNu"); 8 String describe = request.getParameter("describe"); 9 String payCodeType = request.getParameter("payCodeType"); 10 System.out.println("訂單編號:"+"\n"+orderNu); 11 String price = request.getParameter("txnAmt"); 12 if(StringUtils.isBlank(orderNu)){//賬戶充值,不用簽名校驗 13 orderNu = "WE"+StringUtil.getTableId(false); 14 } else { 15 String sign = request.getParameter("sign"); 16 String signParam = "orderNu="+orderNu+"&payPrice="+price; 17 String newSign = DigestUtils.md5Hex(signParam.getBytes("utf-8")); 18 if(!newSign.equalsIgnoreCase(sign)){ 19 Map param = new HashMap<>(); 20 param.put("statu", "2"); 21 JSONArray jsonProduct = JSONArray.fromObject(param); 22 System.out.println("json: "+jsonProduct.toString()); 23 response.getWriter().print(jsonProduct.toString()); 24 return ; 25 } 26 } 27 System.out.println("沒有轉換的金額:"+"\n"+price); 28 BigDecimal bigDecimalPrice = new BigDecimal(price); 29 String pric = bigDecimalPrice.multiply(new BigDecimal(100)).toString().split("\\.")[0]; 30 System.out.println("轉換后的金額:"+"\n"+pric); 31 String filePostfix = "jpg"; 32 // 二維碼圖片名稱 33 String codePng = System.currentTimeMillis() + "." + filePostfix; 34 // 保存路徑 35 // 應用ID 36 String appid = Config.APPID; 37 // 商戶ID 38 String mch_id = Config.MCHID; 39 String key = Config.KEY; 40 // 生成32位的隨機字符串 41 String nonce_str = RandomStringUtil.generate().toUpperCase(); 42 // 商戶產品ID 43 User user = SessionUtil.getSysUserFormSession(request); 44 String product_id = ""; 45 if(StringUtils.isNotBlank(payCodeType) && "1".equals(payCodeType)){//賬戶充值 46 product_id = "1_"+orderNu+"_"+"賬戶保證金充值"+"_"+pric+"_"+user.getUniversalid(); 47 } else if (StringUtils.isNotBlank(payCodeType) && "2".equals(payCodeType)){//訂單結算 48 product_id = "2_"+orderNu+"_"+describe+"_"+pric+"_"+user.getUniversalid(); 49 } 50 //上面的可以不關注.關注下面的簽名和發送的參數,54行開始 51 // 時間戳 52 String time_stamp = System.currentTimeMillis()+""; 53 String sign = "";//這些參數可以去微信掃碼的支付的文檔看看具體是什么意思 54 String temp = "appid=" + appid + "&mch_id=" + mch_id + "&nonce_str=" + nonce_str + "&" + "product_id=" 55 + product_id + "&time_stamp=" + time_stamp; 56 String signTemp = temp + "&key=" + key; 57 System.out.println("二維碼請求參數:"+"\n"+temp); 58 sign = Encrypt.e(signTemp).toUpperCase(); 59 String contentUrl = "weixin://wxpay/bizpayurl?" + temp + "&sign=" + sign; 60 String url = QRCODE_PATH + File.separator + File.separator + codePng; 61 File f = new File(url); 62 if(!f.exists()){ 63 f.mkdirs(); 64 } 65 System.out.println("已生成二維碼url"); 66 QRImgCode.encode(contentUrl, "二維碼", 400, 400, url); 67 System.out.println("二維碼已經生成成功"); 68 Map param = new HashMap<>(); 69 param.put("statu", "1"); 70 param.put("imgName", codePng); 71 System.out.println("返回參數封裝成功"); 72 JSONArray jsonProduct = JSONArray.fromObject(param); 73 System.out.println("json: "+jsonProduct.toString()); 74 response.getWriter().print(jsonProduct.toString()); 75 } catch (Exception e) { 76 throw e; 77 } 78 }
生成二維碼(2):
1 /** 2 * 無中間圖片 3 * @param content 4 * @param width 5 * @param height 6 * @param destImagePath 7 */ 8 public static void encode(String content,String name,int width, int height, String destImagePath) { 9 try { 10 System.err.println("進入二維碼方法"); 11 BitMatrix bitMatrix = genBarcode(content, 190, 184); 12 System.out.println("生成二維碼數據"); 13 MatrixToImageWriter.writeToFile(bitMatrix, "jpg", new File(destImagePath)); 14 System.out.println("二維碼圖片生成完成"); 15 } catch (IOException e) { 16 e.printStackTrace(); 17 } catch (WriterException e) { 18 e.printStackTrace(); 19 } 20 }
PC端掃碼后微信會去調用調用支付回調URL,接下來重要的步驟來了:(險些讓我禿頂)
1 /** 2 * 微信掃碼之后的回調 3 * @return 4 */ 5 public void weixinPay() { 6 System.out.println("微信支付掃碼回調,手動發起統一下單支付"); 7 try { 8 InputStream inStream = this.request.getInputStream(); 9 ByteArrayOutputStream outSteam = new ByteArrayOutputStream(); 10 byte[] buffer = new byte[1024]; 11 int len = 0; 12 while ((len = inStream.read(buffer)) != -1) { 13 outSteam.write(buffer, 0, len); 14 } 15 outSteam.close(); 16 inStream.close(); 17 String re = new String(outSteam.toByteArray(), "utf-8"); 18 System.out.println("用戶掃碼后返回的參數: \n" + re); 19 //轉xml 微信傳輸的是xml 20 Map map = parseXML(re); 21 //掃碼后簽名驗證 22 String sign = gainValidateSign(map,false).toUpperCase(); 23 if (!sign.equalsIgnoreCase((String)map.get("sign"))) { 24 System.out.println("掃碼后生成的簽名不正確"); 25 return; 26 }//這些是掃碼后的驗證 27 System.out.println("掃碼后生成的簽名正確, 繼續進行后續操作"); 28 29 String url = "https://api.mch.weixin.qq.com/pay/unifiedorder"; 30 31 String string = (String)map.get("product_id"); 32 System.out.println("掃碼的product_id參數:\n" + string); 33 String[] para = string.split("_"); 34 //這里個方法是生成與支付訂單的請求參數,看下面的方法,需要傳輸的參數可以去查看微信的幫助文檔,雖然爛,但是這些還是有的. 35 StringEntity param = genProductArgs(para[0] + "_" + para[1], para[2], para[3]); 36 String result = sendPost(url, param); 37 System.out.println("發送請求得到結果===" + result); 38 39 Map resultMap = parseXML(result); 40 String signResult = gainSign(resultMap); 41 if (!signResult.equalsIgnoreCase((String)resultMap.get("sign"))) { 42 System.out.println("統一下單接口-->簽名不正確"); 43 return; 44 } 45 if (StringUtils.isNotBlank(para[0]) && "1".equals(para[0])) { 46 RechargePrice r = rechargePriceServiceService.findByorderIdRecharge(para[1]); 47 if(r == null || r.getUniversalid() == null){ 48 RechargePrice rp = new RechargePrice(); 49 rp.setUserId(para[4]); 50 rp.setCreateDate(DateFormatUtil.strToDate(UtilDate.getDateFormatter())); 51 rp.setExplain("賬戶充值"); 52 rp.setFlowAccountNum(StringUtil.getTableId(true)); 53 BigDecimal bigDecimalPrice = new BigDecimal(para[3]); 54 55 double doubleValue = bigDecimalPrice.divide(new BigDecimal("100")).doubleValue(); 56 rp.setPrice(doubleValue+""); 57 rp.setOrderCode(para[1]); 58 rp.setTransactionStat("2"); 59 rp.setTransactionType("1"); 60 rp.setPaySource("5"); 61 this.rechargePriceServiceService.saveRechargePrice(rp); 62 } 63 } 64 ServletOutputStream outputStream = this.response.getOutputStream(); 65 66 String return_code = (String)resultMap.get("return_code"); 67 String result_code = (String)resultMap.get("result_code"); 68 if (StringUtils.isNotBlank(return_code) && StringUtils.isNotBlank(result_code) && return_code.equalsIgnoreCase("SUCCESS") && result_code.equalsIgnoreCase("SUCCESS")) { 69 String xml = genPay(resultMap);//對於中文亂碼主要是上面的那個預支付訂單請求,只要上面請求OK,這個請求就可以不用轉碼 70 System.out.println("統一下單接口后,向微信發送支付其你去的xml"+"\n" + xml); 71 outputStream.write(xml.getBytes()); 72 outputStream.flush(); 73 outputStream.close(); 74 } 75 } catch (Exception e) { 76 e.printStackTrace(); 77 } 78 }
字符串轉xml:
1 /** 2 * 解析xml方法 3 */ 4 @SuppressWarnings("rawtypes") 5 public Map<String, String> parseXML(String xml) { 6 Document doc; 7 Map<String, String> map = new LinkedHashMap<String, String>(); 8 try { 9 doc = DocumentHelper.parseText(xml); 10 Element rootElement = doc.getRootElement(); 11 Iterator elementIterator = rootElement.elementIterator(); 12 while (elementIterator.hasNext()) { 13 Element recordEle = (Element) elementIterator.next(); 14 String name = recordEle.getName(); 15 String textTrim = recordEle.getTextTrim(); 16 map.put(name, textTrim); 17 } 18 } catch (DocumentException e) { 19 e.printStackTrace(); 20 } 21 return map; 22 }
處理xml集合,返回簽名:
1 /** 2 * 處理xml字符串 返回簽名 3 */ 4 public String gainValidateSign(Map<String, String> map, boolean isUtf8) { 5 StringBuffer sb = new StringBuffer(); 6 Set<String> keySet = map.keySet(); 7 List<String> list = new LinkedList<String>(); 8 list.addAll(keySet); 9 Collections.sort(list); 10 for (String key : list) { 11 if (!key.equals("sign")) { 12 sb.append(key).append("=").append(map.get(key)).append("&"); 13 } 14 } 15 sb.append("key=").append(Config.KEY); 16 String sign = ""; 17 if(isUtf8){ 18 try { 19 sign = Encrypt.e(new String(sb.toString().getBytes(), "utf-8")); 20 } catch (UnsupportedEncodingException e) { 21 e.printStackTrace(); 22 } 23 } else { 24 sign = Encrypt.e(sb.toString()); 25 } 26 return sign; 27 }
封裝請求參數返回xml字符串:
1 /** 2 * 把一個參數添加到 一個集合中,按字典順序,這是為了后面生成 簽名方便 3 * @param attach 4 * @param map 5 * 6 * @return 7 * @throws Exception 8 */ 9 private StringEntity genProductArgs(String attach,String body,String price) throws Exception { 10 List<NameValuePair> packageParams = new LinkedList<NameValuePair>(); 11 packageParams.add(new BasicNameValuePair("appid", Config.APPID)); 12 packageParams.add(new BasicNameValuePair("attach", attach)); 13 packageParams.add(new BasicNameValuePair("body", body)); 14 packageParams.add(new BasicNameValuePair("mch_id", Config.MCHID)); 15 packageParams.add(new BasicNameValuePair("nonce_str", RandomStringUtil.generate().toUpperCase())); 16 packageParams.add(new BasicNameValuePair("notify_url", "")); 17 packageParams.add(new BasicNameValuePair("out_trade_no", com.eryansky.common.utils.StringUtils.getRandomNumbersAndLetters(15).toUpperCase())); 18 packageParams.add(new BasicNameValuePair("spbill_create_ip", request.getRemoteAddr())); 19 packageParams.add(new BasicNameValuePair("total_fee", Integer.parseInt(price)+"")); 20 packageParams.add(new BasicNameValuePair("trade_type", Config.trade_type)); 21 // 調用genXml()方法獲得xml格式的請求數據 22 // String genXml = null; 23 try { 24 StringEntity stringEntityXml = getStringEntityXml(packageParams); 25 return stringEntityXml; 26 } catch (Exception e) { 27 e.printStackTrace(); 28 } 29 return null; 30 }
返回發送的xml數據:(解決中文亂碼問題)
1 /** 2 * 生成xml文檔發送微信生成支付信息 3 * 4 * @param params 5 * @return 6 * @throws Exception 7 */ 8 private StringEntity getStringEntityXml(List<NameValuePair> params) throws Exception { 9 StringBuilder sb = new StringBuilder(); 10 StringBuilder sb2 = new StringBuilder(); 11 sb2.append("<?xml version='1.0' encoding='UTF-8' standalone='yes' ?><xml>"); 12 for (int i = 0; i < params.size(); i++) { 13 // sb是用來計算簽名的 14 sb.append(params.get(i).getName()); 15 sb.append('='); 16 sb.append(params.get(i).getValue()); 17 sb.append('&'); 18 // sb2是用來做請求的xml參數 19 sb2.append("<" + params.get(i).getName() + ">"); 20 sb2.append(params.get(i).getValue()); 21 sb2.append("</" + params.get(i).getName() + ">"); 22 } 23 sb.append("key="); 24 sb.append(Config.KEY); 25 System.err.println("生成簽名的參數:"+"\n"+sb.toString()); 26 String packageSign = null; 27 // 生成簽名-->簽名和xml字符串都需要轉成utf-8格式,中文就不會出現亂碼 28 packageSign = DigestUtils.md5Hex(sb.toString().getBytes("utf-8")).toUpperCase(); 29 // packageSign = DigestUtils.md5Hex(sb.toString()).toUpperCase(); 30 System.out.println("生成發送統一接口的簽名:"+"\n"+packageSign); 31 sb2.append("<sign><![CDATA["); 32 sb2.append(packageSign); 33 sb2.append("]]></sign>"); 34 sb2.append("</xml>"); 35 System.err.println("生成發送統一接口的xml"+"\n"+sb2.toString()); 36 try { 37 StringEntity se = new StringEntity(sb2.toString(), "utf-8"); 38 return se; 39 } catch (Exception e) { 40 e.printStackTrace(); 41 } 42 return null; 43 }
發送請求:
1 /** 2 * 發送請求 3 * @throws UnsupportedEncodingException 4 */ 5 public String sendPost(String url, StringEntity param) throws UnsupportedEncodingException { 6 String reslt = ""; 7 try { 8 CloseableHttpClient httpClient = HttpClients.createDefault(); 9 RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(10000).setConnectTimeout(30000).build(); 10 HttpPost httpPost = new HttpPost(url); 11 httpPost.addHeader("Content-Type", "text/xml"); 12 httpPost.setEntity(param); 13 httpPost.setConfig(requestConfig); 14 HttpResponse response = httpClient.execute(httpPost); 15 HttpEntity entity = response.getEntity(); 16 reslt = EntityUtils.toString(entity, "UTF-8"); 17 } catch (ParseException | IOException e1) { 18 e1.printStackTrace(); 19 } 20 21 //下面注釋掉的直接忽略掉,貼上來主要想說,以前用這種方法中文亂碼,而且用utf-8轉換一下后直接報簽名錯誤,把簽名信息貼到微信的簽名驗證上面確實可以通過,真的是無語 22 // System.out.println("進入發送統一下單支付方法"); 23 // PrintWriter out = null; 24 // BufferedReader in = null; 25 // 26 // String result = ""; 27 // try { 28 // URL realUrl = new URL(url); 29 // 30 // URLConnection conn = realUrl.openConnection(); 31 // 32 // conn.setRequestProperty("content-type", "application/x-www-form-urlencoded"); 33 // 34 // conn.setDoOutput(true); 35 // conn.setDoInput(true); 36 // 37 // out = new PrintWriter(conn.getOutputStream()); 38 // System.out.println("請求參數========" + param); 39 //// param = new String(param.getBytes(), "utf-8"); 40 // 41 // out.write(param); 42 // 43 // out.flush(); 44 // 45 // in = new BufferedReader(new InputStreamReader(conn.getInputStream())); 46 // String line; 47 // while ((line = in.readLine()) != null) 48 // { 49 // result = result + line; 50 // } 51 // } catch (Exception e) { 52 // e.printStackTrace(); 53 // try 54 // { 55 // if (out != null) { 56 // out.close(); 57 // } 58 // if (in != null) 59 // in.close(); 60 // } 61 // catch (IOException ex) { 62 // ex.printStackTrace(); 63 // } 64 // } 65 // finally 66 // { 67 // try 68 // { 69 // if (out != null) { 70 // out.close(); 71 // } 72 // if (in != null) 73 // in.close(); 74 // } 75 // catch (IOException ex) { 76 // ex.printStackTrace(); 77 // } 78 // } 79 // return new String(result.getBytes(), "utf-8"); 80 return reslt; 81 }
拼接微信支付信息參數:
1 /** 2 * 發送支付信息參數 3 * @param param 4 * @return 5 */ 6 public String genPay(Map<String, String> param) { 7 System.out.println("向微信發送支付請求的 參數-->"+"\n"+param.toString()); 8 List<NameValuePair> packageParams = new LinkedList<NameValuePair>(); 9 packageParams.add(new BasicNameValuePair("appid", param.get("appid"))); 10 packageParams.add(new BasicNameValuePair("mch_id", param.get("mch_id"))); 11 packageParams.add(new BasicNameValuePair("nonce_str", RandomStringUtil.generate().toUpperCase())); 12 packageParams.add(new BasicNameValuePair("prepay_id", param.get("prepay_id"))); 13 packageParams.add(new BasicNameValuePair("result_code", param.get("result_code"))); 14 packageParams.add(new BasicNameValuePair("return_code", param.get("return_code"))); 15 if ("FAIL".equalsIgnoreCase(param.get("result_code"))) { 16 packageParams.add(new BasicNameValuePair("return_code", param.get("result_code"))); 17 if(StringUtils.isBlank(param.get("err_code_des"))){ 18 packageParams.add(new BasicNameValuePair("err_code_des", "訂單時效")); 19 } 20 packageParams.add(new BasicNameValuePair("err_code_des", param.get("err_code_des"))); 21 } 22 String genXml = null; 23 try { 24 System.out.println("向微信發送支付請求的xml"+"\n"+packageParams.toString()); 25 genXml = genXml(packageParams); 26 } catch (Exception e) { 27 e.printStackTrace(); 28 } 29 return genXml; 30 }
生成微信支付xml參數:
1 /** 2 * 生成xml文檔發送個給微信支付 3 * 4 * @param params 5 * @return 6 * @throws Exception 7 */ 8 private String genXml(List<NameValuePair> params) throws Exception { 9 StringBuilder sb = new StringBuilder(); 10 StringBuilder sb2 = new StringBuilder(); 11 sb2.append("<?xml version='1.0' encoding='UTF-8' standalone='yes' ?><xml>"); 12 for (int i = 0; i < params.size(); i++) { 13 // sb是用來計算簽名的 14 sb.append(params.get(i).getName()); 15 sb.append('='); 16 sb.append(params.get(i).getValue()); 17 sb.append('&'); 18 // sb2是用來做請求的xml參數 19 sb2.append("<" + params.get(i).getName() + ">"); 20 sb2.append(params.get(i).getValue()); 21 sb2.append("</" + params.get(i).getName() + ">"); 22 } 23 sb.append("key="); 24 sb.append(Config.KEY); 25 System.err.println("生成簽名的參數:"+"\n"+sb.toString()); 26 String packageSign = null; 27 // 生成簽名 28 packageSign = DigestUtils.md5Hex(sb.toString()).toUpperCase(); 29 System.out.println("生成發送統一接口的簽名:"+"\n"+packageSign); 30 sb2.append("<sign><![CDATA["); 31 sb2.append(packageSign); 32 sb2.append("]]></sign>"); 33 sb2.append("</xml>"); 34 System.err.println("生成發送統一接口的xml"+"\n"+sb2.toString()); 35 try { 36 return sb2.toString(); 37 } catch (Exception e) { 38 e.printStackTrace(); 39 } 40 return ""; 41 }
支付成功后:
1 /** 2 * 微信掃碼支付成功后的回調的接口 3 * @throws IOException 4 */ 5 public void weiXinnotify() throws IOException{ 6 InputStream inStream = this.request.getInputStream(); 7 ByteArrayOutputStream outSteam = new ByteArrayOutputStream(); 8 byte[] buffer = new byte[1024]; 9 int len = 0; 10 while ((len = inStream.read(buffer)) != -1) { 11 outSteam.write(buffer, 0, len); 12 } 13 outSteam.close(); 14 inStream.close(); 15 String re = new String(outSteam.toByteArray(), "utf-8"); 16 System.out.println("用戶掃碼后支付返回的參數: \n" + re); 17 Map parseXML = parseXML(re); 18 System.out.println("轉成xml后的map:\n" + parseXML); 19 String string = (String)parseXML.get("attach"); 20 //處理完邏輯后記得向微信發送消息,不然微信會隔一段時間會訪問 21 PrintWriter writer = response.getWriter(); 22 writer.print(re); 23 writer.close(); 24 }
最后,對於微信的幫助文檔,是我目前見過最爛的了,前面聯調支付寶和銀聯都沒這樣.哎不吐槽了,
本博客是根據:
http://www.cnblogs.com/zyw-205520/p/5495115.html這篇博客,寫的很好,他自己寫了一個開源項目,
以及和IT好的幫助下完成的,
如果有好的博客可以推薦給我,大家共同學習,謝謝!!