微信JSAPI支付


微信JSAPI支付小結

 

    公司項目用到微信公眾號支付,所以,從看文檔,到完成,整整用了兩天,好多坑,有的坑爬出來,雖然能用了,但是還沒去深究原因,這里我先整理一下從頭到尾的流程。

 

    

 

    上面是官方給的時序圖,我也是跟着這個流程走的。

    這里我省略了從微信公眾號點擊自定義菜單獲取用戶openid的過程,這個打算另外記錄。

    另外的一些支付的先決條件,這里簡單說一下:(沒做過微信的其他支付方式,這里只是說微信公眾號的)

        1.  微信支付商戶號(下圖中被我打馬賽克的地方)

        

        2.  商戶支付密鑰

        3.  需要支付的微信公眾號要跟微信商戶綁定

      4.  微信公眾號的appId和密鑰

    

 

    有了這些以后,就可以開發了:

        1.  需要支付的頁面引入js:(這個官方有)        

<script src="http://res.wx.qq.com/open/js/jweixin-1.6.0.js"></script>

        2. 需要支付的頁面鑒權:

        進來該頁面的controller里面:

model.addAttribute("jsauthmap",wxOfficalJsApiService.getAuthMap(request));

  

public Map getAuthMap(HttpServletRequest request) {
        String url = pageUrlUtils.getPageUrl(request);
        return getAuthMap(url);
    }

//這個方法是獲取url的,在我的一個工具包中,我摘出來了:
public String getPageUrl(HttpServletRequest request){
String queryString = request.getQueryString();
if(!StringUtils.isEmpty(queryString) || "null".equals(queryString)){
queryString = "?"+queryString;
}else{
queryString = "";
}
String domain = request.getScheme()
+"://"+ microAttr.getDomain()
+ request.getRequestURI()
+queryString;
return domain;
}


public Map getAuthMap(String url){
log.info("獲取微信公眾號jsapi鑒權數據,,,,,,url:"+url);
   //獲取隨機字符串,這里網上搜索一大堆,我就不曬具體代碼了
String nonceStr = RandomStr.getRandom(7, RandomStr.TYPE.LETTER);
long timeStamp = System.currentTimeMillis() / 1000;
  //下面是去獲取jsapi_ticket
String ticket = wxOfficalJsApiService.getWxOfficalJsApiTicket();
String signature = null;
try {
signature = sign(ticket, nonceStr, timeStamp, url);
} catch (OApiException e) {
e.printStackTrace();
}
Map<String, Object> configValue = new HashMap<>();
configValue.put("jsticket", ticket);
configValue.put("signature", signature);
configValue.put("nonceStr", nonceStr);
configValue.put("timeStamp", timeStamp);
return configValue;
}


/**
* 獲取jsapi_ticket
* 這里用到了緩存,第一句是從緩存中取,如果沒有就使用accessToken去獲取
* 訪問微信服務區獲取jsapi_ticket我使用了restTemplate,只是發起遠程調用的一個工具,可以任意換
*/
public String getWxOfficalJsApiTicket(){
String ticket = cacheHandle.getStr(RedisKeys.REDISKEY_WXOFFICAL_JSAPI_TICKET);
if(StringUtils.isEmpty(ticket)){
//緩存中沒有,使用accessToken去微信服務器中獲取:
String wxOfficalAccessToken = moltechToken.getToken();
if(StringUtils.isEmpty(wxOfficalAccessToken)){
log.warning("獲取微信公眾號jsapiticket時,獲取微信公眾號accessToken失敗!");
return "";
}
String jsapiResultStr = restTemplate.getForObject("https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=" + wxOfficalAccessToken + "&type=jsapi", String.class);
log.info("獲取微信公眾號jsapiTicket返回的結果為:");
log.info(jsapiResultStr);
if(StringUtils.isEmpty(jsapiResultStr)){
log.warning("獲取微信公眾號的jsapiticket,返回值為空!");
return "";
}
JSONObject jsonObject = JSONObject.parseObject(jsapiResultStr);
if("ok".equals(jsonObject.getString("errmsg")) && StringUtils.isNotEmpty(jsonObject.getString("ticket"))){
log.info("訪問微信服務器獲取到的jsapi_ticket為:"+jsonObject.getString("ticket"));
ticket = jsonObject.getString("ticket");
cacheHandle.saveStr(RedisKeys.REDISKEY_WXOFFICAL_JSAPI_TICKET,7000,ticket);
}
}
return ticket;
}

        上面的代碼就是后台獲取前端頁面需要用到的鑒權數據的邏輯,獲取到以后存到model中,然后看前端頁面:

 

     

//頁面加載完成后執行
    window.onload = function(){

            console.log("微信公眾號,獲取到的JS-SDK鑒權數據:");
            var jsauthmap = [[${jsauthmap}]];
            wx.config({
                debug: true, // 開啟調試模式,調用的所有api的返回值會在客戶端alert出來,若要查看傳入的參數,可以在pc端打開,參數信息會通過log打出,僅在pc端時才會打印。
                appId: jsauthmap.appId, // 必填,公眾號的唯一標識
                timestamp: jsauthmap.timestamp, // 必填,生成簽名的時間戳
                nonceStr: jsauthmap.nonceStr, // 必填,生成簽名的隨機串
                signature: jsauthmap.signature,// 必填,簽名
                jsApiList: [
                    'chooseWXPay'
                ] // 必填,需要使用的JS接口列表
            });
            //微信公眾號登錄的,使用微信jsapi支付:
            //pay_btn是支付按鈕的id,這里使用jquery給它綁定點擊事件
            $("#pay-btn").on('click',function(){
                //支付1.去后端獲取支付信息     2,帶着支付信息調用微信統一支付接口
                //禁用支付,防止多次支付
                var theBtn = $("#pay-btn");
                theBtn.attr('disabled','disable');
                //money是要支付的金額,,payfor是自定義的支付用途
                var money = $("#cost_span").text().trim();
               wx.ready(function(){
                    // config信息驗證后會執行ready方法,所有接口調用都必須在config接口獲得結果之后,config是一個客戶端的異步操作,所以如果需要在頁面加載時就調用相關接口,則須把相關接口放在ready函數中調用來確保正確執行。對於用戶觸發時才調用的接口,則可以直接調用,不需要放在ready函數中。
                    /* 微信公眾號支付支付 */
                        $.get("/wxPay/webPay", {
                            totalFee: money,
                            payfor: '1'
                        }, function (res) {
                            if (res.code == 0) {
                                let data = $.parseJSON(res.data.jsonStr);
                                let orderid = res.data.outTradeNo;

                                if (typeof WeixinJSBridge == "undefined") {
                                    if (document.addEventListener) {
                                        document.addEventListener('WeixinJSBridgeReady',
                                            onBridgeReady(data,orderid), false);
                                    } else if (document.attachEvent) {
                                        document.attachEvent('WeixinJSBridgeReady',
                                            onBridgeReady(data,orderid));
                                        document.attachEvent('onWeixinJSBridgeReady',
                                            onBridgeReady(data,orderid));
                                    }
                                } else {
                                    onBridgeReady(data,orderid);
                                }
                            } else {
                                if (res.code == 2) {
                                    layer.alert(res.message);
                                } else {
                                    layer.msg("error:" + res.message, {
                                        shift: 6
                                    });
                                }
                            }
                        });
                    /* 微信公眾號支付支付 END */
                });
            })
        
    }   
function onBridgeReady(json,orderid) {
WeixinJSBridge.invoke('getBrandWCPayRequest', json, function (res) {
// 使用以上方式判斷前端返回,微信團隊鄭重提示:res.err_msg將在用戶支付成功后返回 ok,但並不保證它絕對可靠。
// alert(JSON.stringify(res));
if (res.err_msg == "get_brand_wcpay_request:ok") {
        //支付成功的邏輯:
// layer.msg("支付成功", {
// shift: 6
// });
//支付成功跳轉到認證頁面:
// setTimeout(function(){
// window.location.href = '/attr/zhanlve';
// },1000);
        //我自己寫的沒隔5秒去后端獲取支付狀態的邏輯,后面帶着支付成功后跳轉的路徑
getOrderStatus(orderid,5,'/auth/attr/zhanlve');
// self.location = "#(ctxPath)/success";

} else {
layer.msg("支付失敗", {
shift: 6
});
}
});
}


  

 

      /wxPay/webPay:

/     * 公眾號支付
*/ @RequestMapping(value = "/webPay", method = {RequestMethod.POST, RequestMethod.GET}) @ResponseBody public AjaxResult webPay(HttpServletRequest request, @RequestParam("totalFee") String totalFee,@RequestParam("payfor") String payfor,String purchaseId) { // openId,采用 網頁授權獲取 access_token API:SnsAccessTokenApi獲取
        log.info("/wxPay/webPay....totalFee:"+totalFee+",,,payfor:"+payfor+",,,purchaseId:"+purchaseId); // log.info("wxPayBean:"); // log.info(wxPayBean.toString());     //獲取用戶在微信公眾號中的openid,這個是我之前用戶通過微信公眾號的自定義菜單進來以后我存入session中的 String openId = (String) request.getSession().getAttribute("wxOpenId"); if (openId == null || StrUtil.isEmpty(openId)) { return new AjaxResult().addError("openId is null"); } if (StrUtil.isEmpty(totalFee)) { return new AjaxResult().addError("請輸入數字金額"); } 
     //微信支付,金額的單位是分,所以要乘以100 Integer totalFeeInt
= (int)(Double.valueOf(totalFee)*100); String ip = IpKit.getRealIp(request); if (StrUtil.isEmpty(ip)) { ip = "127.0.0.1"; }
     //我這里封裝的map是打算在微信支付的回調事件里面使用,用於業務,如果有需要自定義的東西跟隨支付的最后,可以這樣做 Map paraMap
= new HashMap(); paraMap.put("payFor", payfor); paraMap.put("supplierId",supplier.getPkSupplier()); if(payfor.equals(PayContants.ORDER_PAY_FOR_EXPERT_REVIEW_AND_CONTRACT_FEE)){ if(StringUtils.isBlank(purchaseId)){ return new AjaxResult().addError("支付合同費用時訂單id不能為空!"); } paraMap.put("purchaseId",purchaseId); } // WxPayApiConfig wxPayApiConfig = WxPayApiConfigKit.getWxPayApiConfig();      //獲取隨機字符串充當商戶單號 String outTradeNo = WxPayKit.generateStr(); UnifiedOrderModel build = UnifiedOrderModel .builder() .appid(wxPayBean.getAppId())            //微信公眾號的appid .mch_id(wxPayBean.getMchId())            //微信支付商戶號 .nonce_str(WxPayKit.generateStr())         //隨機字符串 .body(body)                       //商品名稱 .attach(JSONObject.toJSONString(paraMap))     //填充自定義參數的地方 .out_trade_no(outTradeNo)               //商戶單號, .total_fee(totalFeeInt + "")             //支付金額,這里一定要傳字符串,否則會報錯 .spbill_create_ip(ip)                 //這個ip我的理解是服務器的ip .notify_url("http://xxxx/wxPay/payNotify")  //支付結果異步回調接口地址 .trade_type(TradeType.JSAPI.getTradeType())    //微信JSAPI支付標識 .openid(openId)                     //用戶的微信公眾號的openid .build(); log.info(JSON.toJSONString(build)); /** * {"appid":"wx12f87136ac56ba6e", * "attach":"1", * "body":"戰略供應商服務費用", * "mch_id":"1489352152", * "nonce_str":"0a39fadd2abe4145b44ce6163abdd089", * "notify_url":"http://ding.starint.cn/wxPay/payNotify", * "openid":"o_VB40gpnCnTZJHE9R1rZDQj5Xto", * "out_trade_no":"b1f49c61c794470daf09de9d247a6f92", * "spbill_create_ip":"223.99.0.13", * "total_fee":"1", * "trade_type":"JSAPI"} */ Map<String, String> params = build.createSign(wxPayBean.getPartnerKey(), SignType.HMACSHA256); String xmlResult = WxPayApi.pushOrder(false, params); log.info("獲取預支付訂單信息:"); log.info(xmlResult); /** * <xml> * <return_code><![CDATA[SUCCESS]]></return_code> * <return_msg><![CDATA[OK]]></return_msg> * <appid><![CDATA[wx12f87136ac56ba6e]]></appid> * <mch_id><![CDATA[1489352152]]></mch_id> * <nonce_str><![CDATA[miebAaIw7PLx7ZV6]]></nonce_str> * <sign><![CDATA[075360B8CA6D2A8C378764041915DA6CD809558543CBDBF3CD4DC7CBF66408BF]]></sign> * <result_code><![CDATA[SUCCESS]]></result_code> * <prepay_id><![CDATA[wx1110261039192337a6b817c4ba1f4e0000]]></prepay_id> * <trade_type><![CDATA[JSAPI]]></trade_type> * </xml> */ Map<String, String> resultMap = WxPayKit.xmlToMap(xmlResult); String returnCode = resultMap.get("return_code"); String returnMsg = resultMap.get("return_msg"); if (!WxPayKit.codeIsOk(returnCode)) { return new AjaxResult().addError(returnMsg); } String resultCode = resultMap.get("result_code"); if (!WxPayKit.codeIsOk(resultCode)) { return new AjaxResult().addError(returnMsg); } // 以下字段在 return_code 和 result_code 都為 SUCCESS 的時候有返回 String prepayId = resultMap.get("prepay_id"); Map<String, String> packageParams = WxPayKit.prepayIdCreateSign(prepayId, wxPayBean.getAppId(), wxPayBean.getPartnerKey(), SignType.HMACSHA256); String jsonStr = JSON.toJSONString(packageParams); log.info("生成的前台頁面需要傳遞的參數及sign為:"); log.info(jsonStr); Map resultMap2 = new HashMap(); resultMap2.put("jsonStr",jsonStr); resultMap2.put("outTradeNo",outTradeNo); /** * { * "timeStamp":"1599791528", * "signType":"HMAC-SHA256", * "package":"prepay_id=wx1110320782721960a9b0c3912d67670000", * "paySign":"3F9C32F7DADE0F65C4053CC1CE0CF6FBABC3549FF745BBA4AE45853331F80B98", * "nonceStr":"1599791528139", * "appId":"wx12f87136ac56ba6e" * } */ return new AjaxResult().success(resultMap2); }

  回調時間略

  里面的一些工具類請看這個開源項目:https://gitee.com/javen205/IJPay

  里面有一個springboot的Demo,下下來比較着做就OK;

  在此特別感謝作者開源,方便他人。

看完開源框架里面的內容,然后在跟着我的代碼比較一下,微信支付基本就沒啥問題了。
      

  

 

        2.  去后台


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM