Java微信掃碼支付

        前言:讓我用Java寫個微信掃碼支付,身爲小白,網上搜了好多文章,終於找到一個看得明白的,連接。表示人家講的夠詳細了,如今本身要是實現一個,我以爲吧,可能入手比較亂。其實後來發現,代碼都是按照那個流程圖寫的,寫代碼是用來實現功能的,固然要按照功能分析別人的代碼,不然都不知道人家寫來幹什麼的。別人的東西不必定都好,就要給他改改,改的好纔是真好,,哈哈。下面看內容!!!php

 

1、微信掃碼支付內容

1.文檔:

2.選擇:

  • 代碼工具與方式----用的eclipse的插件版:STS,用servlet簡單實現。
  • 入手----實現微信掃碼支付的功能,先從模式二的時序流程圖入手。
  • 後臺:微信商戶平臺----申請成爲微信商戶後能登錄的,後臺查看訂單狀況。
  • 模式:模式二----模式二的時序流程圖,模式二相比模式一更簡單,由於模式二不須要本身生成二維碼信息,只是接收微信返回的信息。
  • 交易類型:掃碼支付----實現的是掃碼支付,若是是公衆號支付、app支付和刷卡支付,請繞行。
  • 穿透工具:花生殼----由於公司已經買了,我用就行。因爲微信的回調地址要求必須是是外網的80端口,怎麼辦?我用的是付費的花生殼內網穿透版,能夠使用國內免費的相似ngrok的natapp
  • debug調試:查看返回xml內容
  • 流程大概:從時序圖分析,
  • ①調用統一下單API:就是給統一下單的參數賦值(必需的參數就行),而後向地址(微信提供的統一下單URL)傳參數,而後接受微信返回參數。其中傳遞的參數格式都是xml格式。
  • ②微信返回的信息裏有二維碼對應的地址url(code_url)。用第三方庫將code_url生成二維碼。
  • ③調用支付結果API異步接收微信通知的支付結果,接收地址(統一下單裏自定義的)接收微信傳過來的參數,再返給微信參數,表示收到通知。

3.流程

(1)參數賦值

  • 根據統一下單API必須傳的參數,進行參數賦值:
  • 商戶號 mch_id :微信分配,企業號corpid即爲此appId 
  • 隨機字符串 nonce_str :微信分配,微信支付分配的商戶號
  • 商品描述 body  :商戶自定義,商品名稱
  • 商戶訂單號 out_trade_no :商戶自定義,惟一,詳見商戶訂單號
  • 總金額 total_fee :商戶自定義,整數,單位爲分,測試值設爲1,表明一分錢
  • 終端IP spbill_create_ip :獲取支付端IP,測試值設爲固定值,如127.0.0.1
  • 通知地址 notify_url :商戶自定義,微信返回含有二維碼信息的地址
  • 交易類型 trade_type :三選一,掃碼支付是 NATIVE,詳細說明見參數規定
  • 簽名 sign :詳見簽名生成算法

(2)拼接參數

  • 將參數拼成xml格式字符串:

(3)參數提交

  • 傳xml字符串格式的參數到統一下單API的URL地址:post方式提交

(4)解析返回參數

  • 微信返回xml字符串,詳細參數見統一下單API,解析爲map集合,其中解析xml會用到jdom庫。

(5)接收支付結果

  • 接收xml字符串,解析成map集合,判斷簽名是否正確,執行成功或失敗的相應業務,
  • 返回xml字符串,告訴微信已經接收到支付結果通知了。
  • 由於對後臺通知交互時,若是微信收到商戶的應答不是成功或超時,微信認爲通知失敗。

(6)申請退款

4.資料

    (1)固定參數:申請成爲微信商戶成功,微信會郵件通知html

  • APP_ID--微信分配的公衆帳號ID,如何得到
  • MCH_ID--微信支付分配的商戶號
  • API_KEY--商戶API密鑰KEY,如何得到
  • NOTIFY_URL--商戶自定義的接收微信異步通知支付結果的網址

    (2)用到的jar包:java

    (3)穿透工具:git

    (4)參考文章 :     web

    (5)易錯總結--簽名錯誤算法

  • 產生緣由:參數名多加空格???(我也不清楚)參數名拼寫錯誤也有可能,待驗證
  • 解決方法:微信官方接口調試生成的字符串,和調試過程當中的簽名字符串對比。
  • 解決步驟:
  • 微信公衆平臺支付接口調試工具,對應輸入參數和值。
  • ②點擊生成簽名,出現
  •                 #1.生成字符串:
  •                 #2.鏈接商戶key:假如叫簽名字符串A
  •                 #3.md5編碼並轉成大寫:
  •                 #4.最終的提交xml:
  • ③debug調試:在生成簽名過程當中打斷點,輸出拼成字符串,假如叫簽名字符串B
  • ④微信官方調試工具生成的簽名字符串A,和實際debug過程當中的簽名字符串B,對比,不一樣之處一目瞭然。

(6)後續總結和問題spring

2、實現步驟代碼

先來張圖,文件結構要清晰apache

1.生成二維碼

  • servlet寫的:參數賦值-->拼接參數-->參數提交-->參數返回並解析-->對應參數生成二維碼
public class EnterServlet extends HttpServlet {
	public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
	
        //1,根據統一下單API,進行參數設定
        String appid = PayConfigUtil.APP_ID;    // appid   
        String mch_id = PayConfigUtil.MCH_ID;   // 商業號  
        String key = PayConfigUtil.API_KEY;     // key  
        String currTime = PayCommonUtil.getCurrTime();   
        String strRandom = PayCommonUtil.buildRandom(4) + "";  
        String nonce_str = currTime + strRandom;        // 隨機字符串 
        String order_price = "1";       // 價格的單位是分(必須整數)   
        String body = "iphone7";        // 商品名稱
        String out_trade_no = nonce_str + "520" ;       // 訂單號
        String spbill_create_ip = PayCommonUtil.CREAT_IP;       // 獲取發起電腦ip    
        String notify_url = PayConfigUtil.NOTIFY_URL;   // 回調接口
        String trade_type = "NATIVE";   // 交易類型是掃碼支付
        //2,將參數放進Map集合
        SortedMap<String,String> packageParams = new TreeMap<String,String>();  
        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);
        //3,簽名算法
        String sign = PayCommonUtil.createSign("UTF-8", packageParams,key);  
        packageParams.put("sign", sign);  

        //4,將Map集合轉換爲xml格式
        String requestXML = PayCommonUtil.getRequestXml(packageParams);  
        //5,用POST方式提交
        String resXml = HttpUtil.postData(PayConfigUtil.PAY_API, requestXML);  
        //6,解析xml爲Map集合,獲得code_url(二維碼連接)
        Map<String, String> map = XMLUtil.doXMLParse(resXml);
        String urlCode = (String) map.get("code_url");
        //7,將code_url生成二維碼
        Qrcode encoder = new Qrcode();
        encoder.createQRCoder(urlCode, response);
        }
        
        protected void doPost(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		doGet(request, response);
	}
}

2.支付結果通知

  • 返回xml數據解析-->返回xml表示已接收到通知
​

public class NotifyServlet extends HttpServlet {

        public void doGet(HttpServletRequest request, HttpServletResponse response) 
                throws ServletException, IOException {
        //1,讀取微信返回的xml格式信息流
        StringBuffer sb = new StringBuffer();  
        InputStream 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();  
        //2,解析xml爲Map集合
        Map<String, String> m = XMLUtil.doXMLParse(sb.toString());
        //3,過濾空,用TreeMap集合存儲信息
        SortedMap<String, String> packageParams = new TreeMap<String, String>();        
        Iterator<String> it = m.keySet().iterator();  
        while (it.hasNext()) {  
            String parameter = it.next();  
            String parameterValue = m.get(parameter);  
            String v = "";  
            if(null != parameterValue) {  
                v = parameterValue.trim();  
            }  
            packageParams.put(parameter, v);  
        }
        // 4,判斷簽名是否正確
        String key = PayConfigUtil.API_KEY; // key  
        if(PayCommonUtil.isTenpaySign("UTF-8", packageParams,key)) {  
           String resXml = "";   
        // String out_trade_no = packageParams.get("out_trade_no"); 
           if("SUCCESS".equals((String)packageParams.get("result_code"))){  
        //裏是支付成功 ,執行本身的業務邏輯
              
        //通知微信.異步確認成功.必寫.否則會一直通知後臺.八次以後就認爲交易失敗了.  
                resXml = "<xml>" + "<return_code><![CDATA[SUCCESS]]></return_code>"  
                        + "<return_msg><![CDATA[OK]]></return_msg>" + "</xml> ";  
            } else {  
        //提示支付失敗,執行相應業務邏輯
              
                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{  
        //通知簽名驗證失敗
        }
        }

        public void doPost(HttpServletRequest request, HttpServletResponse response) 
                throws ServletException, IOException {
                doGet(request, response);
        }
}

​

3.工具類 

(1)固定參數

  • 微信分配的帳戶號 + 自定義的接收支付結果通知用的回調地址
public class PayConfigUtil {
        public static String APP_ID = "";//微信開放平臺應用ID
        public static String MCH_ID = "";//商業號
        public static String API_KEY = "";//API key
        public static String PayConfigUtil.CREATE_IP = "127.0.0.1";//測試用的固定值,怎麼獲取公網IP暫時不會
        public static String NOTIFY_URL = "";//回調地址
}

(2)通用方法

  • 通用方法:簽名是否正確 + 簽名算法 + 拼成xml方法 + 生成隨機數方法 + 獲取當前時間方法
public class PayCommonUtil {

        /**
         * 是否簽名正確,規則是:按參數名稱a-z排序,遇到空值的參數不參加簽名。
         * 
         * @return boolean
         */
        public static boolean isTenpaySign(String characterEncoding, SortedMap<String, String> packageParams,
                        String API_KEY) {
                StringBuffer sb = new StringBuffer();
                Set<Entry<String,String>> es = packageParams.entrySet();
                Iterator<Entry<String, String>> it = es.iterator();
                while (it.hasNext()) {
                        Entry<String, String> 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 
         * @Description:sign簽名
         * @param characterEncoding
         *            編碼格式
         * @param parameters
         *            請求參數
         * @return
         */
        
        public static String createSign(String characterEncoding, SortedMap<String,String> packageParams, String API_KEY) {
                StringBuffer sb = new StringBuffer();
                Set<Entry<String,String>> es = packageParams.entrySet();
                Iterator<Entry<String, String>> it = es.iterator();
                while (it.hasNext()) {
                        Entry<String,String> 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;
        }

        /**
         * @Description:將請求參數轉換爲xml格式的string
         */
        public static String getRequestXml(SortedMap<String,String> parameters) {
                StringBuffer sb = new StringBuffer();
                sb.append("<xml>");
                Set<Entry<String,String>> es = parameters.entrySet();
                Iterator<Entry<String, String>> it = es.iterator();
                while (it.hasNext()) {
                        Entry<String,String> 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;
        }
}

(3)解析XML 

  • 將xml解析成map集合,根元素-->子元素-->遍歷存到集合-->子元素是多層的要遞歸(按道理說)
public class XMLUtil {  
    
    /** 
     * 解析xml,返回第一級元素鍵值對。若是第一級元素有子節點,則此節點的值是子節點的xml數據。 
     * @param strxml 
     * @return 
     * @throws JDOMException 
     * @throws IOException 
     */  
    @SuppressWarnings("unchecked")
    public static Map<String, Object> doXMLParse(String strxml) throws JDOMException, IOException {  
        strxml = strxml.replaceFirst("encoding=\".*\"", "encoding=\"UTF-8\"");  
  
        if(null == strxml || "".equals(strxml)) {  
            return null;  
        }  
        Map<String, Object> m = new HashMap<String, Object>();  
          
        InputStream in = new ByteArrayInputStream(strxml.getBytes("UTF-8"));  
        SAXBuilder builder = new SAXBuilder();  
        Document doc = builder.build(in);  
        Element root = doc.getRootElement();  
        List<Element> list = root.getChildren();  
        Iterator<Element> it = list.iterator();  
        while(it.hasNext()) {  
            Element e = (Element) it.next();  
            String k = e.getName();  
            String v = "";  
            List<Element> 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 
     */  
    @SuppressWarnings("unchecked")
    public static String getChildrenText(List<Element> children) {  
        StringBuffer sb = new StringBuffer();  
        if(!children.isEmpty()) {  
            Iterator<Element> it = children.iterator();  
            while(it.hasNext()) {  
                Element e = (Element) it.next();  
                String name = e.getName();  
                String value = e.getTextNormalize();  
                List<Element> list = e.getChildren();  
                sb.append("<" + name + ">");  
                if(!list.isEmpty()) {  
                    sb.append(XMLUtil.getChildrenText(list));  
                }  
                sb.append(value);  
                sb.append("</" + name + ">");  
            }  
        }  
        return sb.toString();  
    }  
}

(4)POST提交

  • post方式提交,說get方式不安全,不涉及證書
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("\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;
        }
}

(5)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" };

}

 MD5Util

(6)第三方庫生成二維碼 

  • 谷歌生成二維碼的zxing包示例使用好簡單,直接返回BufferImage類型,二進制流圖片
public class Qrcode {
        public void createQRCoder(String content, HttpServletResponse response) {
        try {
            int width = 400;//二維碼寬度
            int height = 400;//二維碼高度
            String qrcodeFormat = "png";//圖片類型
            HashMap<EncodeHintType, String> hints = new HashMap<EncodeHintType, String>();
            hints.put(EncodeHintType.CHARACTER_SET, "UTF-8");
            //MultiFormatWriter 對象爲生成二維碼的核心類,後面的 MatrixToImageWriter 只是將二維碼矩陣輸出到圖片上面。
            BitMatrix bitMatrix = new MultiFormatWriter().encode(content, BarcodeFormat.QR_CODE, width, height, hints);
            MatrixToImageWriter.writeToStream(bitMatrix, qrcodeFormat, response.getOutputStream());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }  
}
相關文章
相關標籤/搜索