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是微信返回的資料
驗證支付狀態參照上面寫的即可
支付寶手機網站支付:
還在研究中 ....