1. 程式人生 > >Java整合微信H5支付/支付寶手機網站支付

Java整合微信H5支付/支付寶手機網站支付

微信H5支付:

1,微信外部H5支付:

名詞解釋:就是在自己的H5網站頁面裡呼叫微信支付功能,注意,這裡只能是在微信外部支付,在微信內開啟網站是無法支付的,

要另外使用微信公眾號支付

呼叫微信H5支付介面前提條件:

1,註冊公眾號並且通過認證

2,在公眾號裡申請微信支付,成為商戶號

3,在商戶平臺裡申請H5支付

4,在商戶平臺裡的開發設定裡設定好H5支付域名

以上4個條件都滿足時,便可以呼叫微信H5支付介面

https://pay.weixin.qq.com/wiki/doc/api/H5.php?chapter=9_20&index=1

上面網址是微信支付API文件,先了解呼叫介面需要傳遞的引數,僅看必填項:

appid:微信分配的公眾賬號ID(企業號corpid即為此appId)

mch_id:微信支付分配的商戶號

nonce_str:隨機字串,不長於32位

sign:簽名

body:商品簡單描述,該欄位須嚴格按照規範傳遞

out_trade_no:商戶系統內部的訂單號,32個字元內、可包含字母

total_fee:訂單總金額,單位為分

spbill_create_ip:必須傳正確的使用者端IP

notify_url:接收微信支付非同步通知回撥地址,通知url必須為直接可訪問的url,不能攜帶引數

trade_type:H5支付的交易型別為MWEB

scene_info:場景資訊,API文件上標註是必填項,但是我沒用到,也成功了

以上就是我們要呼叫微信H5支付介面需要的引數,然後按照以下步驟處理引數:

先呼叫統一下單介面,獲取微信跳轉的支付頁面URL地址

1,先生成一個隨機的32位字串

String nonceStr = UUID.randomUUID().toString().trim().replaceAll("-", "").toUpperCase();

2,生成簽名sign

把引數封裝成一個map,傳遞到以下方法中,得到一個排序後的字串stringA:

Map<String, String> map = new HashMap<String, String>();
map.put("nonce_str", nonceStr);
map.put("appid", appId);
map.put("mch_id", mchId);
map.put("body", body);
map.put("out_trade_no", outTradeNo);
map.put("total_fee", totalFee);
map.put("spbill_create_ip", "113.87.162.45");
map.put("notify_url", notifyUrl);
map.put("trade_type", tradeType);
map.put("key", key);

注意:這個key是商戶平臺裡面設定的一個祕鑰key,要拼接到引數中

String stringA = UnicodeUtils.formatUrlMap(map, false, true);

/** 
     *  
     * 方法用途: 對所有傳入引數按照欄位名的Unicode碼從小到大排序(字典序),並且生成url引數串<br> 
     * 實現步驟: <br> 
     *  
     * @param paraMap   要排序的Map物件 
     * @param urlEncode   是否需要URLENCODE 
     * @param keyToLower    是否需要將Key轉換為全小寫 
     *            true:key轉化成小寫,false:不轉化 
     * @return 
     */  
    public static String formatUrlMap(Map<String, String> paraMap, boolean urlEncode, boolean keyToLower){  
        String buff = "";  
        Map<String, String> tmpMap = paraMap;  
        try{  
            List<Map.Entry<String, String>> infoIds = new ArrayList<Map.Entry<String, String>>(tmpMap.entrySet());  
            // 對所有傳入引數按照欄位名的 ASCII 碼從小到大排序(字典序)  
            Collections.sort(infoIds, new Comparator<Map.Entry<String, String>>() {  
   
                @Override  
                public int compare(Map.Entry<String, String> o1, Map.Entry<String, String> o2) {  
                    return (o1.getKey()).toString().compareTo(o2.getKey());  
                }  
            });  
            // 構造URL 鍵值對的格式  
            StringBuilder buf = new StringBuilder();  
            for (Map.Entry<String, String> item : infoIds){  
                if (StringUtils.isNotBlank(item.getKey())){  
                    String key = item.getKey();  
                    String val = item.getValue();  
                    if (urlEncode){  
                        val = URLEncoder.encode(val, "utf-8");  
                    }  
                    if (keyToLower){  
                        buf.append(key.toLowerCase() + "=" + val);  
                    } else {  
                        buf.append(key + "=" + val);  
                    }  
                    buf.append("&");  
                }  
            }  
            buff = buf.toString();  
            if (buff.isEmpty() == false){  
                buff = buff.substring(0, buff.length() - 1);  
            }  
        } catch (Exception e){  
           return null;  
        }  
        return buff;  
    }  

通過上面方法得到排序後的字串stringA後,拼接key並用MD5加密:

String stringSignTemp = stringA + "&key=" + key;

String sign = MD5Utils.getMD5(stringSignTemp);

簽名sign就得到了

3,在把包括簽名sign的所有引數轉成XML格式:

Map<String, String> map = new HashMap<String, String>();
map.put("nonce_str", nonceStr);
map.put("appid", appId);
map.put("mch_id", mchId);
map.put("body", body);
map.put("out_trade_no", outTradeNo);
map.put("total_fee", totalFee);
map.put("spbill_create_ip", "113.87.162.45");
map.put("notify_url", notifyUrl);
map.put("trade_type", tradeType);
map.put("key", key);
		
String sign = WeChatUtil.buildSign(map);
log.info("生成的支付簽名:" + sign);
map.put("sign", sign);
		
String xmlBody = XmlUtils.map2Xml(map);
log.info("微信支付XML引數:" + xmlBody);

4,把XML作為引數呼叫微信的支付介面:

String payUrl = wechatConfig.getPayUrl();
String result = HttpUtils.getInstance().postXml(payUrl, xmlBody);
log.info("微信支付結果:" + result);
Map<String, Object> resultMap = XmlUtils.xml2Map(result);
//微信返回的微信支付頁面URL
mwebUrl = (String)resultMap.get("mweb_url");
mwebUrl = mwebUrl + "|" + outTradeNo;
			
String prepayId = (String)resultMap.get("prepay_id");
String returnCode = (String)resultMap.get("return_code");
if(returnCode.equals("SUCCESS")){
    //開始業務操作....
}

如果引數都沒有問題呼叫介面返回就會包含mweb_url這樣一個引數

這是微信支付的頁面跳轉連結,當返回結果為SECCUSS的時候處理完業務,把mweb_url返回到前端頁面進行跳轉

注意:如果trade_type設定的不是mweb_url的話是沒有這個引數返回的

5,驗證支付結果

當跳轉到微信支付頁面的時候,支付的過程其實是在微信APP裡面操作的了,跟我們沒什麼關係

當支付完成後,預設會跳轉回我們發起支付的頁面,2種方式驗證:

1,被動驗證:

通過前面設定的notify_url地址,微信回給這個地址傳送支付結果,在這個地址處理微信傳送的資訊再做業務處理

public Object receiveWechatPayResult(HttpServletResponse response, HttpServletRequest request) {
	    String resXml = "";  
        InputStream inStream;  
        try {  
            inStream = request.getInputStream();  
  
            ByteArrayOutputStream outSteam = new ByteArrayOutputStream();  
            byte[] buffer = new byte[1024];  
            int len = 0;  
            while ((len = inStream.read(buffer)) != -1) {  
                outSteam.write(buffer, 0, len);  
            }  
            outSteam.close();  
            inStream.close();  
            String result = new String(outSteam.toByteArray(), "utf-8");// 獲取微信呼叫我們notify_url的返回資訊  
            log.error("微信支付----result----=" + result);  
            Map<String, Object> map = XmlUtils.xml2Map(result);
            //業務操作.....
            resXml = "<xml>"
            		+ "<return_code><![CDATA[SUCCESS]]></return_code>"  
                    + "<return_msg><![CDATA[OK]]></return_msg>"
                    + "</xml> ";  
            
        } catch (IOException e) {  
            // TODO Auto-generated catch block  
        	log.error("支付回調發布異常:" + e);  
            e.printStackTrace();  
        }  
        return resXml;
	}

這裡要注意的是返回的內容

<xml>
     <return_code><![CDATA[SUCCESS]]></return_code>
     <return_msg><![CDATA[OK]]></return_msg>
</xml> 

如果沒有正確返回,會連續收到微信伺服器推送的訊息

如果正確返回了,只會收到3次微信推送的訊息

這是本人的猜測,如果有大神清楚的請留言告知....

這裡會有個延時的問題,就是微信有可能沒有及時的往你這個地址傳送,所以為了不影響業務,我們還可以主動驗證

2,主動驗證:

呼叫微信的訂單查詢介面查詢剛剛的微信支付是否成功

                Map<String, String> map = new HashMap<String, String>();
		String nonceStr = UUID.randomUUID().toString().trim().replaceAll("-", "").toUpperCase(); 
		String appId = wechatConfig.getAppId();
		String mchId = wechatConfig.getMchId();
		String key = wechatConfig.getKey();
		map.put("nonce_str", nonceStr);
		map.put("appid", appId);
		map.put("mch_id", mchId);
		map.put("out_trade_no", tradeNo);
		map.put("key", key);
		
		String sign = WeChatUtil.buildSign(map);
		map.put("sign", sign);
		log.info("生成的支付簽名:" + sign);
		
		String xmlBody = XmlUtils.map2Xml(map);
		log.info("微信查詢訂單狀態XML引數:" + xmlBody);
		
		try {
			String queryPayStatusUrl = wechatConfig.getQueryPayStatusUrl();
			String result = HttpUtils.getInstance().postXml(queryPayStatusUrl, xmlBody);
			log.info("微信查詢訂單狀態結果:" + result);
			Map<String, Object> resultMap = XmlUtils.xml2Map(result);
			tradeState = (String)resultMap.get("trade_state");
			if(tradeState.equals("SUCCESS")){
				//處理業務....
			}
		} catch (WrongHttpParameterException | IOException
				| InvalidHttpResponseException | EmptyDestinationException e) {
			e.printStackTrace();
		}

測試過程中有個問題,就是spbill_create_ip這個引數的問題,說是要用發起支付的使用者端IP,但是根據文件獲取了之後發現一直提示這個IP不對,然後寫死了113.87.162.45這個IP居然還能支付成功,搞不懂

2,微信內部H5支付

前面幾個條件參照上面寫的外部H5支付,

然後在商戶平臺裡的開發設定裡配置好公眾號支付的支付授權目錄

也是要先呼叫統一下單介面,這個步驟參照上面寫的,只有2個區別

1,trade_type這個引數的取值改為JSAPI

2,新增openId引數

openId的獲取自己看一下API,很簡單

微信內部H5支付與外部H5支付的最大區別在於微信外部H5支付是通過統一下單返回的mweb_url這個引數跳轉到微信支付頁面

而微信內部H5支付是把統一下單介面返回的prepay_id引數跟其他引數一起返回到前端頁面,再由前端頁面呼叫微信支付JS的API來跳轉

Map<String, String> premap = new HashMap<String, String>();
premap.put("appId", appId);
premap.put("timeStamp", timeStamp.toString());
premap.put("nonceStr", nonceStr);
premap.put("package", "prepay_id=" + prepayId);
premap.put("signType", "MD5");
premap.put("key", key);
				
sign = WeChatUtil.buildSign(premap, false, false);
log.info("生成的支付簽名:" + sign);
				
jsonObject.put("appId", appId);
jsonObject.put("timeStamp", timeStamp.toString());
jsonObject.put("nonceStr", nonceStr);
jsonObject.put("package", "prepay_id=" + prepayId);
jsonObject.put("signType", "MD5");
jsonObject.put("paySign", sign);
				

這裡要注意的是以下幾點:

1,又要重新獲取一次簽名sign,而且這裡的appId,這個i是大寫的,之前是小寫的

2,timeStamp引數要用字串,不然後面轉成JSON的時候會沒有雙引號,導致微信伺服器解析不到這個欄位

3,加密方式,有些人會出現支付簽名驗證失敗,是由於後臺的加密跟前端的加密不一致導致的,統一加密方式即可

前端程式碼:

1,先引入微信支付JS API

<script type="text/javascript" src="http://res.wx.qq.com/open/js/jweixin-1.0.0.js"></script>

2,呼叫JS觸發微信APP支付

WeixinJSBridge.invoke(
     'getBrandWCPayRequest', data, function(res){
          if(res.err_msg == "get_brand_wcpay_request:ok" ) {
               //doit 這裡處理支付成功後的邏輯,通常為頁面跳轉
               $("#returnpay").show();
          }else{
                alert('支付失敗,請聯絡管理員!res.err_msg:' + res.err_msg);
          }
     }
);

這裡的data是後臺封裝好的JSON物件,作為引數代入,res是微信返回的資料

驗證支付狀態參照上面寫的即可

支付寶手機網站支付:

還在研究中 ....