微信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