微信扫码支付

  • 2019 年 12 月 16 日
  • 筆記

一,需要申请公司的微信公众号,以及商户号。然后在商户号中关联微信公众APPID。在商户平台添加扫码支付功能。

二.根据微信支付扫码开发文档进行开发

https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=6_1

在application.yml封装属性

##微信公众号的appid  app.wx-pay-appId=xxxxxxxx  ###微信公众号的appSecret  app.wx-pay-appSecret=xxxxxxxxx  ##微信商户号  app.wx-pay-mchId=xxxx  ##微信商户号apikey  app.wx-pay-apiKey=xxxxxx  ###统一下单接口(微信文档中有)  app.wx-pay-ufdoderUrl=https://api.mch.weixin.qq.com/pay/unifiedorder  ###通知验证签名的接口路径  app.wx-pay-notifyUrl=http://xxxx/api/wx/get  app.wx-pay-refundUrl=https://api.mch.weixin.qq.com/secapi/pay/refund

工具类 HttpUtil

/**   * http工具类,负责发起post请求并获取的返回   */  public class HttpUtil {        private final static int CONNECT_TIMEOUT = 5000; // in milliseconds      private final static String DEFAULT_ENCODING = "UTF-8";        public static String postData(String urlStr, String data){          return postData(urlStr, data, null);      }        public static String postData(String urlStr, String data, String contentType){          BufferedReader reader = null;          try {              URL url = new URL(urlStr);              URLConnection conn = url.openConnection();              conn.setDoOutput(true);              conn.setConnectTimeout(CONNECT_TIMEOUT);              conn.setReadTimeout(CONNECT_TIMEOUT);              if(contentType != null)                  conn.setRequestProperty("content-type", contentType);              OutputStreamWriter writer = new OutputStreamWriter(conn.getOutputStream(), DEFAULT_ENCODING);              if(data == null)                  data = "";              writer.write(data);              writer.flush();              writer.close();                reader = new BufferedReader(new InputStreamReader(conn.getInputStream(), DEFAULT_ENCODING));              StringBuilder sb = new StringBuilder();              String line = null;              while ((line = reader.readLine()) != null) {                  sb.append(line);                  sb.append("rn");              }              return sb.toString();          } catch (IOException e) {              //logger.error("Error connecting to " + urlStr + ": " + e.getMessage());          }finally {              try {                  if (reader != null)                      reader.close();              } catch (IOException e) {              }          }          return null;      }        //发送get请求方法      public static String sendGet(String url, String param) {          String result = "";          BufferedReader in = null;          try {              String urlNameString = url + "?" + param;              System.out.println(urlNameString);              URL realUrl = new URL(urlNameString);              // 打开和URL之间的连接              URLConnection connection = realUrl.openConnection();              // 设置通用的请求属性              connection.setRequestProperty("accept", "*/*");              connection.setRequestProperty("connection", "Keep-Alive");              connection.setRequestProperty("user-agent",                      "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");              // 建立实际的连接              connection.connect();              // 获取所有响应头字段              Map<String, List<String>> map = connection.getHeaderFields();              // 遍历所有的响应头字段              for (String key : map.keySet()) {                  System.out.println(key + "--->" + map.get(key));              }              // 定义 BufferedReader输入流来读取URL的响应              in = new BufferedReader(new InputStreamReader(                      connection.getInputStream()));              String line;              while ((line = in.readLine()) != null) {                  result += line;              }          } catch (Exception e) {              System.out.println("发送GET请求出现异常!" + e);              e.printStackTrace();          }          // 使用finally块来关闭输入流          finally {              try {                  if (in != null) {                      in.close();                  }              } catch (Exception e2) {                  e2.printStackTrace();              }          }          return result;      }          /**       * 获取用户实际ip       * @param request       * @return       */      public static String getIpAddr(HttpServletRequest request){          String ipAddress = request.getHeader("X-Real-IP");          if(ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {              ipAddress = request.getHeader("x-forwarded-for");          }  //        String ipAddress = request.getHeader("x-forwarded-for");          if(ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {              ipAddress = request.getHeader("Proxy-Client-IP");          }          if(ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {              ipAddress = request.getHeader("WL-Proxy-Client-IP");          }          if(ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {              ipAddress = request.getRemoteAddr();              if(ipAddress.equals("127.0.0.1") || ipAddress.equals("0:0:0:0:0:0:0:1")){                  //根据网卡取本机配置的IP                  InetAddress inet=null;                  try {                      inet = InetAddress.getLocalHost();                  } catch (UnknownHostException e) {                      e.printStackTrace();                  }                  ipAddress= inet.getHostAddress();              }          }          //对于通过多个代理的情况,第一个IP为客户端真实IP,多个IP按照','分割          if(ipAddress!=null && ipAddress.length()>15){ //"***.***.***.***".length() = 15              if(ipAddress.indexOf(",")>0){                  ipAddress = ipAddress.substring(0,ipAddress.indexOf(","));              }          }          return ipAddress;      }        public static String getIpAddress(HttpServletRequest request) throws IOException {          // 获取请求主机IP地址,如果通过代理进来,则透过防火墙获取真实IP地址            String ip = request.getHeader("X-Forwarded-For");            if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {              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.getHeader("HTTP_CLIENT_IP");              }              if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {                  ip = request.getHeader("HTTP_X_FORWARDED_FOR");              }              if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {                  ip = request.getRemoteAddr();              }          } else if (ip.length() > 15) {              String[] ips = ip.split(",");              for (int index = 0; index < ips.length; index++) {                  String strIp = (String) ips[index];                  if (!("unknown".equalsIgnoreCase(strIp))) {                      ip = strIp;                      break;                  }              }          }          return ip;      }        /**       * 复杂条件下取得ip地址       */      public InetAddress getLocalHostLANAddress() throws Exception {          try {              InetAddress candidateAddress = null;              // 遍历所有的网络接口              for (Enumeration ifaces = NetworkInterface.getNetworkInterfaces(); ifaces.hasMoreElements(); ) {                  NetworkInterface iface = (NetworkInterface) ifaces.nextElement();                  // 在所有的接口下再遍历IP                  for (Enumeration inetAddrs = iface.getInetAddresses(); inetAddrs.hasMoreElements(); ) {                      InetAddress inetAddr = (InetAddress) inetAddrs.nextElement();                      if (!inetAddr.isLoopbackAddress()) {// 排除loopback类型地址                          if (inetAddr.isSiteLocalAddress()) {                              // 如果是site-local地址,就是它了                              return inetAddr;                          } else if (candidateAddress == null) {                              // site-local类型的地址未被发现,先记录候选地址                              candidateAddress = inetAddr;                          }                      }                  }              }              if (candidateAddress != null) {                  return candidateAddress;              }              // 如果没有发现 non-loopback地址.只能用最次选的方案              InetAddress jdkSuppliedAddress = InetAddress.getLocalHost();              return jdkSuppliedAddress;          } catch (Exception e) {              e.printStackTrace();          }          return null;      }    }

创建签名工具类 PayToolUtil

public class PayToolUtil {      /**       * 是否签名正确,规则是:按参数名称a-z排序,遇到空值的参数不参加签名。       * @return boolean       */      public static boolean isTenpaySign(String characterEncoding, SortedMap<Object, Object> packageParams, String API_KEY) {          StringBuffer sb = new StringBuffer();          Set es = packageParams.entrySet();          Iterator it = es.iterator();          while(it.hasNext()) {              Map.Entry entry = (Map.Entry)it.next();              String k = (String)entry.getKey();              String v = (String)entry.getValue();              if(!"sign".equals(k) && null != v && !"".equals(v)) {                  sb.append(k + "=" + v + "&");              }          }            sb.append("key=" + API_KEY);            //算出摘要          String mysign = MD5Util.MD5Encode(sb.toString(), characterEncoding).toLowerCase();          String tenpaySign = ((String)packageParams.get("sign")).toLowerCase();            //System.out.println(tenpaySign + "    " + mysign);          return tenpaySign.equals(mysign);      }        /**       * @author       * @date 2016-4-22       * @Description:sign签名       * @param characterEncoding       *            编码格式       * @return       */      public static String createSign(String characterEncoding, SortedMap<Object, Object> packageParams, String API_KEY) {          StringBuffer sb = new StringBuffer();          Set es = packageParams.entrySet();          Iterator it = es.iterator();          while (it.hasNext()) {              Map.Entry entry = (Map.Entry) it.next();              String k = (String) entry.getKey();              String v = (String) entry.getValue();              if (null != v && !"".equals(v) && !"sign".equals(k) && !"key".equals(k)) {                  sb.append(k + "=" + v + "&");              }          }          sb.append("key=" + API_KEY);          String sign = MD5Util.MD5Encode(sb.toString(), characterEncoding).toUpperCase();          return sign;      }        /**       * @author       * @date 2016-4-22       * @Description:将请求参数转换为xml格式的string       * @param parameters       *            请求参数       * @return       */      public static String getRequestXml(SortedMap<Object, Object> parameters) {          StringBuffer sb = new StringBuffer();          sb.append("<xml>");          Set es = parameters.entrySet();          Iterator it = es.iterator();          while (it.hasNext()) {              Map.Entry entry = (Map.Entry) it.next();              String k = (String) entry.getKey();              String v = (String) entry.getValue();              if ("attach".equalsIgnoreCase(k) || "body".equalsIgnoreCase(k) || "sign".equalsIgnoreCase(k)) {                  sb.append("<" + k + ">" + "<![CDATA[" + v + "]]></" + k + ">");              } else {                  sb.append("<" + k + ">" + v + "</" + k + ">");              }          }          sb.append("</xml>");          return sb.toString();      }        /**       * 取出一个指定长度大小的随机正整数.       *       * @param length       *            int 设定所取出随机数的长度。length小于11       * @return int 返回生成的随机数。       */      public static int buildRandom(int length) {          int num = 1;          double random = Math.random();          if (random < 0.1) {              random = random + 0.1;          }          for (int i = 0; i < length; i++) {              num = num * 10;          }          return (int) ((random * num));      }        /**       * 获取当前时间 yyyyMMddHHmmss       *       * @return String       */      public static String getCurrTime() {          Date now = new Date();          SimpleDateFormat outFormat = new SimpleDateFormat("yyyyMMddHHmmss");          String s = outFormat.format(now);          return s;      }        /**       * 时间戳       */        public static long getTimeStamp() {          Date d = new Date();          long timeStamp = d.getTime() / 1000;     //getTime()得到的是微秒, 需要换算成秒          return timeStamp;      }          public static Map<String, String> xmlToMap(String strXML) throws Exception {          try {              Map<String, String> data = new HashMap<String, String>();              DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();              DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();              InputStream stream = new ByteArrayInputStream(strXML.getBytes("UTF-8"));              org.w3c.dom.Document doc = documentBuilder.parse(stream);              doc.getDocumentElement().normalize();              NodeList nodeList = doc.getDocumentElement().getChildNodes();              for (int idx = 0; idx < nodeList.getLength(); ++idx) {                  Node node = nodeList.item(idx);                  if (node.getNodeType() == Node.ELEMENT_NODE) {                      org.w3c.dom.Element element = (org.w3c.dom.Element) node;                      data.put(element.getNodeName(), element.getTextContent());                  }              }              try {                  stream.close();              } catch (Exception ex) {                  // do nothing              }              return data;          } catch (Exception ex) {              throw ex;          }        }    }

XMLUtil4jdom类

public class XMLUtil4jdom {      /**       * 解析xml,返回第一级元素键值对。如果第一级元素有子节点,则此节点的值是子节点的xml数据。       * @param strxml       * @return       */      public static Map doXMLParse(String strxml) throws JDOMException, IOException {          strxml = strxml.replaceFirst("encoding=".*"", "encoding="UTF-8"");            if(null == strxml || "".equals(strxml)) {              return null;          }            Map<String, String> m = new HashMap<String, String>();          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 k = e.getName();              String v = "";              List children = e.getChildren();              if(children.isEmpty()) {                  v = e.getTextNormalize();              } else {                  v = XMLUtil4jdom.getChildrenText(children);              }                m.put(k, v);          }            //关闭流          in.close();            return m;      }        /**       * 获取子结点的xml       * @param children       * @return String       */      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(XMLUtil4jdom.getChildrenText(list));                  }                  sb.append(value);                  sb.append("</" + name + ">");              }          }            return sb.toString();      }  }

二维码工具类

public class QRUtil {        public static String createQrCode(String url, String path, String fileName) {          try {              Map<EncodeHintType, String> hints = new HashMap<>();              hints.put(EncodeHintType.CHARACTER_SET, "UTF-8");              BitMatrix bitMatrix = new MultiFormatWriter().encode(url, BarcodeFormat.QR_CODE, 400, 400, hints);              File file = new File(path, fileName);              if (file.exists() || ((file.getParentFile().exists() || file.getParentFile().mkdirs()) && file.createNewFile())) {                  writeToFile(bitMatrix, "jpg", file);                  System.out.println("搞定:" + file);                  return file.toString();              }            } catch (Exception e) {              e.printStackTrace();          }          return null;      }        public static void writeToFile(BitMatrix matrix, String format, File file) throws IOException {          BufferedImage image = toBufferedImage(matrix);          if (!ImageIO.write(image, format, file)) {              throw new IOException("Could not write an image of format " + format + " to " + file);          }      }        public static void writeToStream(BitMatrix matrix, String format, OutputStream stream) throws IOException {          BufferedImage image = toBufferedImage(matrix);          if (!ImageIO.write(image, format, stream)) {              throw new IOException("Could not write an image of format " + format);          }      }        private static final int BLACK = 0xFF000000;      private static final int WHITE = 0xFFFFFFFF;        private static BufferedImage toBufferedImage(BitMatrix matrix) {          int width = matrix.getWidth();          int height = matrix.getHeight();          BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);          for (int x = 0; x < width; x++) {              for (int y = 0; y < height; y++) {                  image.setRGB(x, y, matrix.get(x, y) ? BLACK : WHITE);              }          }          return image;      }      public static void main(String[] args) {          createQrCode("www.baidu.com","D:\","code.jpg");      }  }

seviceImpl的类。统一下单

@Value("${app.wx-pay-appId}")  private String appid;    @Value("${app.wx-pay-appSecret}")  private String appsecret;    @Value("${app.wx-pay-mchId}")  private String mch_id;    @Value("${app.wx-pay-apiKey}")  private String key;    @Value("${app.wx-pay-ufdoderUrl}")  private String ufdoderUrl;    @Value("${app.wx-pay-notifyUrl}")  private String notify_url;    @Value("${app.wx-pay-refundUrl}")  private String refundUrl;            String requestId = UUID.randomUUID().toString().replace("-", "").toLowerCase();           String out_trade_no =""+System.currentTimeMillis(); //订单号,先生成随机数。后修改为正式的            String currTime = PayToolUtil.getCurrTime();          String strTime = currTime.substring(8, currTime.length());          String strRandom = PayToolUtil.buildRandom(4) + "";          //随机字符串生成          String nonce_str = strTime + strRandom;           //交易类型          String trade_type = "NATIVE";          SortedMap<Object,Object> packageParams = new TreeMap<Object,Object>();          packageParams.put("appid", appid);          packageParams.put("mch_id", mch_id);          packageParams.put("nonce_str", nonce_str);//时间戳          //测试          packageParams.put("body", "哈哈哈哈哈");          packageParams.put("out_trade_no", out_trade_no);            //价格的单位为分          packageParams.put("total_fee", "1");         //正式的处理現在一定是小數點兩位的         // packageParams.put("total_fee", 正式的价格.replace(".",""));            packageParams.put("spbill_create_ip", "127.0.0.1");          //正式获取真实IP          //packageParams.put("spbill_create_ip", HttpUtil.getIpAddress(request));          packageParams.put("notify_url", notify_url);          packageParams.put("trade_type", trade_type);              //签名          String sign = PayToolUtil.createSign("UTF-8", packageParams,key);          packageParams.put("sign", sign);            String requestXML = PayToolUtil.getRequestXml(packageParams);          System.out.println(requestXML);          logger.info("requestId:{},function:{},request:{},response:{}",requestId ,"微信产品参数",packageParams.toString(),requestXML);          String resXml = HttpUtil.postData(ufdoderUrl, requestXML);          logger.info("requestId:{},userId:{},function:{}",requestId ,"请求微信支付");          logger.info("requestId:{},function:{},request:{},response:{}",requestId ,"微信请求支付url",requestXML,resXml);            Map map = null;          try {              map = XMLUtil4jdom.doXMLParse(resXml);          } catch (JDOMException e) {              e.printStackTrace();          } catch (IOException e) {              e.printStackTrace();          }          String urlCode = (String) map.get("code_url");          logger.info("requestId:{},function:{},response:{}",requestId(),"微信获取支付url",urlCode);

controller层

public void pay(HttpServletRequest request, HttpServletResponse response) {    String requestId = UUID.randomUUID().toString().replace("-", "").toLowerCase();        //此处执行付款      try {         //得到二维码链接          String text = iserver.xxxx();          logger.info("function:{},response:{}" "微信pc扫码支付", text);          System.out.println(text);          //根据url来生成生成二维码          int width = 300;          int height = 300;          //二维码的图片格式          String format = "jpg";          Hashtable hints = new Hashtable();          //内容所使用编码          hints.put(EncodeHintType.CHARACTER_SET, "utf-8");          BitMatrix bitMatrix;          bitMatrix = new MultiFormatWriter().encode(text, BarcodeFormat.QR_CODE, width, height, hints);          QRUtil.writeToStream(bitMatrix, format, response.getOutputStream());      } catch (WriterException e) {          logger.info("requestId:{} err:{}", requestId, e);          logger.error("requestId:{} err:{}", requestId, e);      } catch (IOException e) {          logger.info("requestId:{} err:{}", requestId, e);          logger.error("requestId:{} err:{}", requestId, e);      }  }

微信回调

//微信回调  @PostMapping(value = "/get")  @SkipUserAuth  public void get(HttpServletRequest request, HttpServletResponse response) throws IOException, ServiceException {      //读取参数      InputStream inputStream;      StringBuffer sb = new StringBuffer();      inputStream = request.getInputStream();      String s;      BufferedReader in = null;      try {          in = new BufferedReader(new InputStreamReader(inputStream, "UTF-8"));      } catch (UnsupportedEncodingException e) {          e.printStackTrace();      }      while ((s = in.readLine()) != null) {          sb.append(s);      }      in.close();      inputStream.close();        //解析xml成map      Map<String, String> m = new HashMap<String, String>();      try {          m = XMLUtil4jdom.doXMLParse(sb.toString());      } catch (JDOMException e) {          e.printStackTrace();      }      logger.info("requestId:{},function:{},response:{}" , requestId , "微信回调接口" , sb.toString());        //过滤空 设置 TreeMap      SortedMap<Object, Object> packageParams = new TreeMap<Object, Object>();      Iterator it = m.keySet().iterator();      while (it.hasNext()) {          String parameter = (String) it.next();          String parameterValue = m.get(parameter);            String v = "";          if (null != parameterValue) {              v = parameterValue.trim();          }          packageParams.put(parameter, v);      }        // 账号信息      String key = apiKey;          //判断签名是否正确      if (PayToolUtil.isTenpaySign("UTF-8", packageParams, key)) {          //------------------------------          //处理业务开始          //------------------------------          String resXml = "";          if ("SUCCESS".equals((String) packageParams.get("result_code"))) {              // 这里是支付成功                String mch_id = (String) packageParams.get("mch_id");              String openid = (String) packageParams.get("openid");              String is_subscribe = (String) packageParams.get("is_subscribe");              String out_trade_no = (String) packageParams.get("out_trade_no");                String total_fee = (String) packageParams.get("total_fee");                //////////执行自己的业务逻辑////////////////              //暂时使用最简单的业务逻辑来处理:只是将业务处理结果保存到session中              //(根据自己的实际业务逻辑来调整,很多时候,我们会操作业务表,将返回成功的状态保留下来)              request.getSession().setAttribute("_PAY_RESULT", "OK");              System.out.println("支付成功");              //通知微信.异步确认成功.必写.不然会一直通知后台.八次之后就认为交易失败了.              resXml = "<xml>" + "<return_code><![CDATA[SUCCESS]]></return_code>"                      + "<return_msg><![CDATA[OK]]></return_msg>" + "</xml> ";              logger.info("requestId:{},function:{},response:{}" , requestId , "微信回调打印success信息" , resXml);          } else {              resXml = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>"                      + "<return_msg><![CDATA[报文为空]]></return_msg>" + "</xml> ";              logger.info("requestId:{},function:{},response:{}" , requestId , "微信回调报文为空" , resXml);          }            //------------------------------          //处理业务完毕          //------------------------------          BufferedOutputStream out = new BufferedOutputStream(                  response.getOutputStream());          out.write(resXml.getBytes());          out.flush();          out.close();      } else {          logger.info("requestId:{},function:{}" , requestId , "微信回调签名验证失败");          System.out.println("通知签名验证失败");      }    }