zoukankan      html  css  js  c++  java
  • 微信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.  去后台

  • 相关阅读:
    Python教程:从零到大师
    Hive 安装 & Mysql 安装
    Hive基本原理及配置Mysql作为Hive的默认数据库
    分布式存储系统-HDFS
    centos 6.4-linux环境配置,安装hadoop-1.1.2(hadoop伪分布环境配置)
    VirtualBox安装Centos出现E_FAIL (0x80004005)的解决方法
    Hadoop 中HDFS、MapReduce体系结构
    探索性测试及基本用例
    软件测试相关术语(测试策略 && 测试方案 ....)
    高效学习的疑问与思路[软技能]
  • 原文地址:https://www.cnblogs.com/know-more/p/13654024.html
Copyright © 2011-2022 走看看