天天看點

微信支付 - JSAPI總結

背景:最近做項目,甲方提出一個需要要求在手機端直接微信注冊成功後,直接登入并發起微信支付。再三思考後,才決定使用jsapi微信支付。

微信支付官方文檔:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=7_1

首先微信普通商戶版有NATIVE支付、JSAPI支付、APP支付、H5支付、付款碼支付、小程式支付;其中我認為作為web開發的最常用的是native、jsapi、h5支付了。但是jsapi支付在官方微信支付文檔中,留下了很多坑,導緻在使用過程中無比麻煩,下面具體來說說,希望能對各位看客有點幫助。

1.微信支付最麻煩的便是公衆平台和商戶平台的配置,将公衆号APPID、APPSECRET,商戶号appid、秘鑰key配置好。

商戶平台:(https://pay.weixin.qq.com/)

1)微信商戶平台->産品中心->我的産品,檢視你要的支付類型是否開通,未開通,則申請開通,按照開通流程走,主要是申請安裝證書即可。

微信支付 - JSAPI總結

2)微信商戶平台->産品中心->開發配置,檢視支付配置是否已經配置成功。native支付則配置掃碼支付回調位址,jsapi支付則配置公衆号支付授權目錄,該目錄最多可添加5個,如發起支付頁面為:http://baidu.com/index.html,則目錄配置為:http://baidu.com/;如發起支付頁面為:http://baidu.com/wxpay/index.html,則目錄配置為:http://baidu.com/wxpay/;(這一步很關鍵,不然會在發起支付時提示注冊url無效)

微信支付 - JSAPI總結

3)微信商戶平台->賬戶中心->賬戶設定->API安全,檢視是否已經設定了商戶密鑰key,此密鑰在後面生成簽名sign中特别重要,必須設定,設定成功後記住密鑰;

微信支付 - JSAPI總結

以上是商戶平台的配置;

微信公衆平台:(https://mp.weixin.qq.com/)

1)微信公衆平台->開發->基本配置,檢視開發者密碼AppSecret是否已經設定;

微信支付 - JSAPI總結

2)微信公衆平台->設定->公衆号設定,檢視網頁回調位址是否已經配置好,在這裡我将所有的域名配置都配置好了。需要将MP_verify_MHYOHtHKmJzSkCj0.txt檔案放置到項目的根目錄下,隻要通路域名後可以通路到就可以,如配置域名:baidu.com,則通路http://baidu.com/MP_verify_MHYOHtHKmJzSkCj0.txt時通路得到就表示配置成功。

微信支付 - JSAPI總結
微信支付 - JSAPI總結

以上是公衆平台需要的配置。

2.配置完後,檢視商戶平台的jsapi微信支付開發文檔:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=7_1,在微信浏覽器内發起支付之前,必須先獲得預支付訂單号prepay_id,也就是這個訂單号,需要大費周折的去拼接各種資料;

1)由于擷取prepay_id的必填參數中有openid,是以先擷取openid,通過公衆平台網頁授權來擷取。

微信公衆平台->開發->接口權限->網頁服務->網頁授權(網頁授權擷取使用者基本資訊 https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140842)

第一步:使用者同意授權,擷取code:

https://open.weixin.qq.com/connect/oauth2/authorize?appid=APPID&redirect_uri=REDIRECT_URI&response_type=code&scope=SCOPE&state=STATE#wechat_redirect

參數說明:APPID:微信公衆平台AppId;

REDIRECT_URI:回調位址,在配置時需要配置域名,此處的回調位址域名必須與配置的一緻否則無效。

SCOPE:預設擁有scope參數中的snsapi_base和snsapi_userinfo,此處是jsapi支付,必須使用snsapi_userinfo,否則會出現“此公衆号并沒有這些scope的權限,錯誤碼:10005”的提示;

STATE:随機參數,可以用來區分或者攜帶其他參數到回調位址中;

傳回值:code作為換取access_token的臨時票據和state随機參數。

第二步:通過code換取網頁授權access_token:

https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code

參數說明:APPID:微信公衆平台AppId;

SECRET:微信公衆号密鑰,需要配置;

CODE:第一步獲得的code參數;

傳回值:access_token和openid,此處隻寫明這兩個較為重要的參數。

到目前為止便得到了openid。可以進行統一下單了。

2).檢視API清單->統一下單(調用該接口是為了在微信支付服務背景生成預支付交易單)

接口連結:https://api.mch.weixin.qq.com/pay/unifiedorder

請求參數:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_1 檢視官方文檔,此處重點點名:openid,此次為JSAPI,是以必填;sign簽名是整個請求參數中最難生成的。

微信支付 - JSAPI總結
微信支付 - JSAPI總結

當傳回結果return_code 和result_code都為SUCCESS,可以獲得prepay_id.

3)得到預訂單号後,就可以在微信内置浏覽器中發起支付。将統一下單接口傳回的資料中拿到appid、nonce_str、prepay_id,并結合signType、timeStamp、key(商戶号)通過MD5加密傳回paySign支付簽名,将這些資料一并傳回前端頁面調用微信内置對象WeixinJSBridge,發起支付。(微信内h5調起支付連結:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=7_7&index=6 )

整個支付就到此結束了。

在這個過程中,我遇到的最慘痛的難點就是支付簽名paySign的生成,因為在微信統一下單接口傳回的資料中還包含了sign簽名的傳回,我一直以為可以使用這個作為支付簽名。然而在網上找了很多大佬的總結後,都說統一下單接口傳回的sign的簽名加密方式不是MD5,然而支付簽名必須是MD5加密而造成的。但是我覺得不是,因為我已經明确在下單接口中的參數signType寫明就是MD5了,是以表示下單接口傳回的sign并不能作為微信發起支付的簽名paySign。必須自己重新根據傳回的prepay_id生成新的支付簽名。

代碼的實作方式如下:

(後端為java,前端使用vue)

  1. controller層的代碼實作:
/**
     * 第三方發起微信授權登入請求,微信使用者允許授權第三方應用後,
     * 微信會拉起應用或重定向到第三方網站,并且帶上授權臨時票據code參數
     * appid: 微信公衆号唯一辨別id
     * redirect_uri: 回調位址,此處回調位址為/WXRegisterPay
     * 該接口傳回微信請求url
     *
     * @title 微信授權登入請求臨時票據code
     * @resp code_url第三方請求臨時票據code的url|String|必填
     * @return 第三方請求臨時票據code的位址
     * @Author chenxm
     */
    @RequestMapping(value = "getWXCode",method = RequestMethod.GET)
    @OpLog(value = "擷取code",module = "微信支付")
    @TokenIgnore
    BaseResult getWXCode(){
        Map<String,Object> map = new HashMap<String,Object>();
        // 公衆平台
        String url="https://open.weixin.qq.com/connect/oauth2/authorize?appid=" + PropertyUtil.WX_MP_APP_ID +
                "&redirect_uri=" + URL.encode(PropertyUtil.WX_REGISTERPAY) +
                "&response_type=code" +
                "&scope=snsapi_userinfo" +
                "&state=" + MREGISTER +
                "#wechat_redirect";
        map.put("code_url",url);
        return BaseResult.succResult(map);
    }
           
/**
     * 擷取code後,微信平台發起的回調接口
     * @return
     */
    @RequestMapping(value = "WXRegisterPay", method = RequestMethod.GET)
    @OpLog(value = "手機微信注冊支付", module = "Vip子產品")
    BaseResult WXRegisterPay(@LoginUser Member member, String code, String state, HttpServletRequest request){
        // 微信支付參數
        Map<String,Object> pay = new HashMap<>();
        // 支付簽名
        StringBuilder paySign = new StringBuilder();
        WebChatDto webChatAccess = weixinPayService.getAccessToken(code, 2);
        //擷取目前的ip位址
        String ip = WxPayToolUtil.getRemoteHost(request);
        try {
            Map payMap = weixinPayService.weixinRegPay(member.getId(),ip,webChatAccess.getOpenId());
            // 拼接微信支付參數
            pay.put("appId",payMap.get("appid"));
            pay.put("timeStamp",Long.toString(System.currentTimeMillis() / 1000));
            pay.put("nonceStr",payMap.get("nonce_str"));
            pay.put("signType",PropertyUtil.WX_SIGN_TYPE);
            pay.put("package","prepay_id="+payMap.get("prepay_id"));
            // 生成支付簽名
            paySign.append("appId=").append(pay.get("appId"));
            paySign.append("&nonceStr=").append(pay.get("nonceStr"));
            paySign.append("&package=").append(pay.get("package"));
            paySign.append("&signType=").append(pay.get("signType"));
            paySign.append("&timeStamp=").append(pay.get("timeStamp"));
            paySign.append("&key=").append(PropertyUtil.WX_API_KEY);
            pay.put("paySign", MD5Util.MD5Encode(paySign.toString(), "UTF-8").toUpperCase());

            logger.info("URL,{}",payMap);
            logger.info("paySign,{}",pay.get("paySign"));
        } catch (Exception e) {
            logger.info("URL,{}",e.getMessage());
        }
        return BaseResult.succResult(pay);
    }
           
/**
     * 發起支付後,微信平台發起的回調方法
     *
     * @title 微信回調方法
     */
    @RequestMapping("weixinNotify")
    @OpLog(value = "微信平台發起的回調方法", module = MODUEL)
    @TokenIgnore
   void weixinNotify(HttpServletRequest request, HttpServletResponse response) throws JDOMException, Exception {
        //擷取輸入流
        InputStream inputStream = request.getInputStream();
        StringBuffer sb = new StringBuffer();
        String s = null;
        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<>();
        m = XMLUtil4jdom.doXMLParse(sb.toString());
        //過濾空 設定 TreeMap
        SortedMap<Object, Object> packageParams = new TreeMap<>();
        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);
        }
        //判斷簽名是否正确
        if (WxPayToolUtil.isTenpaySign("UTF-8", packageParams, PropertyUtil.WX_API_KEY)) {
            String resXml = "";
            if ("SUCCESS".equals((String) packageParams.get("result_code"))) {
                //商戶訂單号
                String out_trade_no = (String) packageParams.get("out_trade_no");
                //微信訂單号
                String transaction_id = (String) packageParams.get("transaction_id");
                //現金支付金額
                float price = Float.valueOf((String) packageParams.get("cash_fee"));
                //擷取微信查詢訂單傳回的資料
                String str = weixinPayService.weixinOrderquery(transaction_id);
                logger.info(str);
                //通知微信.異步确認成功.必寫.不然會一直通知背景.八次之後就認為交易失敗了.
                resXml = "<xml>" + "<return_code><![CDATA[SUCCESS]]></return_code>"
                        + "<return_msg><![CDATA[OK]]></return_msg>" + "</xml> ";
                logger.info("支付成功!");
            } else {
                resXml = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>"
                        + "<return_msg><![CDATA[封包為空]]></return_msg>" + "</xml> ";
                logger.info("支付失敗!");
            }
            BufferedOutputStream out = null;
            try {
                out = new BufferedOutputStream(response.getOutputStream());
                out.write(resXml.getBytes());
            }catch (Exception e){
                logger.info("微信平台發起的回調方法,{}", e.getMessage());
            }finally {
                if (null != out){
                    out.flush();
                    out.close();
                }
            }
        } else {
            logger.info("通知簽名驗證失敗");
        }
    }
           
  1. service層的代碼實作:

    (1)接口:

/**
     * 請求擷取access_token
     * @param code
     * @return
     */
    WebChatDto getAccessToken( String code, Integer type);
/**
     * 微信jsapi支付
     * @return
     * @throws Exception
     */
    Map weixinRegPay(String userId,String ip, String openid) throws Exception;
    
    /**
     * 微信查詢訂單
     * @param transaction_id 微信訂單号
     * @return
     * @throws Exception
     */
    String weixinOrderquery(String transaction_id) throws Exception;
           

(2)實作類:

/*擷取access_token的url*/
    public static final String AccessToken_url = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=%s&secret=%s&code=%s&grant_type=authorization_code&type";

    @Override
    public WebChatDto getAccessToken(String code, Integer type) {
        // 請求擷取access_token
        // 開放平台
        String url = String.format(AccessToken_url,PropertyUtil.WX_OPEN_APPID,PropertyUtil.WX_OPEN_APPSECRET,code);
        if(type == 2){
            // 公衆平台
            url = String.format(AccessToken_url,PropertyUtil.WX_MP_APP_ID,PropertyUtil.WX_MP_APPSECRET,code);
        }
        // 擷取請求接口傳回的資訊
        JSONObject jsonObject = null;
        try {
            jsonObject = WeChatUtil.doGetJson(url);
        } catch (IOException e) {
            LOGGER.info("擷取TOKEN,{}",e.getMessage());
        }
        // 微信使用者唯一辨別id
        String openId = jsonObject.getString("openid");
        // 許可token
        String accessToken = jsonObject.getString("access_token");
        WebChatDto webChat = new WebChatDto();
        if(EmptyUtils.isNotEmpty(openId) && EmptyUtils.isNotEmpty(accessToken)){
            webChat.setAccessToken(accessToken);
            webChat.setOpenId(openId);
        }else{
            throw new ValidateException("微信授權登入失敗");
        }
        return webChat;
    }
    
@Override
 public Map weixinRegPay(String userId,String spbill_create_ip, String openid) throws Exception {
        //有序的Map
        SortedMap<Object, Object> params = WxPayToolUtil.getPayParams(userId, openid, spbill_create_ip, PropertyUtil.WX_trade_type_JSAPI);
        //格式化成XML形式,微信通信是借助XML
        String requestXML = WxPayToolUtil.getRequestXml(params);
        //得到微信傳回的資料
        String resXml = HttpUtil.postData(PropertyUtil.WX_UFDODER_URL, requestXML);
        Map map = XMLUtil4jdom.doXMLParse(resXml);
        return map;
    }

    @Override
    public String weixinOrderquery(String transaction_id) throws Exception {
        //有序的Map
        SortedMap<Object, Object> packageParams = new TreeMap<Object, Object>();
        //公衆賬号ID
        packageParams.put("appid", PropertyUtil.WX_MP_APP_ID);
        //商戶号
        packageParams.put("mch_id", PropertyUtil.WX_MCH_ID);
        //微信訂單号
        packageParams.put("transaction_id", transaction_id);
        //擷取目前日期和時間
        String currTime = WxPayToolUtil.getCurrTime();
        //獲得目前時間
        String strTime = currTime.substring(8, currTime.length());
        //取出一個指定長度大小的随機正整數.
        String strRandom = WxPayToolUtil.buildRandom(4) + "";
        //随機字元串,長度要求在32位以内。
        String nonce_str = strTime + strRandom;
        //随機字元串
        packageParams.put("nonce_str", nonce_str);
        //簽名類型
        packageParams.put("sign_type", PropertyUtil.WX_SIGN_TYPE);
        //生成簽名
        String sign = WxPayToolUtil.createSign("UTF-8", packageParams, PropertyUtil.WX_API_KEY);
        packageParams.put("sign", sign);
        //格式化成XML形式,微信通信是借助XML
        String requestXML = WxPayToolUtil.getRequestXml(packageParams);
        //得到微信傳回的資料
        String resXml = HttpUtil.postData(PropertyUtil.WX_ORDERQUERY_URL, requestXML);
        return resXml;
    }
           
  1. 輔助工具類utils的代碼實作:
/**
 * @desc 微信支付相關的工具類
 **/
public class WxPayToolUtil implements Serializable {

    private static final Logger logger = LoggerFactory.getLogger(WxPayToolUtil.class);
    /**
     * 是否簽名正确,規則是:按參數名稱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();

        return tenpaySign.equals(mysign);
    }

    /**
     * @Description:sign簽名
     * @param characterEncoding 編碼格式
     * @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);
        logger.info(sb.toString());
        String sign = MD5Util.MD5Encode(sb.toString(), characterEncoding).toUpperCase();
        return sign;
    }

    /**
     * @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;
    }

    /**
     * 目前時間多加多少毫米 yyyyMMddHHmmss
     *
     * @return String
     */
    public static String getAddCurrTime(Integer s) {
        Date now = new Date(System.currentTimeMillis() + s);
        SimpleDateFormat outFormat = new SimpleDateFormat("yyyyMMddHHmmss");
        String str = outFormat.format(now);
        return str;
    }
    
    /**
     * 擷取目前ip
     * @param request
     * @return
     */
    public static String getRemoteHost(HttpServletRequest request){
        String ip = request.getHeader("x-forwarded-for");
        if(ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)){
            ip = request.getHeader("Proxy-Client-IP");
        }
        if(ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)){
            ip = request.getHeader("WL-Proxy-Client-IP");
        }
        if(ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)){
            ip = request.getRemoteAddr();
        }
        return ip.equals("0:0:0:0:0:0:0:1")?"127.0.0.1":ip;
    }

    /**
     * 擷取微信支付參數
     */
    public static SortedMap<Object,Object> getPayParams(String userId,String openid, String spbill_create_ip,String trade_type){
        //擷取目前日期和時間
        String currTime = getCurrTime();
        //獲得目前時間
        String strTime = currTime.substring(8, currTime.length());
        //取出一個指定長度大小的随機正整數.
        String strRandom = buildRandom(4) + "";
        //随機字元串,長度要求在32位以内。
        String nonce_str = strTime + strRandom;
        //有序的Map
        SortedMap<Object, Object> packageParams = new TreeMap<Object, Object>();
        //公衆賬号ID
        packageParams.put("appid", PropertyUtil.WX_MP_APP_ID);
        //商戶号
        packageParams.put("mch_id", PropertyUtil.WX_MCH_ID);
       //使用者辨別
        packageParams.put("openid", openid);
        //随機字元串
        packageParams.put("nonce_str", nonce_str);
        //商品描述
        packageParams.put("body", "VIP會員" );
        //價格 機關為分
        packageParams.put("total_fee",“1”);
        //終端IP
        packageParams.put("spbill_create_ip", spbill_create_ip);
        //标價币種 CNY:人民币
        packageParams.put("fee_type", "CNY");
        //通知位址(回調位址)此處為/weixinNotify
        packageParams.put("notify_url", PropertyUtil.WX_NOTIFY_URL);
        //交易類型 JSAPI支付、NATIVE支付、APP支付
        packageParams.put("trade_type", trade_type);
        //簽名類型
        packageParams.put("sign_type", PropertyUtil.WX_SIGN_TYPE);
        //交易結束時間 time_expire (格式為yyyyMMddHHmmss,最短失效時間間隔必須大于5分鐘)
        packageParams.put("time_expire", getAddCurrTime(120000));
        //附加資訊
        packageParams.put("attach", userId);

        //生成簽名
        String sign = createSign("UTF-8", packageParams, PropertyUtil.WX_API_KEY);
        packageParams.put("sign", sign);
        return packageParams;
    }

}
           

MD5加密,微信支付的加密方式還有HMAC-SHA256;

/**
 * MD5加密工具類
 **/
public class MD5Util implements Serializable {
    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" };

}
           

http請求工具;

/**
 * 微信http工具類
 */
public class WeChatUtil {
    public static JSONObject doGetJson(String url) throws IOException {
        JSONObject jsonObject = null;
        DefaultHttpClient defaultHttpClient = new DefaultHttpClient();
        HttpGet httpGet = new HttpGet(url);
        HttpResponse httpResponse = defaultHttpClient.execute(httpGet);
        HttpEntity httpEntity = httpResponse.getEntity();
        if(httpEntity != null){
            String result = EntityUtils.toString(httpEntity,"UTF-8");
            jsonObject = JSONObject.parseObject(result);
        }
        httpGet.releaseConnection();
        return jsonObject;
    }
}

/**
 * http工具類,負責發起post請求并擷取的傳回
 */
public class HttpUtil implements Serializable{

    //url連接配接時間
    private final static int CONNECT_TIMEOUT = 5000;
    //預設編碼
    private final static String DEFAULT_ENCODING = "UTF-8";

    /**
     * 發起post請求微信支付并擷取的傳回
     *
     * @param urlStr 微信統一下單位址
     * @param data  微信支付所提供的資訊
     * @return 傳回微信傳回的資料
     */
    public static String postData(String urlStr, String data){
        BufferedReader reader = null;
        try {
            URL url = new URL(urlStr);
            //打開連接配接
            URLConnection conn = url.openConnection();
            //URL 連接配接可用于輸入和/或輸出。如果打算使用 URL 連接配接進行輸入,則将 DoInput 标志設定為 true;
            // 如果不打算使用,則設定為 false。預設值為 true。
            conn.setDoOutput(true);
            //設定連接配接時間
            conn.setConnectTimeout(CONNECT_TIMEOUT);
            //設定讀取時間
            conn.setReadTimeout(CONNECT_TIMEOUT);
            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) {
            e.getMessage();
        } finally {
            try {
                if (reader != null)  {
                    reader.close();
                }
            } catch (IOException e) {
            }
        }
        return null;
    }
}

           

解析xml;

public class XMLUtil4jdom implements Serializable{

    /** 
     * 解析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<String, String> m = new HashMap<String, String>(); 
        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 = XMLUtil4jdom.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(XMLUtil4jdom.getChildrenText(list));  
                }  
                sb.append(value);  
                sb.append("</" + name + ">");  
            }  
        }  
          
        return sb.toString();  
    }  

}

           
  1. 前端vue代碼實作:

    mtranster.html:從注冊頁面注冊登入成功後跳轉過來,判斷是否有code(因為是擷取code的回調位址),沒有便去請求,有則直接發起支付;

<!DOCTYPE html>
<html >
<head>
	<meta charset="UTF-8">
	<link rel="stylesheet" type="text/css" href="//unpkg.com/[email protected]/lib/theme-chalk/index.css" target="_blank" rel="external nofollow" />
	<link rel="icon" href="static/images/common/icon.gif" target="_blank" rel="external nofollow"  type="image/x-icon">
	<title>微信支付</title>
</head>
<body style="zoom: 200%;">
<div id="common">
	<div v-if="paying">
		<h1>正在進行微信支付...</h1>
		<small>請不要關閉</small>
	</div>
	<div v-if="!paying">
		<h1>{{payStr}}</h1>
	</div>
</div>
<script type="text/javascript" src="static/js/polyfill.min.js" charset="utf-8"></script>
<script type="text/javascript" src="static/js/vue.js"></script>
<script type="text/javascript" src="static/js/element-ui.js"></script>
<script type="text/javascript" src="static/js/jquery-2.min.js" charset="utf-8"></script>
<script type="text/javascript" src="http://res.wx.qq.com/open/js/jweixin-1.0.0.js" charset="utf-8"></script>
<script type="text/javascript" src="static/js/time.js"></script>
<script type="text/javascript" src="static/js/cookie.js"></script>
<script type="text/javascript" src="static/js/Api.js"></script>
<script>
	var vm = new Vue({
		el : '#common',
		data :function() {
			return{
			    codeUrl: "",
                webChat : {
                    code : "",
                    state : ""
                },
				payurl:"",
                loading:"",
                paying:true,// 支付中
                payStr:"" // 支付結束
			}
		},
		methods : {
            WXRegisterPay : function(code, state) {
                var self = this;
                self.loading = this.$loading({
                    lock: true,
                    text: loadingText,
                    spinner: 'el-icon-loading',
                    background: 'rgba(0, 0, 0, 0.7)'
                });
                APIS.get(APIS.API_WXRegisterPay, {
                    "code" : code,
                    "state" : state
                }).then(function(res) {
                    var pay = res.data.data;
                    if (typeof WeixinJSBridge == "undefined"){
                        if( document.addEventListener){
                            document.addEventListener('WeixinJSBridgeReady', self.onBridgeReady(pay), false);
                        }else if (document.attachEvent){
                            document.attachEvent('WeixinJSBridgeReady', self.onBridgeReady(pay));
                            document.attachEvent('onWeixinJSBridgeReady', self.onBridgeReady(pay));
                        }
                    }else{
                        self.onBridgeReady(pay);
                    }
                    self.onBridgeReady(pay);
                })
			},
            onBridgeReady:function (pay){
                var self = this;
				WeixinJSBridge.invoke(
					'getBrandWCPayRequest', {
						"appId":pay.appId,     //公衆号名稱,由商戶傳入
                        "timeStamp":pay.timeStamp,  //時間戳
                        "nonceStr":pay.nonceStr, //随機串
                        "package":pay.package,
                        "signType":pay.signType,  //微信簽名方式
                        "paySign":pay.paySign //微信簽名
                    },function(res){
                        self.paying = false;
                        self.loading.close();
						if(res.err_msg == "get_brand_wcpay_request:ok" ){
							self.payStr = "支付成功!請到PC端檢視詳情。";
                            self.$confirm('支付成功!請到PC端檢視詳情。', '提示', {
                                confirmButtonText: '确定',
                                showClose:false,
                                showCancelButton:false,
                                closeOnClickModal:false,
                                type: 'success'
                            }).then(function(){
                                location.href = "payNotify.html?pay=ok";
							})
						}else{
                            if(res.err_msg == "get_brand_wcpay_request:fail"){
                                self.payStr = "支付失敗!"
                                var url = "payNotify.html?pay=fail"; // 支付失敗
                            }else{
                                self.payStr = "支付已取消!"
                                var url = "payNotify.html?pay=cancel"; // 取消支付
                            }
                            self.$confirm(JSON.stringify(res.err_desc), '提示', {
                                confirmButtonText: '确定',
                                showClose:false,
                                showCancelButton:false,
                                closeOnClickModal:false,
                                type: 'error'
                            }).then(function(){
								location.href = url;
                            })
						}
					});
			}
		},
		created : function() {},
        mounted() {
			var self = this;
            var params = GetRequest();
            if (null != params.code && undefined != params.code && '' != params.code) {
                this.webChat.code = params.code;
                this.webChat.state = params.state;
                if (this.webChat.code !== "" && this.webChat.state !== "") {
					// 擷取openid,直接跳轉支付
					self.WXRegisterPay(this.webChat.code,this.webChat.state);
                }
            }else{
                // 擷取code
                APIS.get(APIS.API_getWXCode).then(function (res) {
                    self.codeUrl = res.data.data.code_url;
                    location.href = self.codeUrl;
                })
            }
        }
	})
</script>
</body>
</html>
           

微信jsapi支付的流程大概就是這樣,結合自己項目的業務邏輯實作。

以上便是我在做微信支付過程中的總結,若有描述不當的請評論提出哦!

繼續閱讀