微信公众号支付

时间:2022-09-24 17:39:10

前提条件:

1,得到用户的openid,没获取到的朋友可以看我的上一篇文章:https://blog.csdn.net/qq_24800377/article/details/53437040

2,微信支付的商户号,其中公众号支付功能必须开通。

步骤:

第一步,获取 prepay_id   ,生成统一订单,它又叫预支付id。

第二步,在需要支付的页面,调用微信的jssdk,也就是网页中的javascript,微信有代码,调起微信支付页面。(注:prepay_id为该步骤调用微信js的参数  )

第三步:在微信提供的javascript代码中填写支付成功页面的url,搞定。

看起来就三步,十分简单,但微信支付能这么简单嘛?当然不可能,我做的时候最麻烦的地方就是第一步和第二步中,微信需要的参数,各种乱七八糟的,还有签名什么的,还有微信后台需要配置的一些信息…

第一步详解:

逻辑:访问一个网址,按微信要求填写参数,访问成功后,微信返回一段xml数据,其中就包括  prepay_id  数据,也是我下一步需要的。    

网址: https://api.mch.weixin.qq.com/pay/unifiedorder       

参数:一段xml数据

生成参数xml的代码:

public static String createXml(WxPayModel wxPayModel) {
    String xml = "<xml>" + "<appid>APPID</appid>" + "<body>BODY</body>" + "<device_info>DEVICEINFO</device_info>"  + "<mch_id>MCHID</mch_id>" + "<nonce_str>NONCESTR</nonce_str>" + "<notify_url>NOTIFYURL</notify_url>"  + "<openid>OPENID</openid>" + "<out_trade_no>OUTTRADENO</out_trade_no>" + "<sign>SIGN</sign>"  + "<total_fee>TOTALFEE</total_fee>" + "<trade_type>TRADETYPE</trade_type>" + "</xml>";

    xml = xml.replace("APPID", WxPayConstant.appid);
    xml = xml.replace("BODY", wxPayModel.getBody());
    xml = xml.replace("DEVICEINFO", WxPayConstant.device_info);
    xml = xml.replace("MCHID", WxPayConstant.mch_id);
    xml = xml.replace("NONCESTR", wxPayModel.getNonceStr());
    xml = xml.replace("NOTIFYURL", WxPayConstant.notify_url);
    xml = xml.replace("OPENID", wxPayModel.getOpenid());
    xml = xml.replace("OUTTRADENO", wxPayModel.getOutTradeNo());
    xml = xml.replace("SIGN", wxPayModel.getSign());
    xml = xml.replace("TOTALFEE", wxPayModel.getTotalFee());
    xml = xml.replace("TRADETYPE", WxPayConstant.trade_type);

    return xml;
}

生成xml所需参数与获取方法:

appid ==应用ID==登陆微信公众号后台-开发-基本配置   查看

body==商品描述==商品或支付单简要描述  随便写,测试时最好上英文,防止编码错误

device_info==设备号==终端设备号(门店号或收银设备ID),注意:PC网页或公众号内支付请传"WEB"

mch_id == 微信支付商户号==登陆微信支付后台,即可看到

nonce_str==随机字符串==随机字符串 ,下面有工具方法

notify_url==通知地址==接收微信支付异步通知回调地址,先随便起一个

openid==用户标识,前提条件已经获取到了

out_trade_no==商户订单号==商户系统内部的订单号,32个字符内、可包含字母,下方有工具方法

sign==签名==官方给的签名算法 ,下方有工具方法,注:生成签名的参数要按照工具方法中的参数写,它与生成的xml是配套的,如果不一致会一直报签名失败,内部的参数可以自定义,但需要与xml同步修改,不建议改。

trade_type==交易类型==JSAPI 写死

total_fee==总金额==订单总金额,单位为分,测试给1即可,1分钱

注:生成签名sign的时候,需要值key,它是微信商户平台(pay.weixin.qq.com)-->账户设置-->API安全-->密钥设置,自定义生成,用MD5加密成32位的字符串

工具方法:

/**  * 生成随机数  *  * @return  */ public static String getNonceStr() {
    Random random = new Random();
    return MD5Util.MD5Encode(String.valueOf(random.nextInt(10000)), "UTF-8");
}
/**  * 生成订单id  *  * @return  */ public static String generateUUID() {
    return IdGen.uuid().toString().replaceAll("-", "").substring(0, 32);
}
/**  * 生成签名  * @param wxPayModel  * @return  */  public static String createSign(WxPayModel wxPayModel) {

        // 微信api提供的参数  String appid = WxPayConstant.appid;
        String mch_id = WxPayConstant.mch_id;
        String device_info = WxPayConstant.device_info;
        String nonce_str = wxPayModel.getNonceStr();
        String notify_url = WxPayConstant.notify_url;

// System.out.println("++++++++++++++++++++++++++++++++++++++++"); // // System.out.println("参与签名appid:" + appid); // System.out.println("参与签名mch_id:" + mch_id); // System.out.println("参与签名nonce_str:" + nonce_str); // System.out.println("参与签名body:" + wxPayModel.getBody()); // System.out.println("参与签名out_trade_no:" + wxPayModel.getOutTradeNo()); // System.out.println("参与签名total_fee:" + wxPayModel.getTotalFee()); // System.out.println("参与签名spbill_create_ip:" + wxPayModel.getSpbillCreateIp()); // System.out.println("参与签名notify_url:" + notify_url); // System.out.println("参与签名device_info:" + device_info); // System.out.println("参与签名trade_type:" + WxPayConstant.trade_type); // System.out.println("参与签名openid:" + wxPayModel.getOpenid()); // // System.out.println("++++++++++++++++++++++++++++++++++++++++");   SortedMap<Object, Object> parameters = new TreeMap<Object, Object>();
        parameters.put("appid", appid);
        parameters.put("mch_id", mch_id);
        parameters.put("nonce_str", nonce_str);
        parameters.put("body", wxPayModel.getBody());
        parameters.put("out_trade_no", wxPayModel.getOutTradeNo());
        parameters.put("total_fee", wxPayModel.getTotalFee());
        parameters.put("notify_url", notify_url);
        parameters.put("device_info", device_info);
        parameters.put("trade_type", WxPayConstant.trade_type);
        parameters.put("openid", wxPayModel.getOpenid());

        String characterEncoding = "UTF-8";

        StringBuffer sb = new StringBuffer();
        Set es = parameters.entrySet();// 所有参与传参的参数按照accsii排序(升序)  Iterator it = es.iterator();
        while (it.hasNext()) {
            Map.Entry entry = (Map.Entry) it.next();
            String k = (String) entry.getKey();
            Object v = entry.getValue();
            if (null != v && !"".equals(v) && !"sign".equals(k) && !"key".equals(k)) {
                sb.append(k + "=" + v + "&");
            }
        }
        sb.append("key=" + WxPayConstant.key);

        String sign = MD5Util.MD5Encode(sb.toString(), characterEncoding).toUpperCase();
        return sign;
    }

生成xml(reqBody)后,执行以下访问微信网址的代码:

URL httpUrl = new URL("https://api.mch.weixin.qq.com/pay/unifiedorder");
  HttpURLConnection httpURLConnection = (HttpURLConnection) httpUrl.openConnection();
      httpURLConnection.setRequestProperty("Host", "api.mch.weixin.qq.com");
      httpURLConnection.setDoOutput(true);
      httpURLConnection.setRequestMethod("POST");
      httpURLConnection.setConnectTimeout(10*1000);
      httpURLConnection.setReadTimeout(10*1000);
      httpURLConnection.connect();
  OutputStream outputStream = httpURLConnection.getOutputStream();
      outputStream.write(reqBody.getBytes("UTF8"));//reqBody是上面的费大力生成的xml

  //获取内容  InputStream inputStream = httpURLConnection.getInputStream();
  BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream, "UTF8"));
  final StringBuffer stringBuffer = new StringBuffer();
  String line = null;
      while ((line = bufferedReader.readLine()) != null) {
      stringBuffer.append(line);
  }
  String resp = stringBuffer.toString();

      if (stringBuffer!=null) {
      try {
          bufferedReader.close();
      } catch (IOException e) {
          e.printStackTrace();
      }
  }
      if (inputStream!=null) {
      try {
          inputStream.close();
      } catch (IOException e) {
          e.printStackTrace();
      }
  }
      if (outputStream!=null) {
      try {
          outputStream.close();
      } catch (IOException e) {
          e.printStackTrace();
      }
  }

      System.out.println("++++++++++++++++++++++");
      System.out.println("获取预支付id返回的结果为:"+resp);
      System.out.println("++++++++++++++++++++++");

  Map<String, String> hm;
  String prepay_id = "";

try {
      hm = WXPayUtil.xmlToMap(resp);

      System.out.println("++++++++++++++++++++++");
      System.out.println("支付id:"+hm.get("prepay_id"));
      System.out.println("++++++++++++++++++++++");

      prepay_id = hm.get("prepay_id");
  } catch (Exception e) {
      e.printStackTrace();
  }
得到: prepay_id  后,第一步搞定。  

第二步详解:

逻辑:调用微信一段js代码,按微信要求填写参数,访问成功后,微信会弹出输入密码付款的界面,付款后,微信将进入回调。

特殊:这一步有两种方式,第一种方式按照微信文档上的javascript代码片段调用,将相应的参数替换后即可成功,即(如下):

备注:我是怎么调用都没有成功,一直提示WeixinJSBridge未定义,调了老半天后,因为时间有限,不得不换了第二种方式。

function onBridgeReady() {
       WeixinJSBridge.invoke('getBrandWCPayRequest', {
               "appId" : "${appid}", //公众号名称,由商户传入  "timeStamp" : "${timeStamp}", //时间戳,自1970年以来的秒数  "nonceStr" : "${nonceStr}", //随机串  "package" : "${packageValue}",
               "signType" : "MD5", //微信签名方式:  "paySign" : "${sign}" //微信签名  }, function(res) {
           // 使用以下方式判断前端返回,微信团队郑重提示:res.err_msg将在用户支付成功后返回 ok,但并不保证它绝对可靠。  if (res.err_msg == "get_brand_wcpay_request:ok") {
               alert("支付成功");
               window.location.href = "${ctx}/parent/user/buyList";
           } else if (res.err_msg == "get_brand_wcpay_request:fail") {
               alert('支付失败');
           } else if (res.err_msg == "get_brand_wcpay_request:cancel") {
               alert('支付取消');
           } else {
               alert(res.err_msg);
           }
       });
   }

if (typeof ('WeixinJSBridge') == "undefined") {
       if (document.addEventListener) {
           document.addEventListener('WeixinJSBridgeReady', onBridgeReady,
                   false);
       } else if (document.attachEvent) {
           document.attachEvent('WeixinJSBridgeReady', onBridgeReady);
           document.attachEvent('onWeixinJSBridgeReady', onBridgeReady);
       }
   } else {
       onBridgeReady();
   }
第二种方式,是根据微信文档附录上的javascript代码片段写成,如下:

wx.config({
       debug : true, // 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。  appId : "${appid}", // 必填,公众号的唯一标识  timestamp : "${timeStamp}", // 必填,生成签名的时间戳  nonceStr : "${nonceStr}", // 必填,生成签名的随机串  signature : "${signature}",// 必填,签名  jsApiList : ['chooseWXPay']
       // 必填,需要使用的JS接口列表  });

   // config信息验证后会执行ready方法,所有接口调用都必须在config接口获得结果之后,config是一个客户端的异步操作,所以如果需要在页面加载时就调用相关接口,则须把相关接口放在ready函数中调用来确保正确执行。对于用户触发时才调用的接口,则可以直接调用,不需要放在ready函数中。 wx.ready(function() {
       wx.chooseWXPay({//微信支付接口,可以先注释掉,当确定wx.ready执行后再放开

               appId: "${appid}",
               timestamp : "${timeStamp}", // 支付签名时间戳,注意微信jssdk中的所有使用timestamp字段均为小写。但最新版的支付后台生成签名使用的timeStamp字段名需大写其中的S字符  nonceStr : "${nonceStr}", // 支付签名随机串,不长于 32 位  package : "${packageValue}", // 统一支付接口返回的prepay_id参数值,提交格式如:prepay_id=\*\*\*)  signType : "MD5", // 签名方式,默认为'SHA1',使用新版支付需传入'MD5'  paySign : "${paySin}", // 支付签名   success : function(res) {
                     /* alert("支付成功,来自网页的消息:"+res.errMsg); */  if (res.errMsg == "chooseWXPay:ok") {
                        /* alert("支付成功"); */  } else if (res.errMsg == "chooseWXPay:fail") {
                        /* alert('支付失败'); */  } else if (res.errMsg == "chooseWXPay:cancel") {
                        /* alert('支付取消'); */  } else {
               alert(res.errMsg);
           }
       }
               });
   });

wx.error(function(res) {
       // config信息验证失败会执行error函数,如签名过期导致验证失败,具体错误信息可以打开config的debug模式查看,也可以在返回的res参数中查看,对于SPA可以在这里更新签名。  alert('配置信息验证出错:' + res);
   });

解释,当调用以上js代码后,会先走wx.config,当验证成功后,会走wx.ready,否则会走wx.error。

wx.config中的参数,需要在控制台中得到,然后传到前台页面的js中使用(注:除了下面四个参数外,还有调用微信支付接口chooseWXPay的参数,最后要将这些参数一起传给前台js中,开发阶段可以先只传下面四个,调通后再传支付接口需要的参数):

appId:写死的,就是第一步中的appid,注意区分大小写,这里的参数为:appId

timestamp:签名时间戳,都是小写!下方面有工具方法

nonceStr:随即字符串,用第一步的随即字符

signature:签名,下面有工具方法

/**  * 生成时间戳  *  * @return  */ public static String getTimeStamp() {
    return String.valueOf(System.currentTimeMillis() / 1000);
}

签名的获取比较复杂:

1,先通过access_token获取票据

2,通过票据获取签名

/**  * 获取accessToken  */ public static String getAccessToken() throws Exception {

    String appid = WxPayConstant.appid;
    String secret = WxPayConstant.secret;

    String url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=" + appid + "&secret=" + secret;

    System.out.println("+++++++++++++++++++++");
    System.out.println("获取accessTiken的url:"+url);
    System.out.println("+++++++++++++++++++++");

    HttpGet get = HttpClientConnectionManager.getGetMethod(url);
    HttpResponse response = httpclient.execute(get);
    String jsonStr = EntityUtils.toString(response.getEntity(), "utf-8");
    JSONObject object = JSON.parseObject(jsonStr);

    System.out.println("+++++++++++++++++++++");
    System.out.println("得到的access_token:"+object.toString());
    System.out.println("+++++++++++++++++++++");

    String accessToken = object.getString("access_token");

    return accessToken;
}

通过access_token获取临时票据

/**  * 获取临时票据  *  * @return  */ public static String getJsapiTicket() throws Exception {

    String access_token = getAccessToken();
    String url = "https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=" + access_token + "&type=jsapi";

    HttpGet get = HttpClientConnectionManager.getGetMethod(url);
    HttpResponse response = httpclient.execute(get);
    String jsonStr = EntityUtils.toString(response.getEntity(), "utf-8");
    JSONObject object = JSON.parseObject(jsonStr);

    System.out.println("++++++++++++++");
    System.out.println("临时票据json:" + object.toString());
    System.out.println("++++++++++++++");

    String ticket = object.getString("ticket");
    String expires_in = object.getString("expires_in");// 有效时间  String nowTime = object.getString("expires_in");// 当前时间   return ticket;
}
/**  * 生成js-sdk签名算法  *  * @param jsapi_ticket 临时票据  * @param url 是你前台页面的url,也就是写微信js代码的那个页面的地址,  * @param nonce_str 随即字符串,用第一步生的的随即字符  * @param timestamp 时间戳  * @return  */ public static String sign(String jsapi_ticket, String url, String nonce_str, String timestamp) {

    Map<String, String> ret = new HashMap<String, String>();
    String string1;
    String signature = "";

    // 注意这里参数名必须全部小写,且必须有序  string1 = "jsapi_ticket=" + jsapi_ticket + "&noncestr=" + nonce_str + "timestamp=" + timestamp + "&url=" + url;

    System.out.println("+++++++++++++++++++++++++++++");
    System.out.println("js-sdk签名算法:" + string1);
    System.out.println("+++++++++++++++++++++++++++++");

    try {
        MessageDigest crypt = MessageDigest.getInstance("SHA-1");
        crypt.reset();
        crypt.update(string1.getBytes("UTF-8"));
        signature = byteToHex(crypt.digest());
    } catch (NoSuchAlgorithmException e) {
        // TODO Auto-generated catch block  e.printStackTrace();
    } catch (UnsupportedEncodingException e) {
        e.printStackTrace();
    }
    return signature;
}
备注:关于随机数和时间戳,我分别只生成了一次,然后所有用到的地方都用的最开始生成的那一个,至于不用统一的值好不好使,没有测试过,勤劳的小伙伴们可以自己尝试一下。

调通wx.config后进入wx.ready。

到了这一步就要开始调用微信的支付js,里面有六个参数,其中四个参数用上面的值就可以,主要是package、paySign这两个参数。

package:"prepay_id="+prepay_id。记住,package的参数值一定是:"prepay_id="+prepay_id,不要忘记"prepay_id="

paySign:又一个签名,下面有签名工具方法。

public static String createPaySin(String packages, String timeStamp, String nonceStr) {

    SortedMap<Object, Object> parameters = new TreeMap<Object, Object>();
    parameters.put("appId", WxPayConstant.appid);
    parameters.put("timeStamp", timeStamp);// 时间戳==规则:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=4_2。看完仍是一脸迷茫的,没关系,我们有工具类。谁知道呢,直接调用就好了  parameters.put("nonceStr", nonceStr);
    parameters.put("package", packages);
    parameters.put("signType", "MD5");

    System.out.println("++++++++++++++++");
    System.out.println("第二个签名appId:"+WxPayConstant.appid);
    System.out.println("第二个签名timeStamp:"+timeStamp);
    System.out.println("第二个签名nonceStr:"+nonceStr);
    System.out.println("第二个签名package:"+packages);
    System.out.println("第二个签名signType:MD5");
    System.out.println("++++++++++++++++");

    String characterEncoding = "UTF-8";

    StringBuffer sb = new StringBuffer();
    Set es = parameters.entrySet();// 所有参与传参的参数按照accsii排序(升序)  Iterator it = es.iterator();
    while (it.hasNext()) {
        Map.Entry entry = (Map.Entry) it.next();
        String k = (String) entry.getKey();
        Object v = entry.getValue();
        if (null != v && !"".equals(v) && !"sign".equals(k) && !"key".equals(k)) {
            sb.append(k + "=" + v + "&");
        }
    }

    sb.append("key=" + WxPayConstant.key);
    String sign = MD5Util.MD5Encode(sb.toString(), characterEncoding).toUpperCase();

    System.out.println("++++++++++++++++");
    System.out.println("第二个签名:"+sign);
    System.out.println("++++++++++++++++");

    return sign;
}

参与签名的五个参数,到了这步它们应该都有值了,所以直接放进方法内,生成签名即可。

完成以上所有步骤,这时候可以运行调用尝试了!!!

如果一次成功,恭喜!!!如果没成功还得继续解决…

我遇到的问题:

支付目录未授权

这是在微信商户平台上填写的目录授权,实际上就是一个路径,要具体到支付页的上一级目录,比如你的支付页是a文件夹下的b.jsp,那么目录授权必须填到a。

我不是这个原因,这个我填的没问题,后来发现,我需要将我支付的接口也就是生成prepay_id  所在的接口,当成目录,填在商户平台上,最后终于调用成功。

备注:如果做支付的时候,微信返回不成功,又没有具体错误提示,这时候可以换手机试,手机分安卓和苹果,安卓手机上没有提示,不代表苹果上没有…我就是安卓手机上没有,后来在小伙伴的苹果手机上看到具体错误提示的