近期公司调完银联,调支付宝,调完支付宝调微信.说实话微信的帮助文档确实是烂,而且有没有技术支持,害的我头发都掉了一桌.不说废话了,看代码.
首先登陆微信的公众平台(微信的服务号不是订阅号),然后选择微信支付-->开发设置,设置好支付回调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好的帮助下完成的,
如果有好的博客可以推荐给我,大家共同学习,谢谢!!