JAVA微信掃碼支付模式二線上支付功能實現以及回調_java

來源:互聯網
上載者:User

 一、準備工作

首先吐槽一下微信關於支付這塊,本身支援的支付模式就好幾種,但是官方文檔特別零散,連像樣的Java相關的demo也沒幾個。本人之前沒有搞過微信支付,一開始真是被它搞暈,折騰兩天終於調通了,特此寫下來,以享後人吧!

關於準備工作,就“微信掃碼支付模式二”官方文檔地址在這 https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=6_1 可以先看看,實際上需要準備的東西有以下幾個:

其中APP_ID和APP_SECRET可以在公眾平台找著,MCH_ID和API_KEY則在商戶平台找到,特別是API_KEY要在商戶平台設定好,對於“微信掃碼支付模式二”(支付與回調)實際只會用到APP_ID、MCH_ID和API_KEY,其他的都不用。

關於開發環境,我就不羅嗦了,不管你是springMVC、struts2又或者直接serverlet,都差不多,只要你能保證對應的方法能調用起來就行。關於引用第三方的jar包,我這裡只用到了一個xml操作的jdom,記住是1.*的版本,不是官網上最新的2.*,兩者不相容。具體是jdom-1.1.3.jar,依賴包jaxen-1.1.6.jar,就這兩個包,我沒用到有些例子中使用的httpclient,感覺沒必要,而且依賴包特別繁雜,當然你是maven當我沒說。

二、開發實戰

1、首先是接入微信介面,擷取微信支付二維碼。

public String weixin_pay() throws Exception {     // 帳號資訊     String appid = PayConfigUtil.APP_ID; // appid     //String appsecret = PayConfigUtil.APP_SECRET; // appsecret     String mch_id = PayConfigUtil.MCH_ID; // 商業號     String key = PayConfigUtil.API_KEY; // key      String currTime = PayCommonUtil.getCurrTime();     String strTime = currTime.substring(8, currTime.length());     String strRandom = PayCommonUtil.buildRandom(4) + "";     String nonce_str = strTime + strRandom;          String order_price = 1; // 價格  注意:價格的單位是分     String body = "goodssssss";  // 商品名稱     String out_trade_no = "11338"; // 訂單號          // 擷取發起電腦 ip     String spbill_create_ip = PayConfigUtil.CREATE_IP;     // 回調介面      String notify_url = PayConfigUtil.NOTIFY_URL;     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", body);     packageParams.put("out_trade_no", out_trade_no);     packageParams.put("total_fee", order_price);     packageParams.put("spbill_create_ip", spbill_create_ip);     packageParams.put("notify_url", notify_url);     packageParams.put("trade_type", trade_type);      String sign = PayCommonUtil.createSign("UTF-8", packageParams,key);     packageParams.put("sign", sign);          String requestXML = PayCommonUtil.getRequestXml(packageParams);     System.out.println(requestXML);       String resXml = HttpUtil.postData(PayConfigUtil.UFDODER_URL, requestXML);           Map map = XMLUtil.doXMLParse(resXml);     //String return_code = (String) map.get("return_code");     //String prepay_id = (String) map.get("prepay_id");     String urlCode = (String) map.get("code_url");          return urlCode; } 

如果不出意外的話,這裡就從微信伺服器擷取了一個支付url,形如weixin://wxpay/bizpayurl?pr=pIxXXXX,之後我們就需要把這個url產生一個二維碼,然後就可以使用自己手機微信端掃碼支付了。關於二維碼產生有很多種方法,各位各取所需吧,我這裡提供一個google的二維碼產生介面:

public static String QRfromGoogle(String chl) throws Exception {   int widhtHeight = 300;   String EC_level = "L";   int margin = 0;   chl = UrlEncode(chl);   String QRfromGoogle = "http://chart.apis.google.com/chart?chs=" + widhtHeight + "x" + widhtHeight       + "&cht=qr&chld=" + EC_level + "|" + margin + "&chl=" + chl;    return QRfromGoogle; } 
// 特殊字元處理 public static String UrlEncode(String src) throws UnsupportedEncodingException {   return URLEncoder.encode(src, "UTF-8").replace("+", "%20"); } 

上面代碼中涉及到幾個工具類:PayConfigUtil、PayCommonUtil、HttpUtil和XMLUtil,其中PayConfigUtil放的就是上面提到一些配置及路徑,PayCommonUtil涉及到了擷取當前事件、產生隨機字串、擷取參數簽名和拼接xml幾個方法,代碼如下:

public class PayCommonUtil {      /**    * 是否簽名正確,規則是:按參數名稱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    *      編碼格式    * @param parameters    *      請求參數    * @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;   }  } 

HttpUtil和XMLUtil如下:

public class HttpUtil {    private static final Log logger = Logs.get();   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("\r\n");       }       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;   } } 
public class XMLUtil {   /**    * 解析xml,返回第一級元素索引值對。如果第一級元素有子節點,則此節點的值是子節點的xml資料。    * @param strxml    * @return    * @throws JDOMException    * @throws IOException    */   public static Map doXMLParse(String strxml) throws JDOMException, IOException {     strxml = strxml.replaceFirst("encoding=\".*\"", "encoding=\"UTF-8\"");      if(null == strxml || "".equals(strxml)) {       return null;     }          Map m = new HashMap();          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 = XMLUtil.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(XMLUtil.getChildrenText(list));         }         sb.append(value);         sb.append("</" + name + ">");       }     }          return sb.toString();   }    } 

當然還有一個MD5計算工具類

public class MD5Util {    private static String byteArrayToHexString(byte b[]) {     StringBuffer resultSb = new StringBuffer();     for (int i = 0; i < b.length; i++)       resultSb.append(byteToHexString(b[i]));      return resultSb.toString();   }    private static String byteToHexString(byte b) {     int n = b;     if (n < 0)       n += 256;     int d1 = n / 16;     int d2 = n % 16;     return hexDigits[d1] + hexDigits[d2];   }    public static String MD5Encode(String origin, String charsetname) {     String resultString = null;     try {       resultString = new String(origin);       MessageDigest md = MessageDigest.getInstance("MD5");       if (charsetname == null || "".equals(charsetname))         resultString = byteArrayToHexString(md.digest(resultString             .getBytes()));       else         resultString = byteArrayToHexString(md.digest(resultString             .getBytes(charsetname)));     } catch (Exception exception) {     }     return resultString;   }    private static final String hexDigits[] = { "0", "1", "2", "3", "4", "5",       "6", "7", "8", "9", "a", "b", "c", "d", "e", "f" };  } 

2、支付回調

支付完成後,微信會把相關支付結果和使用者資訊發送到我們上面指定的那個回調地址,我們需要接收處理,並返回應答。對後台通知互動時,如果微信收到商戶的應答不是成功或逾時,微信認為通知失敗,微信會通過一定的策略定期重新發起通知,儘可能提高通知的成功率,但微信不保證通知最終能成功。 (通知頻率為15/15/30/180/1800/1800/1800/1800/3600,單位:秒)

關於支付回調介面,我們首先要對於支付結果通知的內容進行簽名驗證,然後根據支付結果進行相應的處理流程即可。

public void weixin_notify(HttpServletRequest request,HttpServletResponse response) throws Exception{          //讀取參數     InputStream inputStream ;     StringBuffer sb = new StringBuffer();     inputStream = request.getInputStream();     String s ;     BufferedReader in = new BufferedReader(new InputStreamReader(inputStream, "UTF-8"));     while ((s = in.readLine()) != null){       sb.append(s);     }     in.close();     inputStream.close();      //解析xml成map     Map<String, String> m = new HashMap<String, String>();     m = XMLUtil.doXMLParse(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 = PayConfigUtil.API_KEY; // key      logger.info(packageParams);     //判斷簽名是否正確     if(PayCommonUtil.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");                  logger.info("mch_id:"+mch_id);         logger.info("openid:"+openid);         logger.info("is_subscribe:"+is_subscribe);         logger.info("out_trade_no:"+out_trade_no);         logger.info("total_fee:"+total_fee);                  //////////執行自己的商務邏輯////////////////                  logger.info("支付成功");         //通知微信.非同步確認成功.必寫.不然會一直通知後台.八次之後就認為交易失敗了.         resXml = "<xml>" + "<return_code><![CDATA[SUCCESS]]></return_code>"             + "<return_msg><![CDATA[OK]]></return_msg>" + "</xml> ";                } else {         logger.info("支付失敗,錯誤資訊:" + packageParams.get("err_code"));         resXml = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>"             + "<return_msg><![CDATA[報文為空白]]></return_msg>" + "</xml> ";       }       //------------------------------       //處理業務完畢       //------------------------------       BufferedOutputStream out = new BufferedOutputStream(           response.getOutputStream());       out.write(resXml.getBytes());       out.flush();       out.close();     } else{       logger.info("通知簽名驗證失敗");     }        } 

    簽名驗證演算法和簽名產生的演算法類似,在上面PayCommonUtil工具類中提供。
三、後話

感覺微信掃描支付體驗效果還是挺好的,唯一缺點就是相關文檔零散,官方的demo居然沒有java編寫的,希望之後微信官方能夠逐步完善吧!

以上就是本文的全部內容,希望對大家的學習有所協助,也希望大家多多支援雲棲社區。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.