作的是微信H5支付,微信APP支付已經有了,H5支付尚未,須要注意的是:H5支付須要在微信支付商家後臺單獨開通。php
微信H5支付開發流程圖:java
紅色的就是咱們服務端須要作的,簡單來講的業務流程就是:node
到這裏微信H5支付的業務流程基本上算完成了。web
具體的代碼是:算法
// 須要的jar等,這裏爲了方便就把一些工具類的方法寫在一塊兒了,須要的jar在這裏,沒有的在下面具體的類上的註釋也有標出來 import java.io.IOException; import java.nio.charset.Charset; import java.util.HashMap; import java.util.Map; import org.apache.hc.client5.http.fluent.Request; import org.apache.hc.core5.http.entity.ContentType; import org.dom4j.DocumentException; import org.springframework.stereotype.Service; import com.alibaba.fastjson.JSON; /*建立微信的H5訂單信息*/ @Override public OrderCreateVO getWechatH5PayOrderInfo(int fee, String callback, Long uid, String out_trade_no) { // 請求和響應參數參考:https://pay.weixin.qq.com/wiki/doc/api/H5.php?chapter=9_20&index=1 HashMap<String, String> params = new HashMap<String, String>(); // 公衆帳號ID:微信分配的公衆帳號ID(企業號corpid即爲此appId) params.put("appid", WX_APP_ID); // 商戶號:微信支付分配的商戶號 params.put("mch_id",WX_MCH_ID); // 設備號:終端設備號(門店號或收銀設備ID),注意:PC網頁或公衆號內支付請傳"WEB" params.put("device_info", "WEB"); // 隨機字符串:隨機字符串,不長於32位。推薦隨機數生成算法(https://pay.weixin.qq.com/wiki/doc/api/H5.php?chapter=4_3) params.put("nonce_str", PayUtil.getRandomHex(32)); // 簽名類型:簽名類型,目前支持HMAC-SHA256和MD5,默認爲MD5 params.put("sign_type", "MD5"); // 商品描述:商品簡單描述,該字段須嚴格按照規範傳遞,具體請見參數規定(https://pay.weixin.qq.com/wiki/doc/api/H5.php?chapter=4_2) params.put("body", "XXX-支付"); // 商戶訂單號:商戶系統內部的訂單號,32個字符內、可包含字母, 其餘說明見商戶訂單號(https://pay.weixin.qq.com/wiki/doc/api/H5.php?chapter=4_2) params.put("out_trade_no", out_trade_no); // 總金額:訂單總金額,單位爲分,詳見支付金額(https://pay.weixin.qq.com/wiki/doc/api/H5.php?chapter=4_2) params.put("total_fee", fee + ""); // 終端IP:必須傳正確的用戶端IP,詳見獲取用戶ip指引(https://pay.weixin.qq.com/wiki/doc/api/H5.php?chapter=15_5) params.put("spbill_create_ip", PayUtil.getHostIp()); // 通知地址:接收微信支付異步通知回調地址,通知url必須爲直接可訪問的url,不能攜帶參數。 // 這裏的callback就是微信檢測到付款成功之後訪問你的系統後臺方法,就是你要改票的狀態或者推送通知什麼的業務,就寫到這個連接的方法裏面去,方法是本身系統的 params.put("notify_url", callback); // 交易類型:H5支付的交易類型爲MWEB params.put("trade_type", "MWEB"); // 簽名:簽名,詳見簽名生成算法(https://pay.weixin.qq.com/wiki/doc/api/H5.php?chapter=4_3) params.put("sign", PayUtil.getWeChatSign(params, WX_API_KEY)); // 參數XML String body = PayUtil.mapToXmlString(params); // 返回響應XML String response; try { response = Request.Post(WX_UNIFIED_ORDER_URL).bodyString(body, APPLICATION_XML).execute().returnContent() .asString(Charset.forName("UTF-8")); } catch (IOException e) { e.printStackTrace(); return null; } //解析相應信息 Map<String, String> xmlResponse; try { xmlResponse = PayUtil.xmlStringToMap(response); } catch (DocumentException e) { e.printStackTrace(); return null; } // 取得解析過的微信響應回執,這個就至關於微信用你的訂單,在他系統裏下了一個單,返回給你(預支付訂單),你再把訂單給前端H5頁面,頁面把這些參數發給微信。其中公衆號內支付是訪問接口發參數,H5支付是訪問mweb_url連接 if ("SUCCESS".equals(xmlResponse.get("return_code")) && "SUCCESS".equals(xmlResponse.get("result_code"))) { // 封裝H5頁面調用參數,參考文檔:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=7_7&index=6 Map<String, String> resultMap = new HashMap<String, String>(); // 回調地址:當微信支付成功後,微信會訪問回調地址,訪問咱們系統的方法,修改票的狀態等 String redirectUrl = "&redirect_url=" + callback; // 支付跳轉連接:mweb_url爲拉起微信支付收銀臺的中間頁面,可經過訪問該url來拉起微信客戶端,完成支付,mweb_url的有效期爲5分鐘。 resultMap.put("mwebUrl", xmlResponse.get("mweb_url") + redirectUrl); // 公衆帳號ID:調用接口提交的公衆帳號ID resultMap.put("appId", xmlResponse.get("appid")); // 時間戳:自1970年以來的秒數 resultMap.put("timeStamp", System.currentTimeMillis() / 1000 + ""); // 隨機字符串:微信返回的隨機字符串 resultMap.put("nonceStr", xmlResponse.get("nonce_str")); // 訂單詳情擴展字符串:統一下單接口返回的prepay_id參數值,提交格式如:prepay_id=*** resultMap.put("prepayIdPackage", "prepay_id=" + xmlResponse.get("prepay_id")); // 簽名方式:簽名類型,默認爲MD5,支持HMAC-SHA256和MD5。注意此處需與統一下單的簽名類型一致 resultMap.put("signType", "MD5"); // 簽名:微信返回的簽名,詳見簽名算法(https://pay.weixin.qq.com/wiki/doc/api/H5.php?chapter=4_3) // 這裏是給前端H5封裝的請求參數,不能用剛纔的請求參數了,須要用此次的請求Map再次簽名 resultMap.put("paySign", PayUtil.getWeChatSign(resultMap, WX_API_KEY)); OrderCreateVO createVO = new OrderCreateVO(); createVO.setOrderId(out_trade_no); createVO.setOrderInfo(resultMap); return createVO; } else { System.out.println("微信下單失敗..." + xmlResponse.get("return_code") + "; 返回信息: "+xmlResponse.get("return_msg")); return null; } } /** * nonce_str生成算法 * * @param len * @return */ public static String getRandomHex(int len) { if (len == 0) return ""; Random random = new Random(); char[] str = "0123456789abcdefABCDEF".toCharArray(); StringBuffer sb = new StringBuffer(); for (int i = 0; i < len; i++) { sb.append(str[random.nextInt(str.length)]); } return sb.toString(); } /** * spbill_create_ip:獲得本地機器的IP * @return */ public static String getHostIp(){ String ip = ""; try{ ip = InetAddress.getLocalHost().getHostAddress(); }catch(UnknownHostException e){ e.printStackTrace(); } return ip; } /** * 獲取微信支付的參數簽名 * 參考文檔:https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=4_3 * * @param map * @param apiKey * @return */ public static String getWeChatSign(Map<String, String> map, String apiKey) { // 先對參數Map進行ASCII字典排序 String authInfo = mapToSortString(map); // 在排序後的參數字符串後拼接API KEY String stringSignTemp = authInfo + "&key=" + apiKey; // 使用org.apache.commons.codec.digest.DigestUtils.md5Hex()進行MD5加密並大寫 String sign = DigestUtils.md5Hex(stringSignTemp).toUpperCase(); return sign; } /** * 對Map進行ASCII字典排序 * * @param map * @return */ public static String mapToSortString(Map<String, String> map) { Collection<String> keyset= map.keySet(); List<String> list=new ArrayList<String>(keyset); Collections.sort(list); //排序 StringBuffer sb = new StringBuffer(); for (String key : list) { sb.append(buildKeyValue(key, map.get(key), false)); sb.append("&"); } removeLastChar(sb); return sb.toString(); } /** * Map轉XML * * @param map * @return */ public static String mapToXmlString(Map<String, String> map) { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.append("<xml>"); for (Map.Entry<String, String> entry : map.entrySet()) { stringBuilder.append(String.format("<%s>%s</%s>", entry.getKey(), entry.getValue(), entry.getKey())); } stringBuilder.append("</xml>"); return stringBuilder.toString(); } /** * XML轉Map * * @param map * @return */ public static Map<String, String> xmlStringToMap(String xml) throws DocumentException { Map<String, String> stringMap = new HashMap<String, String>(); // org.dom4j.DocumentHelper Element element = DocumentHelper.parseText(xml).getRootElement(); @SuppressWarnings("unchecked") Iterator<Element> iterator = element.elementIterator(); while (iterator.hasNext()) { Element node = iterator.next(); stringMap.put(node.getName(), node.getTextTrim()); } return stringMap; }
而後前端H5頁面支付的問題,由於是測試環境,而微信H5支付須要域名和「商家後臺-產品中心-開發配置」裏的域名一致,而測試環境域名不一致,再因爲短期不能上線,之後上線了再加上。spring