­

微信掃碼支付

  • 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("通知簽名驗證失敗");      }    }